In this article, I will present to you a basic implementation of the refresh token mechanism that you can extend to your own needs.





Let’s start with the need of using the refresh tokens. When you make use of the token authentication (e.g. OAuth) and pass the tokens via Authorization HTTP header, usually, these tokens have a specific expiration time. Whether it’s a minute, 10 minutes, an hour or a week makes no big difference, as long as you can provide a way to generate the new token.

Most likely, you don’t want the user to login every time that the token expiration hits its limit. On the other hand, you don’t want to store the user credentials (email, login, password etc.) somewhere in memory (whether it’s a device, cookie or a local storage). What can you do then? Store the so-called refresh tokens instead, that can be used to recreate the access tokens.

You can download the whole sample by cloning a repository and the HTTP requests available as the Postman collection. Now, let’s start with the implementation, just beware that I’m not following here any specialized patterns, rich domain models and so on – it’s just a sample that works, not a sophisticated solution.

At first, let’s start with the models that we’re going to use:

public class User { public string Username { get; set; } public string Password { get; set; } } public class RefreshToken { public string Username { get; set; } public string Token { get; set; } public bool Revoked { get; set; } } public class JsonWebToken { public string AccessToken { get; set; } public string RefreshToken { get; set; } public long Expires { get; set; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class User { public string Username { get ; set ; } public string Password { get ; set ; } } public class RefreshToken { public string Username { get ; set ; } public string Token { get ; set ; } public bool Revoked { get ; set ; } } public class JsonWebToken { public string AccessToken { get ; set ; } public string RefreshToken { get ; set ; } public long Expires { get ; set ; } }

These should be rather straightforward. You might also notice, that we will be able to revoke the refresh token, so it can’t be used anymore if the user wishes so.

This is how we could define the business logic:

public interface IAccountService { void SignUp(string username, string password); JsonWebToken SignIn(string username, string password); JsonWebToken RefreshAccessToken(string token); void RevokeRefreshToken(string token); } 1 2 3 4 5 6 7 public interface IAccountService { void SignUp ( string username , string password ) ; JsonWebToken SignIn ( string username , string password ) ; JsonWebToken RefreshAccessToken ( string token ) ; void RevokeRefreshToken ( string token ) ; }

Into my AccountService implementation, I’ll inject the following services:

public AccountService(IJwtHandler jwtHandler, IPasswordHasher<User> passwordHasher) { _jwtHandler = jwtHandler; _passwordHasher = passwordHasher; } 1 2 3 4 5 public AccountService ( IJwtHandler jwtHandler , IPasswordHasher < User > passwordHasher ) { _jwtHandler = jwtHandler ; _passwordHasher = passwordHasher ; }

The IJwtHandler (which is responsible for generating JSON Web Tokens) can be found in a repository and the IPasswordHasher comes from Microsoft.AspNetCore.Identity namespace.

Let’s focus now on refreshing and revoking the tokens:

public JsonWebToken RefreshAccessToken(string token) { var refreshToken = GetRefreshToken(token); if (refreshToken == null) { throw new Exception("Refresh token was not found."); } if (refreshToken.Revoked) { throw new Exception("Refresh token was revoked"); } var jwt = _jwtHandler.Create(refreshToken.Username);; jwt.RefreshToken = refreshToken.Token; return jwt; } public void RevokeRefreshToken(string token) { var refreshToken = GetRefreshToken(token); if (refreshToken == null) { throw new Exception("Refresh token was not found."); } if (refreshToken.Revoked) { throw new Exception("Refresh token was already revoked."); } refreshToken.Revoked = true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public JsonWebToken RefreshAccessToken ( string token ) { var refreshToken = GetRefreshToken ( token ) ; if ( refreshToken == null ) { throw new Exception ( "Refresh token was not found." ) ; } if ( refreshToken . Revoked ) { throw new Exception ( "Refresh token was revoked" ) ; } var jwt = _jwtHandler . Create ( refreshToken . Username ) ; ; jwt . RefreshToken = refreshToken . Token ; return jwt ; } public void RevokeRefreshToken ( string token ) { var refreshToken = GetRefreshToken ( token ) ; if ( refreshToken == null ) { throw new Exception ( "Refresh token was not found." ) ; } if ( refreshToken . Revoked ) { throw new Exception ( "Refresh token was already revoked." ) ; } refreshToken . Revoked = true ; }

The logic is very simple here – just ensure that the refresh token exists and that it was not already revoked, so it can be used again and again. And when to create a new refresh token? For example, when the user authenticates:

public JsonWebToken SignIn(string username, string password) { var user = GetUser(username); if (user == null) { throw new Exception("Invalid credentials."); } var jwt = _jwtHandler.Create(user.Username); var refreshToken = _passwordHasher.HashPassword(user, Guid.NewGuid().ToString()) .Replace("+", string.Empty) .Replace("=", string.Empty) .Replace("/", string.Empty); jwt.RefreshToken = refreshToken; _refreshTokens.Add(new RefreshToken { Username = username, Token = refreshToken }); return jwt; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public JsonWebToken SignIn ( string username , string password ) { var user = GetUser ( username ) ; if ( user == null ) { throw new Exception ( "Invalid credentials." ) ; } var jwt = _jwtHandler . Create ( user . Username ) ; var refreshToken = _passwordHasher . HashPassword ( user , Guid . NewGuid ( ) . ToString ( ) ) . Replace ( "+" , string . Empty ) . Replace ( "=" , string . Empty ) . Replace ( "/" , string . Empty ) ; jwt . RefreshToken = refreshToken ; _refreshTokens . Add ( new RefreshToken { Username = username , Token = refreshToken } ) ; return jwt ; }

It’s up to you how the refresh token is going to look like, I decided to create a unique GUID and then make use of IPasswordHasher to create a secure, random string.

And pretty much that’s it, we can define the following controller in our API to see if everything works as expected:

public class AccountController : Controller { private readonly IAccountService _accountService; public AccountController(IAccountService accountService) { _accountService = accountService; } [HttpGet("account")] public IActionResult Get([FromBody] SignUp request) => Content($"Hello {User.Identity.Name}"); [HttpPost("sign-up")] [AllowAnonymous] public IActionResult SignUp([FromBody] SignUp request) { _accountService.SignUp(request.Username, request.Password); return NoContent(); } [HttpPost("sign-in")] [AllowAnonymous] public IActionResult SignIn([FromBody] SignIn request) => Ok(_accountService.SignIn(request.Username, request.Password)); [HttpPost("tokens/{token}/refresh")] [AllowAnonymous] public IActionResult RefreshAccessToken(string token) => Ok(_accountService.RefreshAccessToken(token)); [HttpPost("tokens/{token}/revoke")] public IActionResult RevokeRefreshToken(string token) { _accountService.RevokeRefreshToken(token); return NoContent(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class AccountController : Controller { private readonly IAccountService _accountService ; public AccountController ( IAccountService accountService ) { _accountService = accountService ; } [ HttpGet ( "account" ) ] public IActionResult Get ( [ FromBody ] SignUp request ) = > Content ( $ "Hello {User.Identity.Name}" ) ; [ HttpPost ( "sign-up" ) ] [ AllowAnonymous ] public IActionResult SignUp ( [ FromBody ] SignUp request ) { _accountService . SignUp ( request . Username , request . Password ) ; return NoContent ( ) ; } [ HttpPost ( "sign-in" ) ] [ AllowAnonymous ] public IActionResult SignIn ( [ FromBody ] SignIn request ) = > Ok ( _accountService . SignIn ( request . Username , request . Password ) ) ; [ HttpPost ( "tokens/{token}/refresh" ) ] [ AllowAnonymous ] public IActionResult RefreshAccessToken ( string token ) = > Ok ( _accountService . RefreshAccessToken ( token ) ) ; [ HttpPost ( "tokens/{token}/revoke" ) ] public IActionResult RevokeRefreshToken ( string token ) { _accountService . RevokeRefreshToken ( token ) ; return NoContent ( ) ; } }