Translation of an article in English, mainly to see for himself - how to refresh the token in ASP.NET Core Web Api in

Original Address: https: //www.blinkingcaret.com/2018/05/30/refresh-tokens-in-asp-net-core-web-api/

First state, my English dishes too, should use translation software to see each facing see, too painful, so just translation of this blog, good English see for yourself, the following text

 

When using the access token to protect web api, the first thought is how to do when the token expires?

Did you ask the user for credentials again? This is not a good option.

This blog article is about using refresh tokens to solve this problem. Especially in the use ASP.NET Core Web Apis in JWT token.

 

First of all, it really is a big thing right? Why not set an expiration date longer it directly in the access token? For example, a month or even a year?

Because if we do, someone managed to get that token, they can use a month, or a year. Even if you changed the password.

This is because, if the token's signature is valid, the server will trust it, and the only way to change it is not valid for the key signature, which will result in everyone else's token is invalid.

That would not get elected. This leads to the idea of using refresh tokens.

 

Then refresh the token is how to work it?

Imagine, when you get an access token, you will get another token one-time use: refresh token. Application store refresh tokens, then leave it.

Whenever an application sends a request to the server, it will send the access token (authorization here: bearer token), so that the server knows who you are. One day the token expires, the server will notify you in some way.

When this happens, your application will be sent expired token and refresh token and get a new token and refresh token. So repeat replaced.

If there is a suspicious happens, refresh token can be revoked, which means that when an application tries to use it to get a new access token, the request is rejected, the user will have to enter credentials to log in again.

 

In order to clarify the position of the last point, assuming the application when creating the refresh token storage request (for example, Dublin, Ireland). If the user can access the information, if there are some logged-on user does not recognize the place, the user can revoke the token refresh, so that when the access token expires who use the application it will not continue to use. This is why it might be a good idea to be short-lived (I have the access token. Valid for a few minutes).

To use the refresh token, we need to be able to do:

  • Create an access token (we will use here JWT)
  • Create, save, and retrieve the token Undo (server)
  • JWT will expire tokens and refresh tokens replaced with a new token and refresh token JWT (JWT ie refresh token)
  • Use ASP. NET authentication middleware for use JWT token to authenticate users
  • There is a way to notify the application access token has expired (optional)
  • When the token expires, allowing clients to transparently obtain a new token

If you need to separate information on these topics, please continue. If you want to see all of these work together, you can find a demo project (here Demo Project here Wallpaper ).

 

JWT create an access token

If you want a more detailed description of how to use the JWT in ASP.NET Core, I recommend checking out Secure A Web Api in ASP.NET Core . This is a summary. .

First, we need to add  System.IdentityModel.Tokens.Jwt package:

$ dotnet add package System.IdentityModel.Tokens.Jwt

Create a new token JWT:

private string GenerateToken(IEnumerable<Claim> claims)
{
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the server key used to sign the JWT token is here, use more than 16 chars"));

    var jwt = new JwtSecurityToken(issuer: "Blinkingcaret",
        audience: "Everyone",
        claims: claims, //the user's claims, for example new Claim[] { new Claim(ClaimTypes.Name, "The username"), //... 
        notBefore: DateTime.UtcNow,
        expires: DateTime.UtcNow.AddMinutes(5),
        signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
    );    

    return new JwtSecurityTokenHandler().WriteToken(jwt); //the method is called WriteToken but returns a string
}

  

Here we create a new jwt token, its expiration date is five minutes, using HmacSha256 sign.

Create, save, and retrieve the token Undo

Refresh tag must be unique, it is impossible (or difficult) to guess them.

A simple GUID appears to satisfy this condition. Unfortunately, the guid generation process is not random. This means that, given a few guid, you can easily guess the next guid.

Thankfully, there is a secure random number generator in ASP. NET Core, we can use it to generate a unique string, even if a few of them are given, it is difficult to predict the next look like:

using System.Security.Cryptography;

//...

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

  

Here we generate a 32-byte random number, and converts it to base64, so that we can use it as a string. No guidance on the length, except that it should result in a unique and difficult to guess than a token. I chose 32, but 16 is also available.

We need to generate refresh token when you first token is generated JWT and "Refresh" expired token.

Save each time a new refresh token generation, we should be in a link to a user issues an access token way.

The easiest way is for the user table refresh mark adding an extra column. As a result, only allows the user to log in (each user only one valid refresh token) in one place.

 

Alternatively, you can maintain multiple refresh tokens for each user, and save the location, time, etc. from initiating their requests in order to provide activity reports for users.

In addition, let the refresh token expires might be a good idea, for example, after a few days (expiration date must be saved together with the refresh token) in.

One thing must not be forgotten that refresh tag to remove it when used in the refresh operation, so that it can not be used more than once.

 

JWT will expire and refresh tokens replaced with a new token and refresh token JWT (JWT ie refresh token)

From an expired access token to obtain a new access token, we need to be able to access the statement in the token, even if the token has expired.

When you use ASP.NET Core middleware for user authentication when using JWT authenticate, it will return 401 in response to the expired token.

We need to allow anonymous users to create a controller action, and accept JWT and refresh token.

 

We need to allow anonymous users to create a controller action, and accept JWT and refresh token.

In the controller action, we need to manually verify an expired access token (you can choose to ignore the token lifetime), and extract all the information about the user contained therein.

Then, we can use the user to retrieve information stored in the refresh token. Then, we can refresh the stored token is compared with the token sent in the request.

If all goes well, we will create a new JWT and refresh tokens, save the new refresh token, discard the old, and send the new JWT and refresh token to the client.

Here is how to retrieve ClaimsPrincipal form from the expired token JWT user information:

private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
        ValidateIssuer = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the server key used to sign the JWT token is here, use more than 16 chars")),
        ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
    };

    var tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken securityToken;
    var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
    var jwtSecurityToken = securityToken as JwtSecurityToken;
    if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
        throw new SecurityTokenException("Invalid token");

    return principal;
}

  

The above code fragment is noteworthy that portion, we used ValidateLifeTime = false in TokenValidationParameters, thus expired token is considered to be effective. In addition, we are checking algorithm used to sign the token meets our expectations (in this case HmacSha256).

The reason for this is that, in theory, you can create JWT token and the signature algorithm is set to "none" ( the SET at The Signing algorithm to "none" .). JWT token is valid (even if unsigned). Use a valid refresh token this way, it can be a false token replaced with a real JWT token.

Now we just need to move the controller (it should be a post, because it has side effects, and long token query string parameter):

[HttpPost]
public IActionResult Refresh(string token, string refreshToken)
{                   
    var principal = GetPrincipalFromExpiredToken(token);
    var username = principal.Identity.Name;
    var savedRefreshToken = GetRefreshToken(username); //retrieve the refresh token from a data store
    if (savedRefreshToken != refreshToken)
        throw new SecurityTokenException("Invalid refresh token");

    var newJwtToken = GenerateToken(principal.Claims);
    var newRefreshToken = GenerateRefreshToken();
    DeleteRefreshToken(username, refreshToken);
    SaveRefreshToken(username, newRefreshToken);

    return new ObjectResult(new {
        token = newJwtToken,
        refreshToken = newRefreshToken
    });
}

  

This fragment includes the above assumptions. I omitted to retrieve, save and delete, but also assume that each user has only a token refresh, this is the simplest scenario.

asp.net core authentication middleware uses jwt token to authenticate users

We need to configure asp.net core middleware pipeline, so that if the request of the head with a valid  Authorization: Bearer JWT_TOKEN authorization, the user is "logged" in ( "Signed in" )

 

If you want more in-depth discussion of how to set jwt especially in asp.net core, check Secure A Web Api in ASP.NET Core ..

After ASP.NET Core2.0 version, we add to the pipeline in an authentication middleware, and configure the configureservices startup.cs in:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    //...

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = "bearer";
        options.DefaultChallengeScheme = "bearer";
    }).AddJwtBearer("bearer", options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false,
            ValidateIssuer = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("the server key used to sign the JWT token is here, use more than 16 chars")),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.Zero //the default for this setting is 5 minutes
        };
        options.Events = new JwtBearerEvents
        {
            OnAuthenticationFailed = context =>
            {
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });
}

  

Note that the above code fragment is onAuthenticationFailed handling of events. When the request token has expired, the response is added to the head will expire the token. Clients can use this information to decide to use a refresh token. However, we can allow the client to attempt to use the refresh token when it receives a 401 response. post request we will rely on the head of the expired token recovery blog (original translation: We will rely on the token expires head in response to the rest of the article in this blog).

Now we just need to add to the pipelines authentication middleware:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAuthentication();
    //...

 Client

The goal here is to build a api client, when the client can realize that token expires, and to take appropriate action to obtain a new token and transparently performs all these operations.

When an access token expires failed because the request should be used to access and refresh tokens will send a new request to refresh the endpoint. After the completion of the request and the client to obtain a new token, the original request should be repeated.

This will be achieved depends on the type of client you are using. Here we will describe a possible javascript client. We will depend on the response to the request, the request token has expired a "token expired" named head. We will use fetch to execute a request for a web api's.

async function fetchWithCredentials(url, options) {
    var jwtToken = getJwtToken();
    options = options || {};
    options.headers = options.headers || {};
    options.headers['Authorization'] = 'Bearer ' + jwtToken;
    var response = await fetch(url, options);
    if (response.ok) { //all is good, return the response
        return response;
    }

    if (response.status === 401 && response.headers.has('Token-Expired')) {
        var refreshToken = getRefreshToken();

        var refreshResponse = await refresh(jwtToken, refreshToken);
        if (!refreshResponse.ok) {
            return response; //failed to refresh so return original 401 response
        }
        var jsonRefreshResponse = await refreshResponse.json(); //read the json with the new tokens

        saveJwtToken(jsonRefreshResponse.token);
        saveRefreshToken(jsonRefreshResponse.refreshToken);
        return await fetchWithCredentials(url, options); //repeat the original request
    } else { //status is not 401 and/or there's no Token-Expired header
        return response; //return the original 401 response
    }
}

  

 

There getjwttoken, getrefreshtoken, savejwttoken and saverefreshttoken in the above code fragment. In the browser, which uses the locally stored browser to save and retrieve the token,

E.g:

function getJwtToken() {
    return localStorage.getItem('token');
}

function getRefreshToken() {
    return localStorage.getItem('refreshToken');
}

function saveJwtToken(token) {
    localStorage.setItem('token', token);
}

function saveRefreshToken(refreshToken) {
    localStorage.setItem('refreshToken', refreshToken);
}

There refresh function. This API function performs endpoint POST request to refresh the token, e.g., if the endpoint is located / token / refresh:

async function refresh(jwtToken, refreshToken) {
    return fetch('token/refresh', {
        method: 'POST',
        body: `token=${encodeURIComponent(jwtToken)}&refreshToken=${encodeURIComponent(getRefreshToken())}`,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    });
}

  If you try to use this client in chrome developer tools console, it looks like this:

 

One thing to note here is 401 console is displayed in red. When the request fails due to token expires This happens.

If you want to avoid seeing the "wrong" (error in quotation marks because it is a valid status code is appropriate in this case), you can access token expiration date in javascript, and prior to maturity refresh it. 

jwt token has three parts, "." separated by one. The second part contains the user's declaration, a statement which has called exp, wherein the token comprises expiration time unix timestamp. This is how the acquisition method with javascript date object jwt token expiration date of:

var claims = JSON.parse(atob(token.split('.')[1]));
var expirationDate = new Date(claims.exp*1000); //unix timestamp is in seconds, javascript in milliseconds

 

Check the expiration date feeling very complex, so I do not recommend it (while processing zones could be a problem). I decided to mention this is because it is a very interesting thing, which makes us very clearly that you put in jwt token thing is not secret, it can not be tampered with unless invalidate the token.

 

I hope you find this article interesting. If this is the case, put a line in the comments.

Guess you like

Origin www.cnblogs.com/bamboo-zhang/p/11699772.html