Reject JWT Token in Asp.Net Core application - c#

I am using this library https://github.com/openiddict/openiddict-core and implemented the oAuth token flow.
// Register the OpenIddict services.
// Note: use the generic overload if you need
// to replace the default OpenIddict entities.
services.AddOpenIddict(options =>
{
// Register the Entity Framework stores.
options.AddEntityFrameworkCoreStores<ApplicationDbContext>();
// Register the ASP.NET Core MVC binder used by OpenIddict.
// Note: if you don't call this method, you won't be able to
// bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
options.AddMvcBinders();
// Enable the token endpoint (required to use the password flow).
options.EnableTokenEndpoint("/connect/token");
// Allow client applications to use the grant_type=password flow.
options.AllowPasswordFlow();
// During development, you can disable the HTTPS requirement.
options.DisableHttpsRequirement();
});
Mostly everything is working. I also included RoleManager along with it and roles are being stored in token as well using this:
var principal = await _signInManager.CreateUserPrincipalAsync(user);
foreach (var claim in principal.Claims.Where(c => c.Type == OpenIdConnectConstants.Claims.Role))
{
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
So far everything is done. After that admin user goes and modified the role to make him Supervisor etc. At this point of role change I want to make the previous tokens invalid. Is this possible in the library because at the moment he is able to use old token which has "Maid" role and is incorrect because somebody just changed his role to "Supervisor". In this case I want that when client sends old token it should not be treated as valid and should be sent back to login page.
How do I handle this in OpenIDDict library?

Related

Fetch permissions from identity server during authorization

I am using identity server 4 for authentication and authorization, and user permissions are saved in JWT and then used on API-s to check if users has required permission.
But the problem is that JWT got too big and I would like to remove permissions from it, and make custom authorization on API-s so that its fetches permissions from identity server instead of getting it from JWT.
API would get only userId from JWT and then based on that fetch additional information from identity server. Is it possible to do something like that?
We basically have a similar problem in our application.
The way to solve this problem is using an event which is raised at the level of the API resource (the API which you are protecting by using JWT bearer tokens authentication) once the JWT token has been read from the incoming request and validated.
This event is called OnTokenValidated, see here for more details.
This is the top level plan:
keep your JWT bearer token minimal. At the very minimum it contains the subject id, which is the unique identifier of the user at the identity provider level. You can put other claims there, but the idea is that the JWT bearer token must be small
implement a way to get the user permissions given the user unique identifier (you can use the subject id as an identifier or any other id which makes sense in your system)
make the user permissions fetch mechanism of the previous point accessible via api call. Caching this API is a good idea, because usually permissions are stable. Defining a smart way to evict this cache is beyond the scope of this answer, but it's something you should definitely think about.
once you have fetched the user permissions (via an API call) you need to make them available to the ASP.NET core authorization framework. The simplest way to do so is create a custom claim type (for instance: "app_permission") and create one user claim per each user permission. Each of these permission claims has the custom claim type ("app_permission") and the permission name as the claim value. For instance a user having the two permissions "read-content" and "write-content" will have two claims both having "app_permission" as the claim type, the first one having "read-content" as the claim value and the second one having "write-content" as the claim value.
the permissions claims defined at the previous point can be injected in the user identity (at the API resource level) by defining an additional ClaimsIdentity for the user and by adding it to the current user identity. The process depicted here is quite similar to a claims transformation done by an MVC application using cookie authentication.
In the Startup class of your API resource, in the point where you register the authentication services, you can do something like this:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://localhost:8080";
options.Audience = "sample-api";
options.RequireHttpsMetadata = false;
// register callbacks for events
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
if (!context.Principal.Identity.IsAuthenticated)
{
return;
}
var subjectId = context.Principal.FindFirst(JwtClaimTypes.Subject)?.Value;
if (string.IsNullOrWhiteSpace(subjectId))
{
return;
}
// do whatever you want with the user subjectId in order to get user permissions.
//You can resolve services by using context.HttpContext.RequestServices which is an instance of IServiceProvider
//Usually you will perform an API call to fetch user permissions by using the subject id as the user unique identifier
// User permissions are usually transformed in additional user claims, so that they are accessible from ASP.NET core authorization handlers
var identity = new ClaimsIdentity(userPermissionsClaims);
context.Principal.AddIdentity(identity);
}
};
});
+1 for the accepted answer but I would just like to offer an alternative solution to this problem. If your permissions are pretty simple like readResource or writeResource then you could define all your permissions as enum and use integers instead of strings in JWT, that would reduce JWT size.
If permission list is still huge then you could also group permissions together so that the permission list is smaller for some customers e.g. merge readResource, writeResource, updateResource, deleteResource into one permission called crudResource.

IdentityServer4 - Delegated access tokens for clients

In IdentityServer4 you can specify an extension grand to enable delegated access tokens for users, so if a webservice needs to call another webservice during a request from a user, it can request a new access token from the IdentityServer with a valid scope and audience. My issue is, that this grant is thought to be used "on behalf of the interactive user", which leads to my question:
If my client calls a webservice which needs to call another webservice during the request, how can i create a delegated access token?
I tried to modify the example from the documentation, but the extension grand expects a token from a user, which holds a "sub" claim, and not from a client, which does not hold a "sub" claim.
The client is a daemon application, so it runs fully automated and without any user interaction, and is authenticated with the client credentials flow.
To clarify what i mean:
Here we see an use case, if a user is present:
User accesses a UI
UI redirects the user to the identity server to authenticate (With the Authorization Code Flow + PCKE). The UI gets the access token back
The UI calls an WebApi A with the access token
The WebApi A needs to access the WebApi B, but the access token was meant for WebApi A. Therefore the WebApi A asks the IdentityServer for a delegated access token to access WebApi B.
The IdentityServer provides the delegated access token.
The new access token is passed along to the WebApi B.
Here we see the same use case, but no user is present and a daemon application does the same thing:
The deamon appliaction authenticates against the IdentityServer with the Client Credentails Flow and gets the access token back
The deamon appliaction calls an WebApi A with the access token
The WebApi A needs to access the WebApi B, but the access token was meant for WebApi A. Therefore the WebApi A asks the IdentityServer for a delegated access token to access WebApi B.
How to get the IdentityServer to provide a the delegated access token for the client?
The new access token is passed along to the WebApi B.
For machine to machine (Service to service) communication you typically use the client credientials grant. This allows services to communicate even if no user is present. The userid (Subject) is typically included in the API calls that is protected using this flow.
See this article
The communication between WebApi-A and WebApi-B can be done using client credentials flow and here you don't need to pass any access token from the user. Instead you pass the subjectId(userID) and optionally some additional claims as part of the API calls between A and B. This way is much simpler and the benefit is that A-B can communicate without any user involved (for example to do workflow style communication in the background).
I have found the solution to my issue. You can extend the given example implementation of the delegation grand and extend it in a way, that a delegation token is issued for a client:
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var oldToken = context.Request.Raw.Get("token");
if (string.IsNullOrEmpty(oldToken))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var result = await _validator.ValidateAccessTokenAsync(oldToken);
if (result.IsError)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (sub != null)
{
// The old token had a user context
context.Result = new GrantValidationResult(sub, GrantType);
}
// The "magic" is the part below
else
{
// The old token had a client context
var client = result.Claims.FirstOrDefault(c => c.Type.Equals("client_id", StringComparison.Ordinal));
if (string.IsNullOrWhiteSpace(client?.Value))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient);
return;
}
context.Request.ClientId = client.Value;
context.Result = new GrantValidationResult(new Dictionary<string, object>());
}
}

JwtBearer Authentication with Custom Authorization in .net core 2.1

We have a Gateway (implemented using Ocelot), which performs both Authentication & Authorization of the calls before it reaches the APIs
For Authentication, the gateway uses JwtBearer like below
services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = JwtBeaerEvents();
options.TokenValidationParameters = TokenValidationParameters(tokenConfig);
});
And, this validates the token correctly.
Apart from this, the Gateway is implemented with Custom Authorization, to which it reads the permission related settings using a custom configuration file. And, this Custom Authorization is added as a middleware
We try to add this Authorization middleware after Authentication middleware, like
app.UseAuthentication().UseAuthorizationMiddleware();
This works for a valid token. However, for an invalid token, irrespective of Authentication got failed, the call is being routed to AuthorizationMiddleware as well. And, based on these findings, looks like we need to go with DI, rather than middleware. But, what we want is a custom implementation for Authorization which accepts the permissions/policy/scope via config file (in the gateway) along with JwtBearer scheme, rather than decorating them in the API attribute. Could anyone throw some light on how to achieve the same?
Your help is much appreciated
The issue is due to the behaviour of .net core. When the Identity's IsAuthenticated flag is false, Http StatusCode is not set to 401 by the framework in case of Token validation failure during Authentication and also it proceeds to the next call. If only we used the Policy based Authorization, it would have been automatically taken care by RequireAuthenticatedUser() while building the Authorization Policy. However, since we are using a custom middleware, introduced one another middleware which replicates what DenyAnonymousAuthorizationRequirement does, like below
var user = httpContext.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (userIsAnonymous)
{
httpContext.Response.StatusCode = 401;
return Task.CompletedTask;
}
return _next(httpContext);
We placed this middleware in between Authentication & Authorization middlewares and the issue has been resolved

How to sign out other user in ASP.NET Core Identity

How can i sign out another user (not the currently logged one) in ASP.NET Core Identity.
I know there is a SignOutAsync() method in SignInManager, but there seems to be no override accepting user as argument. I'm looking for something like:
signInManager.SignOutAsync(user);
First update the security stamp of that user:
await userManager.UpdateSecurityStampAsync(user)
Then that user won't be noticed the changes until the arrival of the SecurityStampValidationInterval. So set it to Zero for the immediate logout:
services.AddIdentity<User, Role>(identityOptions =>
{
// enables immediate logout, after updating the user's stat.
identityOptions.SecurityStampValidationInterval = TimeSpan.Zero;
}
Update: For ASP.NET Core Identity 2.x, 3.x, 5.x
services.Configure<SecurityStampValidatorOptions>(options =>
{
// enables immediate logout, after updating the user's stat.
options.ValidationInterval = TimeSpan.Zero;
});
I think you might find some revoke functionality, which make sign out user forcefully. It is not easily implemented currently as the nature of stateless connection and token-based (or we can say claim-based) authentication.
A revoked user should be accessed to a token validation endpoint in order to check the token valid or not. Until then, (1) the user could be shown as a signed-in, or (2) we need to implement client(app or web) to access to the token endpoint very frequently till token expiration or revokation.
SignIn/Out is tighted to token-authorized user identity scope, so that the viable solution is to invalidate a token.

Retrieve bearer token in ASP.NET WebAPI

I have a Web API with authentication enabled (bearer token). This is called by a client application and I want to protect it from anonymous usage so I would like to create a single user and create a bearer token for it.
I can create the token by calling the register and token methods, but I would like to do this from code.
As far as I know, the bearer token is not stored in the database. Can it be retrieved somehow using the ASP.NET Identity API?
I would also like to create this user from code and save the token somewhere because I need to deploy the database to multiple servers.
I do not recommend going with this approach if you have only one client who will talk to your API, my understanding that you need to issue a very very long lived access token maybe for a year and keep using this token to access the back-end API, right?
What you will do if this token is stolen? You can't revoke the access token, so it is somehow like your master key (password).
My recommendation is to use OAuth refresh tokens along with access tokens. This depends on the type of your client, you can check how this is done here http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/
The refresh tokens can be revoked and they can expire after a very long time. Let me know if you need further details to implement this.
Create a Custom Authentication Attribute and store the token hashes for users. A user can have multiple tokens. Then you can let user do what he wants - log out all other sessions when password is changed or remove sessions selectively
public class CustomAuthAttribute : System.Web.Http.AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext context)
{
var accessToken = HttpContext.Current.Request.Headers["Authorization"];
var hash = accessToken.Md5();
//store the hash for that user
//check if the hash is created before the password change or its session was removed by the user
//store IP address and user agent
var isBlackListed = ...
.....
return !isBlackListed && base.IsAuthorized(context);
}
}
If you're needing to decode the token within a WebAPI Controller function, I found this worked:
String token = Request.Headers.Authorization.Parameter;
Microsoft.Owin.Security.AuthenticationTicket t = Startup.OAuthOptions.AccessTokenFormat.Unprotect(token);

Categories