0
votes

I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.

Now, how to authorize uses before some action or inside action in my api/app controller?

I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.

But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.

I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).

Question how? Or What I have to looking for?

1

1 Answers

0
votes

So there's a few possible solutions here:

  • Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
  • Lower the cookie lifetime to refresh user info automatically more often
  • Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
  • Have IdentityServer force single sign out when user profile data is changed

In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)

Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.

The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:

  1. On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
  2. Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
  3. The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal

Code Samples

Get updated UserInfo from endpoint on each call

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
    Events = new CookieAuthenticationEvents()
    {
        OnValidatePrincipal = async context =>
        {
            // Get updated UserInfo from IdentityServer
            var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
            var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
            var userInfoResponse = await userInfoClient.GetAsync(accessToken);

            // Invalidate Principal if Error Response
            if (userInfoResponse.IsError)
            {
                context.RejectPrincipal();
                await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
            }
            else
            {
                // Check if claims changed
                var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
                if (claimsChanged)
                {
                    // Update claims and replace principal
                    var newIdentity = context.Principal.Identity as ClaimsIdentity;
                    newIdentity.AddClaims(userInfoResponse.Claims);
                    var updatedPrincipal = new ClaimsPrincipal();
                    context.ReplacePrincipal(updatedPrincipal);
                    context.ShouldRenew = true;
                }
            }
        }
    }
});

Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
    Events = new CookieAuthenticationEvents()
    {
        OnValidatePrincipal = async context =>
        {
            // Get User ID
            var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");

            var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
            var userChanged = await userChangedService.HasUserChanged(userId);

            if (userChanged)
            {
                // Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
            }
        }
    }
});

The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes

Hope this helps!