Search code examples
c#asp.net-mvcauthorizationidentityserver4roles

How do role-based authorization using identityserver4 for microservices architecture?


Prehistory:
I develop authentication and authorization for microservices using IdentityServer4, but have problem with authorization. I have two services:
- service with identityserver4, it's my auth service
- test MVC project

I successfully connect these services and use Hybrid flow, and authentication work successfully, and also my roles works, because I include roles in JWT token using custom ProfileService on auth service side.

Problem situation:
1. User have not any auth token. Try open page with [Authorize(Roles = "Admin")] attribute on MVC site and redirect to page on Auth service. 2. User enter login and password.
3. Got token with Role=Admin, redirect back to MVC site on Auth service.
4. Page opened for user, because he had Admin role.
5. Remove user from Admin role on Auth Service.
6. Reload page and page again opened fine for this user, and it's correct, because in token he had role admin.

Question: How I can actualize token after change roles or claims on auth service side?

IdentityServer Config:

public static IEnumerable<Client> GetClients()
        {
            return new Client[]
            {
                new Client
                {
                    ClientId = "Epp.Web.Mvc",
                    ClientName = "Единый Портал Потребителей",
                    AllowedGrantTypes = new List<string>{GrantType.Hybrid},
                    ClientSecrets = new List<Secret>
                    {
                        new Secret("secret".Sha256())
                    },
                    RequireConsent = false,
                    AllowAccessTokensViaBrowser = true,
                    AlwaysIncludeUserClaimsInIdToken = true,
                    AlwaysSendClientClaims = true,
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "epp",
                        "roles"
                    },
                    RedirectUris = new List<string>
                    {
                        "http://localhost:5002/signin-oidc",
                        "https://localhost:5003/signin-oidc"
                    },
                    PostLogoutRedirectUris = new List<string>{ "http://localhost:5002/signout-callback-oidc" },
                    AccessTokenLifetime = 60 * 10 
                }
            };
        }

MVC Startup:

services.AddAuthentication(options =>
                {
                    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = "oidc";
                })
                .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromHours(2))
                .AddOpenIdConnect("oidc", options =>
                    {
                        options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                        options.Authority = "https://localhost:5001";
                        options.ClientId = "Epp.Web.Mvc";
                        options.ResponseType = "code id_token";
                        options.ClientSecret = "secret";

                        options.GetClaimsFromUserInfoEndpoint = true;
                        options.SaveTokens = true;
                        options.RequireHttpsMetadata = false;


                        options.Scope.Add(IdentityServerConstants.StandardScopes.OpenId);
                        options.Scope.Add(IdentityServerConstants.StandardScopes.Profile);
                        options.Scope.Add("epp");
                        options.Scope.Add("roles");
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            NameClaimType = "name",
                            RoleClaimType = "role"
                        };
                    });

Solution

  • Actually the question is not specifically about OpenId-Connect, nor Identity server. It's more a generic question about how long to keep some cached (security) data. JWT is immutable by design, so you can treat it as a kind of cache. Once the cached item (or a jwt) expires, we get a new one. So the only solution is to set a reasonably short expiration for your bearer token and use a refresh one to "actualize" the bearer.

    I personally can't see how changing roles to scopes for a given service can help. That's about a bit different thing. We can more or less constantly define that application1 has access to service1.write scope, and by that restrict All but application1' users from access to the API. But what if the app is universal? No answer here.

    Another suggestion was to use reference tokens and invalidate when necessary. Well, you could do. But by default the API caches the reference token validation' result, so... We get the same problem in a new place.

    What should work is moving role based authorization into each service, as @VidmantasBlazevicius suggested, but again inside the service you most likely have some cache, so you again have to think how to invalidate it when necessary.