How to manage with Refresh Token in ASP.NET Core 2.0 Web API

What Will I Learn?

  • Using JSON Web Token
  • Manage Refresh Token
  • Create professional authorization in WEB API project

Requirements

  • C#
  • .NET Core 2.0
  • Visual Studio 2015+/Visual Studio Core
  • knowledge from my previous post about JWT basic

Difficulty

  • Intermediate

Tutorial Contents

This is continuation of my previous post where i showed you how to create JWT, how to add data to appsetting.json. In this tutorial I will show You how to manage refresh Token, so how to create 2 steps authorization.

What are access and refresh token?

  • access token is JWT Token which have very short expiry date, this is important because you can use this token for only few minutes. After this time it will expire and you have to get new one. When someone will steal your Token he will be able to use it just for a few minutes. It makes your application much more safe.

  • refresh token in long term token, is it used to create new access token.

Implementation

I will be working in the project from previous tutorial, I will add some necessary classes and functions.
Let's start.

At the beginning we will add to appsettings.json one more definition about expiration our tokens:

"Tokens": {
    "Key": "TokensSecretKey,
    "AccessExpireMinutes": "5",
    "RefreshExpireMinutes": "1000"
  },

As you can see I set 5 minutes long for access token and 1000 minutes for refresh token.

Now we will create some resources for our tokens:

image.png

  • TokenResource
    public class TokenResource
    {
        public string Token { get; set; }
        public long Expiry { get; set; }
    }
  • AuthorizationTokensResource
    public class AuthorizationTokensResource
    {
        public TokenResource AccessToken { get; set; }
        public TokenResource RefreshToken { get; set; }
    }

If we have created two classes with resources we have to change our JwtHandler. Because we have 2 different tokens, we have to create two methods for these tokens.

  • Create Access Token method:
public TokenResource CreateAccessToken(Guid userId, string email, Role role)
        {
            var now = DateTime.UtcNow;
            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
                new Claim(JwtRegisteredClaimNames.UniqueName, userId.ToString()),
                new Claim(ClaimTypes.Email, email),
                new Claim(ClaimTypes.Role, role.ToString()),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, now.ToTimeStamp().ToString(), ClaimValueTypes.Integer64),
            };

            var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"])),
                SecurityAlgorithms.HmacSha256);
            var expiry = now.AddMinutes(double.Parse(_configuration["Tokens:AccessExpireMinutes"]));
            var jwt = CreateSecurityToken(claims, now, expiry, signingCredentials);
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);

            return CreateTokenResource(token, expiry.ToTimeStamp());
        }
  • Create Refresh Token method:
public TokenResource CreateRefreshToken(Guid userId)
        {
            var now = DateTime.UtcNow;
            var claims = new Claim[]
            {
                new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
                new Claim(JwtRegisteredClaimNames.UniqueName, userId.ToString()),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, now.ToTimeStamp().ToString(), ClaimValueTypes.Integer64),
            };

            var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"])),
                SecurityAlgorithms.HmacSha256);
            var expiry = now.AddMinutes(double.Parse(_configuration["Tokens:RefreshExpireMinutes"]));
            var jwt = CreateSecurityToken(claims, now, expiry, signingCredentials);
            var token = new JwtSecurityTokenHandler().WriteToken(jwt);

            return CreateTokenResource(token, expiry.ToTimeStamp());
        }

As you can see, in refresh token we don't have defined role of user and his email. This token has only basic information, only those what are necessary for match user with token.

Now we will create method for refreshing our token. The flow of application should be like this:

  • ask for tokens -e.g login to application
  • return access and refresh token to user after successful login
  • user send reguest for some resource from server, he has to put in header his access token
  • server verify access token and if everything is ok, return 200
  • user again send request to server
  • server verify that access token is expired then return - unatorizes status
  • user send to server his resfesh token
  • server verify user's refresh token and if everything is ok, return new access token and updateg refresh token

method for refreshing token:

public async Task<TokenResource> RefreshToken(Guid userId, string refreshToken)
        {
            var user = await _unitOfWork.Users.GetAsync(userId);
            if (user == null)
            {
                throw new ServiceExceptions(ErrorCodes.InvalidRefreshToken, "Invalid refresh token");
            }

            var tokenHash = _encrypter.GetHash(refreshToken, user.Salt);
            var session = user.Sessions.FirstOrDefault(s => s.Token == tokenHash);
            if (session == null || session.IsExpired())
            {
                throw new ServiceExceptions(ErrorCodes.InvalidRefreshToken, "invalid refresh token");
            }
            session.Refresh();
            await _unitOfWork.CompleteAsync();

            return _jwtHandler.CreateAccessToken(user.Id, user.Email, user.Role);
        }

Now we just have to create one more method in our controller for refreshing token:

[HttpPost]
        [Authorize]
        [Route("account/refreshToken")]
        public async Task<IActionResult> RefreshToken([FromBody]RefreshTokenResource resource)
        {
            var userId = new Guid(User.Identity.Name);
            var accesToken = await _accountService.RefreshToken(userId, resource.RefreshToken);
            return Ok(accesToken);
        }

Thank you for attention, if you want to learn more about ASP.NET Core Web API you can check my profile, or section below -> Curriculum

Curriculum



Posted on Utopian.io - Rewarding Open Source Contributors

H2
H3
H4
3 columns
2 columns
1 column
Join the conversation now
Logo
Center