Search code examples
c#asp.net-coreasp.net-identityidentityserver4

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?


Solution

  • 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);
                        }
                    };
                  });