Search code examples
asp.net-corecookiesjwtasp.net-identityidentity

Update user claims on site if claims changed on my identity server's database


I have a couple of sites that require a logged user with specific permissions like role, group. So i decided to create my own identity server with the user's database. I used JWt-token and cookie auth to achieve my goal. When a not-logged-in user tries to get access to any of my sites, the site redirects him to the identity server's login page. After successful login, the server generates JWT token with the user's claims

private async Task<string> CreateAccessToken(User user)
        {
            var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(ClaimTypes.Email, user.Email)
            };

            var userRoles = await _userManager.GetRolesAsync(user);

            foreach (var userRole in userRoles)
            {
                claims.Add(new Claim(ClaimTypes.Role, userRole));
                var role = await _roleManager.FindByNameAsync(userRole);
                if (role != null)
                {
                    var roleClaims = await _roleManager.GetClaimsAsync(role);
                    foreach (Claim roleClaim in roleClaims)
                    {
                        claims.Add(roleClaim);
                    }
                }
            }

            var jwt = new JwtSecurityToken(
                    issuer: AuthOptions.ISSUER,
                    audience: AuthOptions.AUDIENCE,
                    claims: claims,
                    expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(4)),
                    signingCredentials: new SigningCredentials(AuthOptions.GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256));
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            return encodedJwt;

        }

which will be extracted and stored in the cookie file on client PC to work with site's resources.

public async Task<IActionResult> TokenForCookie(string token)
        {
            HttpContext.Session.SetString("accesstoken", token);
            var JWThandler = new JwtSecurityTokenHandler();
            var validations = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = AuthOptions.ISSUER,
                ValidateAudience = true,
                ValidAudience = AuthOptions.AUDIENCE,
                ValidateLifetime = true,
                IssuerSigningKey = AuthOptions.GetSymmetricSecurityKey(),
                ValidateIssuerSigningKey = true
            };

            SecurityToken tokenSecure;
            ClaimsPrincipal claimsPrincipal;

            try
            {
                claimsPrincipal = JWThandler.ValidateToken(token, validations, out tokenSecure);
            }
            catch (Exception ex)
            {
                HttpContext.Response.StatusCode = 500;
                return new JsonResult(new { success = false, message = "Token validation error" });
            }

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal,
    new AuthenticationProperties
    {
        IsPersistent = true,
        AllowRefresh = true
    });

            return RedirectToPage("/Index");
        }

This works fine for me, but the only problem is: how can i update user's claims if the identity server's admins changed them or if they changed manually in the database?

I googled some articles and seems like i need to create some validator added to each request for comparing claims between resources sites and identity server. I am new to the asp.net core and identity process so i spent more than a week building at least something like that. If you know a better way how this can be done, please help me to find the right way


Solution

  • There is general solution for api scenario, which is refresh token.
    When user login, he get access token & refresh token.

    The access token with user principals exipred in a shorter time such as 1 day. This prevents the old principals(roles) keeps being effective. When access token expired, you can get a new access token just using the refresh token. thus you don't have to login again to get access token, the refresh token authenticate for you.

    refresh token has longer expire time such as months, but it doesn't contain any information of user principals(roles). It is just used for getting new access token which contains user principals(roles). Refresh token is simply generated , just a random code:

            private static string GenerateRefreshToken()
            {
                var randomNumber = new byte[64];
                using var rng = RandomNumberGenerator.Create();
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
    

    For how refresh token work,you can try this tutorial with codes can be downloaded. https://www.c-sharpcorner.com/article/jwt-authentication-with-refresh-tokens-in-net-6-0/

    Your project used cookie so you should make the cookie expired more quickly. Then use refresh token to get access token by each request ,or use javascript timer to make this request at background every day to make an update.