Secure access only to controller in IdentityServer4 - asp.net-core

I created a basic IdentityServer4 as per tutorials http://docs.identityserver.io/en/release/quickstarts/0_overview.html.
This consists of APIserver, JSClient and ID4 Server.
OVerall all is good, now i am trying to go one step further, I want to create a basic controller in ID4Server called AuthorizedUserController that is restful, and that can only be accessed by Authorised user from JSClient.
I cannot find any examples on how to achieve this currently so hoping for some guidance.

You could make IdentityServer include bearer token authentication:
services.AddAuthentication()
.AddIdentityServerAuthentication("bearer", options =>
{
options.Authority = "you identityserver base url";
options.ApiName = "identityserver_api";
});
And then have an authorization policy that checks for the scheme and the client ID claim:
services.AddAuthorization(options =>
{
options.AddPolicy("JsClient", config =>
{
config.AddAuthenticationSchemes("bearer");
config.RequireClaim("client_id", "my javascript client");
});
});
And then add an authorize attribute to your controller that specifies this authorization policy:
[Authorize("JsClient")]

Related

IdentityServer4: [Authorize(Roles = "Admin")] not granting access to Admin User even when the JWT Token has {"role": "Admin"} claim

My problem is that even when the logged on user is an Admin still he is not able to access the Controller with [Authorize(Roles = "Admin")] attribute.
Note: The codes works perfectly fine on postman testing but not on openid connect.
IdentityServer 4
I have my Identity server 4 set up and working great with OpenId connect. No problem with it.
Client Project
There is a client probject just having a web api which is secured with [Authorize(Roles = "Admin")] attribute.
[ApiController]
[Route("[controller]")]
[Authorize(Roles = "Admin")]
public class WeatherForecastController : ControllerBase
{
......
}
It's URL is https://localhost:5002/WeatherForecast.
Now: I open this secured URL in the browser, I am redirected to the IdnentityServer login page where I enter the username and password of admin user.
After that what happens is that I am redirected to https://localhost:5002/Account/AccessDenied?ReturnUrl=%2FWeatherForecast page (which should not be as user has admin role).
On the console I get the error -
Authorization failed. These requirements were not met:
RolesAuthorizationRequirement:User.IsInRole must be true for one of
the following roles: (Admin)
On decoding the token on jwt site, I can clearly see "role": "Admin"there. See the below image:
So I can say My token has Admin role claim on it and it should therefore be able to access the secured controller. But it does not happen??
If i remove the roles from attribute attribute - [Authorize] then I am able to access the controller with the token. See image below:
The Codes:
The startup.cs ConfigureServices method is the place where I have added the Openid connect codes:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:5001";
options.Audience = "IS4API";
})
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "postman";
options.ResponseType = "code";
options.Scope.Add("fullaccess");
options.SaveTokens = true;
});
I am not sharing the codes for IdentityServer because the token is generating property and I don't think there is any problem on IdentityServer.
So what can be the problem here, why roles based IdentityServer 4 authentication failing on my case?
You have to bring the role claim types into your app. Inside .AddJwtBearer options there's a place to set the role claim type inside the token validation paramters:
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
NameClaimType = "name",
RoleClaimType = "role"
};
Then inside the config file in IDS4, add the Jwt claim type "Role" for your app:
new ApiScope
{
Name = "myAPI",
DisplayName = "my API",
Enabled = true,
UserClaims =
{ JwtClaimTypes.Name,
JwtClaimTypes.Email,
JwtClaimTypes.Subject,
JwtClaimTypes.Role,
JwtClaimTypes.Address,
JwtClaimTypes.Confirmation,
JwtClaimTypes.EmailVerified,
JwtClaimTypes.Id,
JwtClaimTypes.Profile
}
},
If it's working on postman, in my opinion something wrong in your openid connect configuration. Check the identity server openid connection documentation
This problem will happen for some reason. please check the below steps.
1.The problem can be in the order of Authentication and Authorization in the pipeline, make sure Authentication is placed before Authorization.
2.You must add the following line of code in Startup.cs to enable RoleManager.
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>() // <-- Add this line
.AddEntityFrameworkStores<ApplicationDbContext>();
3.According to this GitHub Link, Add this line of code to ConfigureServices
// Add Role claims to the User object
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
Please check all steps. I think it will help resolve your issue.

ASP.NET Core 3.1 web application use authorization for multiple areas using different authentication types

I have an ASP.NET Core 3.1 application which follows domain driven architecture and it has 2 areas, one for admin and other one for customers (application users).
I want to enable authentication and authorization for each area separately. For example use Identity 4 for the customer area and cookie base authentication for admin area. But it should be done using a single database and role base authentication should not used to separate areas.
What is the best approach to follow. For example "Multiple authentication scheme", Or any other method.
When it comes to login for admin and customer you can implement it by using acr_values (see definition in spec). Identity server can decide how to authenticate based on acr_values, for example if you provided admin_login as acr_values, then based on that Identity Server will authenticate user (use different identity provider or different database/table).
Your application needs to know whether user wants to login as customer or admin before you redirect to identity server authorize endpoint. In order to know that you will have to implement different authentication schemes in your application (one for admin and one for customer). Once you know user login type, you can add correct acr_values. Below code is not tested but it should give you an idea on how to implement it.
services.AddAuthentication(options =>
{
options.DefaultScheme = "CustomerCookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("CustomerCookie", options =>
{
options.Cookie.Name = "CustomerCookie";
options.ForwardChallenge = "oidc";
})
.AddCookie("AdminCookie", options =>
{
options.Cookie.Name = "AdminCookie";
options.ForwardChallenge = "admin-oidc";
})
.AddOpenIdConnect("oidc", options =>
{
// Configure all other options needed.
options.SignInScheme = "CustomerCookie";
options.CallbackPath = "/signin-oidc-customer";
options.Events.OnRedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.SetParameter("acr_values", "customer_login");
return Task.FromResult(0);
};
})
.AddOpenIdConnect("admin-oidc", options =>
{
// Configure all other options needed.
options.SignInScheme = "AdminCookie";
options.CallbackPath = "/signin-oidc-admin";
options.Events.OnRedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.SetParameter("acr_values", "admin_login");
return Task.FromResult(0);
};
});
On identity server side you have full control on what to do based on acr_values, you can use external provider for admin.
You could use IIdentityServerInteractionService.GetAuthorizationContextAsync to retrieve acr_values and you could implement IProfileService so that once authenticated, you can decide what claims to include based on user type (admin or customer).
That would be the basic idea, hopefully it is useful.

Can I create an Identity Server 4 ASP.NET Core API using 2 different token authentication middleware?

I am trying to figure out if its possible to write an ASP.NET Core API that consumes an identity server token using either Reference Tokens or JWT tokens based on whatever I've configured my identity server to use. The back-end configuration for IS4 is pretty easy, I'm just not convinced that I can configure 2 different token middlewares and my service will both be ok with it and know what to do.
So the idea is:
If my API gets a jwtToken, it attempts to use the jwt middleware for authorization back to identity server.
If my API gets a reference token, it attempts to use the introspection middleware for authorization back to identity server.
Obviously, if the wrong type of token is provided for whatever is configured on the IS4 service, it will fail.
Handling the token endpoint and revocation endpoint should also be easy enough, it's just the middleware magic I'm concerned with.
I know typically you wouldn't want to do this but we have a niche use case for it. All I'm currently concerned with is whether or not its even possible. I'm not familiar with how the auth middleware works in the back-end.
According to the Identity Server 4 Protecting APIs document, we can see that it supports to use both JWTs and reference tokens in asp.net core.
You can setup ASP.NET Core to dispatch to the right handler based on the incoming token, see this blog post for more information.
services.AddAuthentication("token")
// JWT tokens
.AddJwtBearer("token", options =>
{
options.Authority = Constants.Authority;
options.Audience = "resource1";
options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
// if token does not contain a dot, it is a reference token
options.ForwardDefaultSelector = Selector.ForwardReferenceToken("introspection");
})
// reference tokens
.AddOAuth2Introspection("introspection", options =>
{
options.Authority = Constants.Authority;
options.ClientId = "resource1";
options.ClientSecret = "secret";
});
Supporting both JWTs and reference tokens
In addition to #Zhi Lv post you might need to add Authorization policy, Authentication Schemes to allow validating JWT and reference tokens.
Here is the sample code template replace api name, api secret and audience appropriatly.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(Options =>
{
Options.Authority = "https://identity.domain.com/identity/";
Options.Audience = "resource1"; //your api baseurl e.g if you want userinfo_endpoint specify https://identity.domain.com/identity/connect/userinfo
Options.TokenValidationParameters.ValidTypes = new[] { "at+jwt" };
})
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://identity.domain.com/identity/";
options.ApiName = "api name / scope";
options.ApiSecret = "api secret / scope secret";
});
services.AddAuthorization(options =>
{
options.AddPolicy("tokens", x =>
{
x.AddAuthenticationSchemes("jwt", "introspection");
x.RequireAuthenticatedUser();
});
});
}
The way I would do it is to use introspection and claims caching in both cases, so that the API does not need to know or care which type of access token it receives.
The introspection would only occur when an access token is first received. Subsequent requests with the same token then use cached claims.
RESOURCES
Blog Post
Sample C# Code

.net Core 3.0 & OpenId Connect - How to configure authetication to validate Azure generated non JWT access token against UserInfo in a WebAPI?

So, we have a SPA that is calling a series a microservices that sits behind an API Gateway. The authentication scheme is OpenId flow against Azure AD. So, it goes as follows: SPA (Public client) gets the access code, then the API gateway (Confidential Client) call the token service in order to get the access token; this is what it gets forwarded to the microservices themselves. So, in the end, our microservices will receive just the non-JWT Access Token. It's important to note that this token is NOT a JWT. In order to validate we're forced to use the /openId/UserInfo endpoint in the azure tenant to check the user with the access token.
We've tried used the AddOpenIdConnect extension in startup, as described [here] https://learn.microsoft.com/en-us/dotnet/architecture/microservices/secure-net-microservices-web-applications/
{
//…
// Configure the pipeline to use authentication
app.UseAuthentication();
//…
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
var identityUrl = Configuration.GetValue<string>("IdentityUrl");
var callBackUrl = Configuration.GetValue<string>("CallBackUrl");
// Add Authentication services
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = identityUrl;
options.SignedOutRedirectUri = callBackUrl;
options.ClientSecret = "secret";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.RequireHttpsMetadata = false;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("orders");
options.Scope.Add("basket");
options.Scope.Add("marketing");
options.Scope.Add("locations");
options.Scope.Add("webshoppingagg");
options.Scope.Add("orders.signalrhub");
});
}
But this assumes the whole OpenId flow. Regardless of putting the Bearer token in the request, application redirects to the login page.
So the question is, is there any out-of-the-box configuration for this? Or we should rely in some custom handler? In such case, how the user can be properly authenticated within the context? We would need to access the HttpContext.User.Claims in the controllers themselves.
Any hint would be greatly appreciated!
Could I ask a couple of questions - partly for my own understanding:
What causes the token to not be a JWT - I don't think the link you posted explains this?
Are you using some form of token exchange from the original one issued to the SPA?
I have an equivalent solution that works, but I am using a free developer account, so maybe my setup is different:
Token validation code
Write up on messages and config
If I understand how to reproduce your setup - and a rough understanding of the config differences - I may be able to help.
Using the user info endpoint to validate tokens doesn't feel right. It would be good to get token validation working in the standard way.

Identity Server 4 Client Credentials for custom endpoint on token Server

I implemented a token server using Identity Server 4.
I added a custom API endpoint to the token server and struggle with the authentication. The custom endpoint is inherited from ControllerBase and has 3 methods (GET, POST, DELETE).
I intend to call the custom endpoint from within another API using a dedicated client with credentials (server to server) implemented as HttpClient in .NET Core. There is no user involved into this.
For getting the access token I use the IdentityModel DiscoveryClient and TokenEndpoint.
So in sum I did the following so far:
setup "regular" identity server and validate it works -> it works
implement custom endpoint and test it without authorizatio -> it works
add another api resource ("api.auth") with a custom scope "api.auth.endpoint1"
setup a client with client credentials allowing access to scope "api.auth.endpoint1".
implement the HttpClient and test setup -> I get an access token via the Identity Model Token Endpoint.
Now, when I call the endpoint using the HttpClient with the access token I received I get response code 200 (OK) but the content is the login page of the identity server.
The documentation of Identity Server 4 state the use of
services.AddAuthentication()
.AddIdentityServerAuthentication("token", isAuth =>
{
isAuth.Authority = "base_address_of_identityserver";
isAuth.ApiName = "name_of_api";
});
as well as the use of
[Authorize(AuthenticationSchemes = "token")]
Unfortunatly the compiler state that .AddIdentityServerAuthentication can't be found. Do I miss a special nuget?
The nugets I use on the token server so far are:
IdentityServer4 (v2.2.0)
IdentityServer4.AspNetIdentity (v2.1.0)
IdentityServer4.EntityFramework (v2.1.1)
Figured out that part. The missing nuget for AddIdentityServerAuthentication is:
IdentityServer4.AccessTokenValidation
Struggling with the authorization based on the custom scope.
Does anyone know how the security has to be configured?
Configure a client with ClientGrantTypes = client_credentials and your api like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth";
});
Where ApiName is the name of the resource. Please note that resource != scope. In most samples the resource name is equal to the scope name. But not in your case, where resource name is api.auth and scope name is api.auth.endpoint1.
Configure the client to request the scope.
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, secret);
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api.auth.endpoint1");
IdentityServer will lookup the Resource name and add that to the token as audience (aud) while the scope is added as claim with type scope.
This should be enough to make it work. Also check the sample project.
Custom authentication scheme and scope based policies for different access rights bundled together looks like that:
// Startup.ConfigureServices
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication("CustomAuthEndpointsAuthenticationScheme", options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "api.auth"; //IdentityServer4.Models.ApiResource.Name aka Audience
});
services.AddAuthorization(options =>
{
options.AddPolicy("Endpoint1Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint1"); } ); //IdentityServer4.Models.Scope.Name
options.AddPolicy("Endpoint2Policy", policy => {
policy.AddAuthenticationSchemes(new string[] { "CustomAuthEndpointsAuthenticationScheme" });
policy.RequireScope("api.auth.endpoint2"); } ); //IdentityServer4.Models.Scope.Name
} );
// securing the custom endpoint controllers with different access rights
[Authorize(AuthenticationSchemes = "CustomAuthEndpointsAuthenticationScheme", Policy = "Endpoint1Policy")]
It seems not to interfere with the IdentityServer4 default endpoints nor with the ASP.NET Core Identity part.