JWT authentication with ASP.NET Core

 
 
  • Gérald Barré

In a previous post, I wrote about using cookie authentication for an ASP.NET Core website. Authenticating users with cookies is common for websites, but APIs more commonly rely on tokens. JSON Web Token (JWT) is a standard way to create and validate tokens. In this post, we'll see how to use JWT with ASP.NET Core to authenticate users. While the client can be any kind of application, I'll use a JavaScript/TypeScript front-end.

#What Is JSON Web Token (JWT)

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA.https://jwt.io/introduction

JWTs consist of 3 parts:

  • The header contains the type of the token (JWT) and the algorithm used to sign it
  • The payload contains the list of claims. Claims can be of 3 types: predefined claims (issuer, subject, expiration date, etc.), public claims (defined in the IANA JWT registry), and private claims (custom names)
  • The signature is used to verify the message wasn't changed along the way

To create the JWT, the three parts are encoded in base64 and separated by a dot. Here's the header and the payload of a JWT:

{
  "alg": "HS256",
  "typ": "JWT"
}

{
  "sub": "meziantou",
  "iss": "meziantou.net"
}

This token is encoded in this form:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtZXppYW50b3UiLCJpc3MiOiJtZXppYW50b3UubmV0In0.LR3RSg_y4xtsvc7vnKh3JXySCQtEsSNo4xkDBW9J2r4

Then, you can use it to authenticate with the Authorization header:

Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtZXppYW50b3UiLCJpc3MiOiJtZXppYW50b3UubmV0In0.LR3RSg_y4xtsvc7vnKh3JXySCQtEsSNo4xkDBW9J2r4

Note that the authority that issues the token and the one that validates it may be different. The only requirement is the ability to validate the signature, which ensures the token was generated by a trusted authority.

Now that you have a better understanding of what JSON Web Token is, let's create an ASP.NET Core application that uses JWT to authenticate users!

#Prerequisites - Generate a secret key

To create and validate a token, you need a secret key. According to this Information Security Stack Exchange post, the key should be at least 256 bits for the HmacSha256 algorithm (read this thread carefully, as the requirement may vary by algorithm). Generating a random key with .NET is straightforward. Create a console application and copy the following code:

C#
public static void Main(string[] args)
{
    var rng = System.Security.Cryptography.RandomNumberGenerator.Create();
    var bytes = new byte[256 / 8];
    rng.GetBytes(bytes);
    Console.WriteLine(Convert.ToBase64String(bytes));
}

Then, you can store the generated key in the configuration file of your ASP.NET Core website. Open the appsettings.json file and add the following section:

JSON
{
  "JwtAuthentication": {
    "SecurityKey": "ouNtF8Xds1jE55/d+iVZ99u0f2U6lQ+AHdiPFwjVW3o=",
    "ValidAudience": "https://localhost:44318/",
    "ValidIssuer": "https://localhost:44318/"
  }
}

Finally, you can register the configuration in the service collection to retrieve these settings easily:

C#
using Microsoft.IdentityModel.Tokens;

public class JwtAuthentication
{
    public string SecurityKey { get; set; }
    public string ValidIssuer { get; set; }
    public string ValidAudience { get; set; }

    public SymmetricSecurityKey SymmetricSecurityKey => new SymmetricSecurityKey(Convert.FromBase64String(SecurityKey));
    public SigningCredentials SigningCredentials => new SigningCredentials(SymmetricSecurityKey, SecurityAlgorithms.HmacSha256);
}

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.Configure<JwtAuthentication>(Configuration.GetSection("JwtAuthentication"));
    }
}

Consider using Azure Key Vault, AWS KMS, or a similar service to store this secret key securely.

#Generate a token for a user

The first step is to generate a token for a client. Before issuing a token, you must verify the user's credentials. In this sample, we'll use a dummy validation. In your application, you should consider using ASP.NET Core Identity or a similar solution to handle users and validate passwords.

C#
public class UserController : Controller
{
    private readonly IOptions<JwtAuthentication> _jwtAuthentication;

    public UserController(IOptions<JwtAuthentication> jwtAuthentication)
    {
        _jwtAuthentication = jwtAuthentication ?? throw new ArgumentNullException(nameof(jwtAuthentication));
    }

    [HttpPost]
    [AllowAnonymous]
    public IActionResult GenerateToken([FromBody]GenerateTokenModel model)
    {
        // TODO use your actual logic to validate a user
        if (model.Password != "654321")
            return BadRequest("Username or password is invalid");

        var token = new JwtSecurityToken(
            issuer: jwtAuthentication.ValidIssuer,
            audience: jwtAuthentication.ValidAudience,
            claims: new[]
            {
                // You can add more claims if you want
                new Claim(JwtRegisteredClaimNames.Sub, model.Username),
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            },
            expires: DateTime.UtcNow.AddDays(30),
            notBefore: DateTime.UtcNow,
            signingCredentials: _jwtAuthentication.Value.SigningCredentials);

        return Ok(new
        {
            token = new JwtSecurityTokenHandler().WriteToken(token)
        });
    }

    public class GenerateTokenModel
    {
        [Required]
        public string Username { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

#Authenticate the user on the server

The next step is to authenticate the user using the token. ASP.NET Core handles this natively. It reads the Authorization header, parses the token, and validates it.

First, you need to protect your action from anonymous users. You can use the Authorize with the Bearer scheme:

C#
public class SampleController : Controller
{
    [Authorize(JwtBearerDefaults.AuthenticationScheme)]
    public IActionResult Get()
    {
        return Ok();
    }
}

In the startup.cs file, add the following code to register the JWT authentication handler:

C#
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // [...]

        services.Configure<JwtAuthentication>(Configuration.GetSection("JwtAuthentication"));

        // I use PostConfigureOptions to be able to use dependency injection for the configuration
        // For simple needs, you can set the configuration directly in AddJwtBearer()
        services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerOptions>();
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer();
    }

    private class ConfigureJwtBearerOptions : IPostConfigureOptions<JwtBearerOptions>
    {
        private readonly IOptions<JwtAuthentication> _jwtAuthentication;

        public ConfigureJwtBearerOptions(IOptions<JwtAuthentication> jwtAuthentication)
        {
            _jwtAuthentication = jwtAuthentication ?? throw new System.ArgumentNullException(nameof(jwtAuthentication));
        }

        public void PostConfigure(string name, JwtBearerOptions options)
        {
            var jwtAuthentication = _jwtAuthentication.Value;

            options.ClaimsIssuer = jwtAuthentication.ValidIssuer;
            options.IncludeErrorDetails = true;
            options.RequireHttpsMetadata = true;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateActor = true,
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = jwtAuthentication.ValidIssuer,
                ValidAudience = jwtAuthentication.ValidAudience,
                IssuerSigningKey = jwtAuthentication.SymmetricSecurityKey,
                NameClaimType = ClaimTypes.NameIdentifier
            };
        }
    }
}

The website is now ready. You can generate a token and use it to authenticate users. Let's create a JavaScript client.

#JavaScript Client

First, you need to generate a token for the current user. The request is a POST that contains your username and password.

Note: the url may be different in your context

JavaScript
 const response = await fetch("/user/generatetoken", {
        method: "POST",
        body: JSON.stringify({
            username: "foo@bar",
            password: "654321"
        }),
        headers: {
            "Content-Type": "application/json",
            "Accept": "application/json"
        }
    });
const json = await response.json();
const token = json.token;
console.log(token);

Then, you can use this token. In each request, you must add the Authorization header with the token. Here's the code:

JavaScript
const response = await fetch("/sample", {
        method: "GET",
        headers: {
            "Authorization": "Bearer " + token, // Add the authentication header
            "Accept": "application/json"
        },
        credentials: "include"
    });
console.log(response.ok);

The Fetch API makes this straightforward.

#Protecting the whole website / API

This part is optional.

If you want all users to be authenticated before accessing the API, you can decorate all controllers with the [Authorize] attribute. However, this is not very convenient. Instead, you can apply the attribute globally using an authorization policy. Open the Startup.cs file and change the ConfigureService method to add the policy.

C#
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(config =>
    {
        var policy = new AuthorizationPolicyBuilder()
            .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
            .RequireAuthenticatedUser()
            .Build();
        config.Filters.Add(new AuthorizeFilter(policy));
    });
}

You can read more about authorization policy in the documentation

#Security considerations

It is strongly recommended to use HTTPS for your web API. A JWT is equivalent to the user's credentials, so you must prevent man-in-the-middle attacks. It's very easy to get a free certificate with Let's Encrypt.

RFC 7518, section 8, contains many security considerations that depend on the algorithm used. You should read it before implementing JWT to ensure you follow best practices.

JWT is signed and base64-encoded, not encrypted. This means you should not store sensitive information in it, because anyone with the token can read the data. You can check the content of a token using https://jwt.io

#Conclusion

Using JWT authentication with an ASP.NET Core application is pretty easy. In a few lines of code, you can add it to your web API. You can see the JWT authentication in action in my PasswordManager application.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?