Tech

Oauth2 Refresh Token Explained Using .NET Core 6

Enviroment Settings Microsoft.AspNetCore.Authentication.JwtBearer     Version 6.0.11 Microsoft.IdentityModel.Tokens               Version  6.25.1 System.IdentityModel.Tokens.Jwt        Version  6.25.1 Microsoft.EntityFrameworkCore.InMemory Version 7.0.0 Scope/Assumptions This write up is continution of ASP.NET...

· 12 min read >

Enviroment Settings

Microsoft.AspNetCore.Authentication.JwtBearer     Version 6.0.11

Microsoft.IdentityModel.Tokens               Version  6.25.1

System.IdentityModel.Tokens.Jwt        Version  6.25.1

Microsoft.EntityFrameworkCore.InMemory Version 7.0.0

Scope/Assumptions

  1. We will make use of Entity Framework Core In-Memory database/cache , this is not an introduction to Entity Framework Core and scope is intentionally set to be very specific and limited to Refresh Token Grant. Most of the online articles floating around venture into full fledge database implementation and based on comments and feedback observed that many folks get stuck into entity framework core and SQL server settings , db migration and all . Instead Oauth Refresh grant flow is priority.
  2. The code intentionally does not follow many coding standard in order to minimze the boilerplate code, as every project has it’s prefered layout and structure and you may not end up using the same folder structure and pattern used here.
  3. we are assuming user sessions are not managed through traditional browser cookies.

Prerequisite Knowledge 

This write up is continution of ASP.NET CORE 6 JWT Authentication. The recommendation is to go through it or you can directly clone this repository. From here on we assume that either you went through article mentioned or you cloned the repository code & You carefully read the Scope section above to manage your expectations.

Let’s Recap in few lines what we accomplished in ASP.NET CORE 6 JWT Authentication.

we supplied username/password and exchanged it with the Access Token (limited expiry time) to access the protected resource. Once this token expire user re-autheticate and obtain the access token to access the protected resource.

Frequent re-authentication can diminish the perceived user experience of your application. Even if you are doing so to protect their data, users may find your service frustrating or difficult to use. This is where Refresh token can help us.

Note:
Refresh token is mostly about user experience on how often you want user to log out and again login in , rather a rock solid security measure however it can help to mitigate the risk to some level.

Why do I need use the Refresh Token ?

For improving user experience and mitigate the security risk to some level. So Access token should be short-lived so that, even if it is compromised, the attacker gets access only for shorter period.If something goes wrong, the refresh token can be revoked which means use must login again.

There will always be various edge scenarios that will neeed custom implementation based on the client requirement and some implementation may not be worth it due to maintenance overhead. The goal is to mitigate as much risk as we can while balancing the security , User experience and maintenance overhead cost.Remember Nothing is 100% secure once you put it online.

Refresh Token ?

refresh token can help us balance security with User Experience(UX). Since refresh tokens are usually longer-lived, the client application can get a new access tokens without having to ask the user to log in again.The point is how long after we want user to login again will dictate the expiry time of refresh token.

How information is Exchanged ?

jwt refresh token
resource rfc6749 www.rfc-editor.org
   (A)  The client requests an access token by authenticating with the
        authorization server and presenting user name and password.

   (B)  The authorization server authenticates the client and validates
        the Credentials, and if valid, issues an access token
        and a refresh token.

   (C)  The client makes a protected resource request to the resource
        server by presenting the access token.

   (D)  The resource server validates the access token, and if valid,
        serves the request.

   (E)  Steps (C) and (D) repeat until the access token expires.  If the
        client knows the access token expired, it skips to step (G);
        otherwise, it makes another protected resource request.

   (F)  Since the access token is invalid, the resource server returns
        an invalid token error.

   (G)  The client requests a new access token by authenticating with
        the authorization server and presenting the refresh token.  The
        client authentication requirements are based on the client type
        and on the authorization server policies.

   (H)  The authorization server authenticates the client and validates
        the refresh token, and if valid, issues a new access token (and,
        optionally, a new refresh token).

Note: In simple implementation scenario and some web application Resource server and authorization server can be done on one server. Like we will do in our implementation.

Let’s start coding for oauth2 refresh token implementation

1- Clone this repository (main) branch – repository. This repository is end result of this topic ASP.NET CORE 6 JWT Authentication – Listed below is the solution explorer view after cloning and opening it in the visual studio.

If you followed the previous topic mentioned above – you will know the code you cloned is capable of generating access token and we tested it using postman it is recommended that you should know how to generate access token and test it you can follow this

#Step10-GeneratingAccessToken&Testing

jwt refresh token
jwt refresh token

At this point you should have tested code you cloned and should know how to generate the access token , secure an endpoint and test it using postman. From next step Refresh Token implementation will start.

2- Now we have to install Nuget package – Let’s Access the package manager console using Menu bar Tools –> Nuget Package Manager —> Package Manager Console

jwt refresh token

Now before just copy pasting the install command . Remember this package is entity framwork in-memory database/cache. we need some way to store tokens and remove them. In real time enviorment it will be your database like sql server etc.

Install this package first using the command below. Type the command and press Enter to install it.

Install-Package Microsoft.EntityFrameworkCore.InMemory   -version 7.0.0

Build the project and make sure there is no error.

3- For holding Refresh Token , Inside Model Folder Add a class RefreshToken.cs . Pay attention we extended it from BaseModel class we will create this in next step.

namespace asp.net_core_6_jwt_authentication.Models
{
    public class RefreshToken:BaseModel

    {
        public RefreshToken(string token, string tokenType, DateTime expiration,string username)
        {
            Token = token;
            TokenType = tokenType;
            Expiration = expiration;
            Username = username;
        }

        public string Token { get; set; }
        public string TokenType { get; set; }
        public DateTime  Expiration { get; set; }
        public string Username { get; set; }
    }
}

4- Inside Model Folder Add a class BaseModel.cs . This class will be base class and will be used for entity framwork core in memory. The content of the class below

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace asp.net_core_6_jwt_authentication.Models
{
    public class BaseModel

    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Required]
        public int Id { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime ModifiedAt { get; set; }
    }
}

5- Inside model open file and Update LoginRequest.cs add the following two properties.

public string Token { get; set; }
public string RefreshToken { get; set; }

6- Inside Model Folder open and update file LoginResponse.cs . The content of the file is below.

namespace asp.net_core_6_jwt_authentication.Models
{
    public class LoginResponse : BaseModel

    { 
        public LoginResponse(string UserId, string Token, DateTime accessTokenExpiry)
        {
            this.Token = Token;
            this.UserId = UserId;
            AccessTokenExpiry = accessTokenExpiry;  
        }

        public string UserId { get; set; }
        public string Token { get; set; }
        public DateTime AccessTokenExpiry { get; set; }
        public RefreshToken RefreshToken { get; set; }     

    }

}

7- Next we need to add the DbContext class for Entity Framework Core . At root level I added a class with name AppDbContext. The content of class is below.

we added two Dbset one to save the refresh token and other to track/save user login activity. SaveChange over-ride method is used to populate two Base class properties with modified and created values automatically.

using asp.net_core_6_jwt_authentication.Models;
using Microsoft.EntityFrameworkCore;

namespace asp.net_core_6_jwt_authentication
{
    public class AppDbContext : DbContext

    {

        public AppDbContext(DbContextOptions<AppDbContext> dbContextOptions) : base(dbContextOptions)
        {

        }


        public DbSet<LoginResponse> Users { get; set; }
        public DbSet<RefreshToken> RefreshToken { get; set; }


        public override int SaveChanges()
        {
            var entries = ChangeTracker.Entries()
                .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);

            foreach (var entityEntry in entries)
            {
                if (entityEntry.Metadata.FindProperty("ModifiedAt") != null)
                {
                    entityEntry.Property("ModifiedAt").CurrentValue = DateTime.UtcNow;
                }
                if (entityEntry.Metadata.FindProperty("CreatedAt") != null)
                {
                    if (entityEntry.State == EntityState.Added)
                    {
                        //entityEntry.Property("UUID").CurrentValue = Guid.NewGuid();
                        entityEntry.Property("CreatedAt").CurrentValue = DateTime.UtcNow;
                    }
                }
            }
            return base.SaveChanges();
        }
    }
}

8- Add this db context inside Program.cs after this line — builder.Services.AddSwaggerGen();

you will need to import this lib at top of the file.

using Microsoft.EntityFrameworkCore;

            #region DBContext


            builder.Services.AddDbContext<AppDbContext>(options =>
                options.UseInMemoryDatabase("NotesAppInMemDb")
            );

            #endregion

9- Create a Folder by name Service and add interface IUserInterface.cs

We planned to add 4 methods for our Jwt Refresh Token

  • we need to record user login activity to track combination of access token and refresh token.
  • A method to get user detail.
  • Return a refresh token detail by username
  • Remove Refresh token from the database
using asp.net_core_6_jwt_authentication.Models;

namespace asp.net_core_6_jwt_authentication.Service
{
    public interface IUserInterface

    {
        // record the user login+ accesstoken+ refresh token
        void RecordUserLogin(LoginResponse user);

        // get User detail
        LoginResponse? GetUser(string email);

        // Return a refresh token detail for a user
        public RefreshToken? GetRefreshTokenDetail(string email);

        // Remove refresh token from the database
        public void RemoveRefreshToken(string username, string refreshToken);
        
     }
}

Let’s implement This interface. Add a interface implementation add a class name UserService.cs

using asp.net_core_6_jwt_authentication.Models;

namespace asp.net_core_6_jwt_authentication.Service
{
    public class UserService : IUserInterface
    {
        private readonly AppDbContext context;


        public UserService(AppDbContext context) 
        { 
            this.context= context;        
        }

        // record the user login+accesstoken+refresh token 
        public void RecordUserLogin(LoginResponse user)
        {
            var existingUser = GetUser(user.UserId);
            var refreshtoken = GetRefreshTokenDetail(user.UserId);

             if (existingUser == null) 
            { 
                this.context.Users.Add(user);               
            }
            else
            { 
                this.context.Users.Remove(existingUser);
                this.context.Users.Add(user);                
            }

            if (refreshtoken == null)
            {
                this.context.RefreshToken.Add(user.RefreshToken);
               
            }

            this.context.SaveChanges();
        }

        // get User detail
        public LoginResponse? GetUser(string email)
        {
            return this.context.Users.SingleOrDefault(x => x.UserId == email);
        }

        // Return a refresh token detail for a user
        public RefreshToken? GetRefreshTokenDetail(string email)
        {
      
     return this.context.RefreshToken.SingleOrDefault(x => x.Username == email);
        
       }

        // Remove refresh token from the database
        public void RemoveRefreshToken(string username,string refreshToken) {

            var entity = GetRefreshTokenDetail(username);

            if (entity != null)
            {
                this.context.RefreshToken.Remove(entity);
                this.context.SaveChanges();
            }
        }
    }
}
Oauth2 Refresh Token

10- we have to update the program.cs to integrate User Services and Also Update the JWT middleware. add the following line before var app = builder.Build();



builder.Services.AddScoped<IUserInterface, UserService>();

Now update the JWT Middleware , we added a property to validate life time to true.

Then we set ClockSkew to zero. we want token expiration time to be exact instead of 5 min later.


            // Add JWT Authentication Middleware - This code will intercept HTTP request and validate the JWT.
            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
                opt => {
                    opt.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8
                        .GetBytes(builder.Configuration.GetSection("AppSettings:Token").Value)),
                        ValidateIssuer = false,
                        ValidateAudience = false,
                        ValidateLifetime = true,
                        ClockSkew = TimeSpan.FromSeconds(0)
                    };
                }
              );

11- Finally Now let’s use all of the above and open and update the LoginController

add this import first

using asp.net_core_6_jwt_authentication.Service;

first we got to add a method to generate the refresh token we will add this method later to login action method to send the refresh token along with the access token.

In method below notice expiration – refresh token will be valid for 5 minutes after that user must login in to authenticate again. This can be changed based on the client requirement.

        // create Refresh Token Creation

        private RefreshToken CreateRefreshToken(string id)
        {
            var refreshToken = new RefreshToken
            (
                token: Guid.NewGuid().ToString(),
                expiration: DateTime.Now.AddMinutes(5),
                tokenType: "Refresh",
                 username:id
            );
            return refreshToken;
        }

then We need to add a Endpoint to Receive Request to provide the Refresh Token .

Let’s discuss What happens inside Refresh Token api endpoint.

To get Access Token we send three parameters refer to postman screenshot below.

  • Username
  • Access Token
  • Refresh Token
refresh token post request
   [HttpPost("refresh")]
        public IActionResult RefreshToken([FromBody] LoginRequest refreshTokenResource)
        {
            // find username + accessToken combination in the database

            var user = this.userService.GetUser(refreshTokenResource.UserName);
            var refreshtoken = this.userService.GetRefreshTokenDetail(refreshTokenResource.UserName);

            if (user == null) { return BadRequest(); }
            if (refreshtoken == null) { return BadRequest(); }
            if (user.Token != refreshTokenResource.Token) { return BadRequest("Not a Valid Access Token"); };

            if (refreshtoken.Expiration < DateTime.Now) {

                this.userService.RemoveRefreshToken(refreshTokenResource.UserName,refreshtoken.Token);
                return BadRequest("Refresh Token Expired. Please Login again"); 
            };

            //Update the user access token + refresh token
             
            var loginResponse = new LoginResponse(string.Empty, string.Empty, DateTime.UtcNow) { };

            string token = CreateToken(refreshTokenResource.UserName);
            loginResponse.AccessTokenExpiry = DateTime.Now.AddMinutes(2);

            loginResponse.Token = token;

            loginResponse.RefreshToken = new RefreshToken(string.Empty, string.Empty,    DateTime.UtcNow,string.Empty);


            loginResponse.RefreshToken = refreshtoken;
            loginResponse.UserId = refreshTokenResource.UserName;


            //update database with new access token/refresh token combination for the user.
            this.userService.RecordUserLogin(loginResponse);


            return Ok(new { loginResponse });

        }
  • we get the user info by username ,a bad request will be returned if no user is found with the provided username.
  • Now we check if access token provided by user match to one we have saved in the database for user if it dont match , Invalid Access Token response is returned.
  • Next Refresh Token is checked if it is not already expired – if expired Refresh Token Expired response is returned and user must authenticate again.
  • Finally we match the incoming Refresh Token with one we have save for user and if there is match we we send a new only ACCESS TOKEN back to user.

Notice that we did not created a new Refresh Token in last step . This mean after Refresh Token expire the user must login this period can be custom set to a week a month or a day per requirement . In Some scenario there is concept called Sliding Window where every time a new refresh token is sent back. Again All of this depend on the custom client implementation you are doing.

Next Inside CreateToken method update the expiring time of the token for testing purpose. I updated it to 2 min. this mean Access token will expire after 2 minutes and you will have to request a new token using the refresh token endpoint.

expires: DateTime.Now.AddMinutes(2),

12- Update the Authenticate Method with the following content.

public ActionResult<object> Authenticate([FromBody] LoginRequest login)
        {
            var loginResponse = new LoginResponse(string.Empty,string.Empty,DateTime.UtcNow) { } ;
            LoginRequest loginrequest = login;

            bool isUsernamePasswordValid = false;

            if (login != null)
            {
                // make await call to the Database to check username and password. here we only checking if password value is admin
                isUsernamePasswordValid = loginrequest.Password == "admin" ? true : false;
            }
            // if credentials are valid
            if (isUsernamePasswordValid)
            {
                string token = CreateToken(loginrequest.UserName);

                loginResponse.Token = token;

                loginResponse.RefreshToken = new RefreshToken(string.Empty, string.Empty, DateTime.UtcNow,string.Empty);


                loginResponse.RefreshToken = CreateRefreshToken(loginrequest.UserName);
                loginResponse.UserId = loginrequest.UserName;

                this.userService.RecordUserLogin(loginResponse);

                //return the token
                return Ok(new { loginResponse });
            }
            else

            {
                // if username/password are not valid send unauthorized status code in response               
                return BadRequest("Username or Password Invalid!");
            }
        }

Let’s discuss what is happening after we updated this Authenticate method below.

  1. we receive a request from client and if password is admin we assume these are valid credentials in real time it would be call to database with credentials to determine validity of credentials.
  2. Now credentials are valid we create a access token by using CreateToken method by passing a username to it.
  3. Next we create a Refresh Token using method CreateRefreshToken
  4. Next we record the user login request that contain user access token along with refresh token in entity framework in memory cache in real time it would be your actual database.
  5. Finally we sent the login Response back that contain access token and refresh token.

Now Update the constructor

public LoginController(IUserInterface _userService,IConfiguration configuration)
        {
            this.userService = _userService;
            _configuration = configuration;
        }

add this variable

private readonly IUserInterface userService;

Finally we have to add the Revoke token endpoint. This would help to invalidate the refresh token from the database.

       [HttpPost("revoke")]
        public IActionResult RevokeToken([FromBody] LoginRequest resource)
        {
            this.userService.RemoveRefreshToken(resource.UserName, resource.RefreshToken);
            return NoContent();
        }

Testing oauth2 refresh token implementation

1- get an access token + refresh token based on our code access token would be expire after 2 minutes and refresh token will expire after 5 minutes.

Payload is below and post call will be to https://localhost:yourport#/api/Login/login

{
    "Username": "testhuntearpiece@gmail.com",
    "Password": "admin"
}

2- Using Access Token we can access the secure method for 2 minutes. After 2 minutes Access token expires

3- WAIT FOR 2 minute so that access token expire Since Access Token has expired we can Use Refresh token to get the access token

BELOW IS THE SAMPLE post REQUEST TO https://localhost:PORT#/api/Login/refresh

PUT YOUR TOKEN AND REFRESH TOKEN FROM PREVIOUS STEP.

{
    "Username": "testdecatechalbs@gmail.com",   
    "Token":"eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTUxMiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InplZXNoYW5hc2xhbW9ubGluZUBnbWFpbC5jb20iLCJleHAiOjE2NzM4MTc4MTh9.egp0-47miOPbvUAzKkhkUhX2Rfc3c-D6E9FTy052sLgTux8wGVrm9dg6BLkkb45Z3IcwAF00A0LhgjduS3tdfg",
    "RefreshToken":"edfdb869-79e8-46c0-b1fe-89f7cb13207d"
}

Step#2 and Step#3 goes on

After 5 minutes the REFRESH Token is invalid

4- the user Must login again from postman perspective we return to Step#1 to reauthenticate and flow start again.

FAQS

What happens if a refresh token is stolen? Then the attacker, will generate n number of access tokens with stolen refresh token?

On basic level always use http cookie. Now let’s say if the jwt gets hacked, in this case what I’ve seen most sites including google does is they bind that token with specific devices and that device info can be taken from library like platform.js.

 Ideally while accepting jwt one should also check from which device it’s coming from, if it’s registered then no problem. If it’s coming from hacker, ideally, he won’t have device ID token and his device will be different from yours. Now if you have configured your backend for this verification, you’ll be good to go. Again, I highly recommend you to look for how to blacklist jwt token. In many sites they give you an option to logout from all devices. What they actually do is blacklist the jwt token so that even hacker has the token, they’ll be pretty useless.

On logout event how I can invalidate access token if it still left with validity time?

That’s a backed process. Set a TTL for access token in database and let’s say you log before access token expires, set a parameter that sets the access tokens status to true of false in database from there you can invalidate it.

don’t ever store access token in database but do make it short lived for 1 day. As for refresh token create a encrypted hash using crypto and a key (which is long and strong) that’ll help you to secure it more.

why don’t attackers just focus on stealing the Refresh token? Won’t that give them access to create new access tokens and then steal all other resources?

Because that’s hard. To steal it, you either need to break TLS, or break into the Client, or break into the Authorization Server. Breaking TLS is extremely hard and we usually assume that you cannot. Breaking into Client or Authorization Server is easier than that but at that point, you have much more control than what you can do with a Refresh Token. Also, just stealing Refresh Token is not useful. You need to steal the client’s credential as well. So, stealing Refresh Token does not make much sense. Stealing Access Token on the other hand is considerably easier. In the Basic cases, it is usually a bearer token unlike Refresh Token. So, it would be usable by the attacker.

Some of the actionable concepts and tips when working with Jwt Refresh Token.

  • Do use a strong and longer Secret for your JWT signature generation
  • Senstive data storage in JWT is big No No as any kid can use base64url online decode utlity to get the content of the payload.
  • Use only Httponly cookies to store JWT’s
  • The expiration time of the refresh token is intentionally never communicated to the client.

asp.net core 6 Oauth2 Refresh Token Code:

The branch (Refresh-Token-Grant-Flow) contains the code for this write up.

github repository code

Recommended Next Reading :

net core six article

ASP.NET CORE 6 JWT Authentication

Apollo11 in Tech
  ·   9 min read
  • >