Token Based Authentication in ASP.NET Core (Refreshed)

Token Based Authentication in ASP.NET Core (refreshed)

Working from Matt Dekrey's fabulous answer, I've created a fully working example of token-based authentication, working against ASP.NET Core (1.0.1). You can find the full code in this repository on GitHub (alternative branches for 1.0.0-rc1, beta8, beta7), but in brief, the important steps are:

Generate a key for your application

In my example, I generate a random key each time the app starts, you'll need to generate one and store it somewhere and provide it to your application. See this file for how I'm generating a random key and how you might import it from a .json file. As suggested in the comments by @kspearrin, the Data Protection API seems like an ideal candidate for managing the keys "correctly", but I've not worked out if that's possible yet. Please submit a pull request if you work it out!

Startup.cs - ConfigureServices

Here, we need to load a private key for our tokens to be signed with, which we will also use to verify tokens as they are presented. We're storing the key in a class-level variable key which we'll re-use in the Configure method below. TokenAuthOptions is a simple class which holds the signing identity, audience and issuer that we'll need in the TokenController to create our keys.

// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();

// Create the key, and a set of token options to record signing credentials
// using that key, along with the other parameters we will need in the
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
Audience = TokenAudience,
Issuer = TokenIssuer,
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};

// Save the token options into an instance so they're accessible to the
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);

// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
.RequireAuthenticatedUser().Build());
});

We've also set up an authorization policy to allow us to use [Authorize("Bearer")] on the endpoints and classes we wish to protect.

Startup.cs - Configure

Here, we need to configure the JwtBearerAuthentication:

app.UseJwtBearerAuthentication(new JwtBearerOptions {
TokenValidationParameters = new TokenValidationParameters {
IssuerSigningKey = key,
ValidAudience = tokenOptions.Audience,
ValidIssuer = tokenOptions.Issuer,

// When receiving a token, check that it is still valid.
ValidateLifetime = true,

// This defines the maximum allowable clock skew - i.e.
// provides a tolerance on the token expiry time
// when validating the lifetime. As we're creating the tokens
// locally and validating them on the same machines which
// should have synchronised time, this can be set to zero.
// Where external tokens are used, some leeway here could be
// useful.
ClockSkew = TimeSpan.FromMinutes(0)
}
});

TokenController

In the token controller, you need to have a method to generate signed keys using the key that was loaded in Startup.cs. We've registered a TokenAuthOptions instance in Startup, so we need to inject that in the constructor for TokenController:

[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenAuthOptions tokenOptions;

public TokenController(TokenAuthOptions tokenOptions)
{
this.tokenOptions = tokenOptions;
}
...

Then you'll need to generate the token in your handler for the login endpoint, in my example I'm taking a username and password and validating those using an if statement, but the key thing you need to do is create or load a claims-based identity and generate the token for that:

public class AuthRequest
{
public string username { get; set; }
public string password { get; set; }
}

/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
// Obviously, at this point you need to validate the username and password against whatever system you wish.
if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
{
DateTime? expires = DateTime.UtcNow.AddMinutes(2);
var token = GetToken(req.username, expires);
return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
}
return new { authenticated = false };
}

private string GetToken(string user, DateTime? expires)
{
var handler = new JwtSecurityTokenHandler();

// Here, you should create or look up an identity for the user which is being authenticated.
// For now, just creating a simple generic identity.
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });

var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
Issuer = tokenOptions.Issuer,
Audience = tokenOptions.Audience,
SigningCredentials = tokenOptions.SigningCredentials,
Subject = identity,
Expires = expires
});
return handler.WriteToken(securityToken);
}

And that should be it. Just add [Authorize("Bearer")] to any method or class you want to protect, and you should get an error if you attempt to access it without a token present. If you want to return a 401 instead of a 500 error, you'll need to register a custom exception handler as I have in my example here.

How to use auto generated refresh token in asp.net core 3.1?

What you're doing there is called Resource Owner Password Credential in OAuth2. It looks like pretty much a minimal implementation to me. You need to have the refresh token value or at least a reference value anchoring the token in your authorization layer. It doesn't have to be a database to store such value, you can have in-memory storage, physical file, or whatever you want. But you need to hold the value to validate later anyway.

Below is the standard communication flow, specified OAuth2 RFC 6749 documentation, which explains a scenario using refresh token.

1.5. Refresh Token

Refresh tokens are credentials used to obtain access tokens. Refresh
tokens are issued to the client by the authorization server and are
used to obtain a new access token when the current access token
becomes invalid or expires, or to obtain additional access tokens
with identical or narrower scope (access tokens may have a shorter
lifetime and fewer permissions than authorized by the resource
owner). Issuing a refresh token is optional at the discretion of the
authorization server. If the authorization server issues a refresh
token, it is included when issuing an access token (i.e., step (D) in
Figure 1).

A refresh token is a string representing the authorization granted to
the client by the resource owner. The string is usually opaque to
the client. The token denotes an identifier used to retrieve the authorization information. Unlike access tokens, refresh tokens are
intended for use only with authorization servers and are never sent
to resource servers.

 +--------+                                           +---------------+
  |        |--(A)------- Authorization Grant --------->|               |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
              Figure 2: Refreshing an Expired Access Token

The flow illustrated in Figure 2 includes the following steps:

(A) The client requests an access token by authenticating with the
authorization server and presenting an authorization grant.

(B) The authorization server authenticates the client and validates
the authorization grant, 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).

This problem is implementation specific where you can have too many options for achieving the best practice in your application level. To that end, I want to say "stick to the standard".

Token Based Authentication in ASP.NET Core

Update for .Net Core 3.1:

David Fowler (architect for the ASP .NET Core team) has put together an incredibly simple set of task applications, including a simple application demonstrating JWT. I'll be incorporating his updates and simplistic style to this post soon.

Updated for .Net Core 2:

Previous versions of this answer used RSA; it's really not necessary if your same code that is generating the tokens is also verifying the tokens. However, if you're distributing the responsibility, you probably still want to do this using an instance of Microsoft.IdentityModel.Tokens.RsaSecurityKey.

  1. Create a few constants that we'll be using later; here's what I did:

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
  2. Add this to your Startup.cs's ConfigureServices. We'll use dependency injection later to access these settings. I'm assuming that your authenticationConfiguration is a ConfigurationSection or Configuration object such that you can have a different config for debug and production. Make sure you store your key securely! It can be any string.

    var keySecret = authenticationConfiguration["JwtSigningKey"];
    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret));

    services.AddTransient(_ => new JwtSignInHandler(symmetricKey));

    services.AddAuthentication(options =>
    {
    // This causes the default authentication scheme to be JWT.
    // Without this, the Authorization header is not checked and
    // you'll get no results. However, this also means that if
    // you're already using cookies in your app, they won't be
    // checked by default.
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
    options.TokenValidationParameters.ValidateIssuerSigningKey = true;
    options.TokenValidationParameters.IssuerSigningKey = symmetricKey;
    options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
    options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
    });

    I've seen other answers change other settings, such as ClockSkew; the defaults are set such that it should work for distributed environments whose clocks aren't exactly in sync. These are the only settings you need to change.

  3. Set up Authentication. You should have this line before any middleware that requires your User info, such as app.UseMvc().

    app.UseAuthentication();

    Note that this will not cause your token to be emitted with the SignInManager or anything else. You will need to provide your own mechanism for outputting your JWT - see below.

  4. You may want to specify an AuthorizationPolicy. This will allow you to specify controllers and actions that only allow Bearer tokens as authentication using [Authorize("Bearer")].

    services.AddAuthorization(auth =>
    {
    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
    .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
    .RequireAuthenticatedUser().Build());
    });
  5. Here comes the tricky part: building the token.

    class JwtSignInHandler
    {
    public const string TokenAudience = "Myself";
    public const string TokenIssuer = "MyProject";
    private readonly SymmetricSecurityKey key;

    public JwtSignInHandler(SymmetricSecurityKey symmetricKey)
    {
    this.key = symmetricKey;
    }

    public string BuildJwt(ClaimsPrincipal principal)
    {
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
    issuer: TokenIssuer,
    audience: TokenAudience,
    claims: principal.Claims,
    expires: DateTime.Now.AddMinutes(20),
    signingCredentials: creds
    );

    return new JwtSecurityTokenHandler().WriteToken(token);
    }
    }

    Then, in your controller where you want your token, something like the following:

    [HttpPost]
    public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory)
    {
    var principal = new System.Security.Claims.ClaimsPrincipal(new[]
    {
    new System.Security.Claims.ClaimsIdentity(new[]
    {
    new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User")
    })
    });
    return tokenFactory.BuildJwt(principal);
    }

    Here, I'm assuming you already have a principal. If you are using Identity, you can use IUserClaimsPrincipalFactory<> to transform your User into a ClaimsPrincipal.

  6. To test it: Get a token, put it into the form at jwt.io. The instructions I provided above also allow you to use the secret from your config to validate the signature!

  7. If you were rendering this in a partial view on your HTML page in combination with the bearer-only authentication in .Net 4.5, you can now use a ViewComponent to do the same. It's mostly the same as the Controller Action code above.

Set authentication token expiry in ASP.NET Core 6.0 MVC

According to the docs, you'd add something like the following in:

builder.Services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
options.LoginPath = "/Identity/Account/Login";
options.AccessDeniedPath = "/Identity/Account/AccessDenied";
options.SlidingExpiration = true;
});

From the docs about ExpireTimeSpan:

Controls how much time the authentication ticket stored in the cookie will remain valid from the point it is created
The expiration information is stored in the protected cookie ticket. Because of that an expired cookie will be ignored
even if it is passed to the server after the browser should have purged it.

This is separate from the value of Microsoft.AspNetCore.Http.CookieOptions.Expires, which specifies how long the browser will keep the cookie.

In other words, while it doesn't set the cookie itself with an expiration value, the ExpireTimeSpan and SlidingExpiration settings will cause the application to provide a new value in responses around every 5 minutes and update within the protected cookie value the new expiration.



Related Topics



Leave a reply



Submit