Click here to Skip to main content
15,884,472 members
Articles / Security

JWT Security Part 1 - Create Token

Rate me:
Please Sign up or sign in to vote.
4.96/5 (11 votes)
4 Sep 2017CPOL8 min read 48.9K   2.7K   44   2
Learn how to create JWT and use with WebApi, REST and MVC all build with .NET Core

Introduction

JWT (JSON Web Token) becomes more and more popular as a standard for securing web sites, and REST services. I discuss how you can implement JWT security for both a REST service and a MVC web application all build with .NET Core. I divided the JWT security in 3 blogs:

  1. Create JWT
  2. Secure REST service with JWT
  3. Secure web application with JWT

This is the first of the three blogs and I start with a small JWT explanation.

JWT Primer

JWT (JSON Web Tokens) is open, security protocol for securely exchanging claims between 2 parties. A server generates or issues a token and is signed by a secret key. The client also knows the secret key and the key and can verify if the token is genuine. The token contains claims for authentication and authorization. Authentication is simply the verification if someone is really who he claims to be. Authorization is when a user is granted to access a resource or execute a certain task. For example, user A can view payments and user B can execute payments. JWT are self contained. Because JWT is a protocol and not a framework, it works across different languages like .NET, Java Python and many more. The JWT is usually transmitted by adding the JWT to the header of the request, but can also be used as a parameter in an URL. This transmission makes the JWT stateless.

JWT Structure

JWT has three parts:

  1. Header
  2. Payload
  3. Signature

The parts are separated with a dot.

aaaa.bbbb.cccc

Header

The header and the payload has one or more key value pairs. The header contains the token type ('typ') and the hashing algorithm ('alg') SHA256.

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

The Header and the Payload parts are base64 encoded, this makes the Header part:

JavaScript
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

The payload part is the most interesting section because it contains all the claims. There are three claims types Registered, Public and Private claims.

Registered Claims

The registered claims are part of the JWT standard and have the same purpose on all implementations. In order to keep the JWT size small, the key is always 3 characters long. Here's the short list:

  • iss Issuer Identifies who issued the JWT.
  • sub Subject Identifies the principal (read user) of the JWT.
  • aud Audience Identifies recipients the JWT is intended for.
  • exp Expiration Sets the expiration date and when expired, the JWT must be refused.
  • nbf Not before. Sets the date before the JWT may not be used.
  • iat Issued at. Sets the date when the JWT was created.
  • jti Unique identifier for the JWT. Use for a one time token and prevent token replay.

All registered claims dates are in the Unix Epoch date format and describe the seconds after UTC time 1 January 1970.

Public Claims

Public claims contain more general information, for example 'name'. Public names are also registered to prevent collision with other claims.

Private Claims

A private claim is agreed between issuer and audience. Always check if a private claim does not collide with existing claims. The claim 'role' is private claim example we will use later on.

Payload Example

JavaScript
{
  "iss": "JwtServer",
  "sub": "hrmanager",
  "email": "hrmanager@xyz.com",
  "jti": "e971bd9c-7655-41d5-9c49-fabc054dc466",
  "iat": 1503922683,
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
    "Employee",
    "HR-Worker",
    "HR-Manager"
  ],
  "Department": [
    "HR",
    "HR"
  ],
  "nbf": 1503922683,
  "exp": 1503924483
}

will result in:

eyJpc3MiOiJKd3RTZXJ2ZXIiLCJzdWIiOiJocm1hbmFnZXIiLCJlbWFpbCI6ImhybWFuYWdlck
B4eXouY29tIiwianRpIjoiZTk3MWJkOWMtNzY1NS00MWQ1LTljNDktZmFiYzA1NGRjNDY2Iiwi
aWF0IjoxNTAzOTIyNjgzLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMD
YvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiRW1wbG95ZWUiLCJIUi1Xb3JrZXIiLCJIUi1NYW5h
Z2VyIl0sIkRlcGFydG1lbnQiOlsiSFIiLCJIUiJdLCJuYmYiOjE1MDM5MjI2ODMsImV4cCI6MTUwMzkyNDQ4M30

Signature

So far, there was nothing secure about a JWT. All data is base64 encoded and although not human readable, it's easy to decode it into a readable text. This is where the signature comes in. With the signature, we can verify if the JWT is genuine and has not been tampered. The signature is calculated from the Header, the Payload and a secret key.

C#
var headerAndPayload = base64Encode(header) + "." + base64Encode(payload);

var secretkey = "@everone:KeepitSecret!";

signature = HMACSHA256(headerAndPayload, secretkey);

The secret key is symmetric and is known to issuer and client. Needless to say, be careful where you store the secret key!

Put It All Together

The screen dump below is constructed with help from https://jwt.io/ where you can test and debug JWT claims. The left pane holds the JWT and the other pane shows the extracted Header and Payload. If you add the secret key, the page also verifies the signature.

General JWT Security Overview

Fig.1 Solution overview

The solution overview shows three separate servers, the Web application, the RESTful service and the JWT issuer server. They could be hosted in one server and in one project, but I made three items for it. In this way, it's much more clear how each server is configured. Because JWT is self contained, there no need for some kind of connection between the JWT issuer and the REST service to validate the JWT claim.

General JWT Flow

The basic JWT flow is quite simple:

  • The user enters the login credentials on the web application.
  • The web application sends the login credentials to JWT issuer and asks for a JWT claim.
  • JWT issuer validates login credentials with user database.
  • JWT issuers creates JWT based on claims and roles from user database and adds the 'exp' (Expires) claim for limited lifetime (30 minutes).
  • JWT issuer sends the JWT to web application.
  • Web application receives JWT and stores it in an authentication cookie.
  • Web application verifies JWT and parses payload for authentication and authorization.
  • Web application adds JWT to REST service calls.

Pros and Cons

Pros

  • Comparatively simple. Security is never easy, whatever you choose. JWT is a smart design and combined with the .NET libraries who do the "hard" work makes JWT relative easy to implement.
  • REST service is truly stateless as it supposed to be. In most cases, security adds some kind of session management for authentication.
  • Stateless makes scalable. If you need more servers to handle the workload, there is no need to shares sessions among all the servers. This makes scaling easier and less error prone.
  • Useable across different services. JWT are self contained and the service can authorize without having access to the user database.
  • JWT provides neat options for temporary authorization elevation. Claims can be added or removed during an user session. For example, you can add a claim to a user that he successfully passed a two way authentication for executing a payment. The claim can be removed when the payment is successfully executed. In this manner, there's no need to create a special way for tracking the user status.

Cons

  • JWT has no build in features for sliding expirations, although you can build it yourself.
  • The Secret key is very important. If the secret key is somehow stolen or leaked, the security is heavily compromised.

Create JWT Issuer Project

The main task is to deliver JWT claims based on user credentials. The project is a standard MVC application with Individual User Accounts as Authentication.

The Individual User Accounts Authentication is used to secure the website and having easy access to users and their roles and claims. I added the package Microsoft.AspNetCore.Authentication.JwtBearer for the actual JWT creation. Because JWT is not used to secure this web site caller, there is no need to register JwtBearer services during start up. Only the JWT parameters are configured during start up.

C#
{
...
  "JwtIssuerSettings": {
    "Issuer": "JwtServer",
    "ValidFor": 30, // minutes
    "SecretKey": "@everone:KeepitSecret!"
  },
...

The DI (Dependency Injection) pattern is applied for the configuration. The class JwtIssuerSettings maps to the config section JwtIssuerSettings in appsettings.json and the class JwtIssuerFactory creates an instance of IJwtIssuerOptions interface.

C#
public void ConfigureServices(IServiceCollection services)
   {
     ...
     // setup JWT parameters
     services.Configure<JwtIssuerSettings>
              (Configuration.GetSection(nameof(JwtIssuerSettings)));
     services.AddTransient<IJwtIssuerOptions, JwtIssuerFactory>();
     ...

They are added to the service collection and are now available as parameters in controller constructor.

Create JWT Claim

The function Login on controller JwtIssuerController creates the JWT claim. The process is pretty straight forward:

  • Find the user.
  • Check password.
  • Create Issuer, Subject, Email, Unique Id and IssuedAt claims.
  • Collect user roles (claims) from storage.
  • Create JWT based on configuration parameters and secret key.
  • Send token to caller.
C#
namespace Security.Controllers
{
  [AllowAnonymous]
  [Route("api/security")]
  public class JwtIssuerController : Controller
  {
    private readonly IJwtIssuerOptions JwtOptions;
    private readonly UserManager<ApplicationUser> UserManager;
    private readonly SignInManager<ApplicationUser> SignInManager;
    private readonly RoleManager<IdentityRole> RoleManager;

    public JwtIssuerController(IJwtIssuerOptions jwtOptions,
      UserManager<ApplicationUser> userManager,
      SignInManager<ApplicationUser> signInManager,
      RoleManager<IdentityRole> roleManager)
    {
      JwtOptions = jwtOptions;
      UserManager = userManager;
      SignInManager = signInManager;
      RoleManager = roleManager;
    }

    [HttpPost(nameof(Login))]
    public async Task<IActionResult> Login([FromBody] LoginResource resource)
    {
      if (resource == null)
        return BadRequest("Login resource must be asssigned");

      var user = await UserManager.FindByEmailAsync(resource.Email);

      if (user == null || (!(await SignInManager.PasswordSignInAsync
                          (user, resource.Password, false, false)).Succeeded))
        return BadRequest("Invalid credentials");

      String result = await CreateJwtTokenAsync(user);

      // Token is created, we can sign out
      await SignInManager.SignOutAsync();

      return Ok(result);
    }

    /// <summary>
    /// Fetch user roles and claims from storage
    /// </summary>
    /// <param name="user">application user</param>
    /// <returns>JWT token</returns>
    private async Task<String> CreateJwtTokenAsync(ApplicationUser user)
    {
      // Create JWT claims
      var claims = new List<Claim>(new[]
      {
        // Issuer
        new Claim(JwtRegisteredClaimNames.Iss, JwtOptions.Issuer),   

        // UserName
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),       

        // Email is unique
        new Claim(JwtRegisteredClaimNames.Email, user.Email),        

        // Unique Id for all Jwt tokes
        new Claim(JwtRegisteredClaimNames.Jti, await JwtOptions.JtiGenerator()), 

        // Issued at
        new Claim(JwtRegisteredClaimNames.Iat, 
        JwtOptions.IssuedAt.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64) 
      });

      // Add userclaims from storage
      claims.AddRange(await UserManager.GetClaimsAsync(user));

      // Add user role, they are converted to claims
      var roleNames = await UserManager.GetRolesAsync(user);
      foreach (var roleName in roleNames)
      {
        // Find IdentityRole by name
        var role = await RoleManager.FindByNameAsync(roleName);
        if (role != null)
        {
          // Convert Identity to claim and add 
          var roleClaim = new Claim(ClaimTypes.Role, role.Name, 
                                    ClaimValueTypes.String, JwtOptions.Issuer);
          claims.Add(roleClaim);

          // Add claims belonging to the role
          var roleClaims = await RoleManager.GetClaimsAsync(role);
          claims.AddRange(roleClaims);
        }
      }

      // Prepare Jwt Token
      var jwt = new JwtSecurityToken(
          issuer: JwtOptions.Issuer,
          audience: JwtOptions.Audience,
          claims: claims,
          notBefore: JwtOptions.NotBefore,
          expires: JwtOptions.Expires,
          signingCredentials: JwtOptions.SigningCredentials);

      // Serialize token
      var result = new JwtSecurityTokenHandler().WriteToken(jwt);

      return result;
    }

Test Data

During startup, an in-memory database is created. It contains three users and three roles and mimics a Human Resource department.

Roles:

  • Employee - This can be any company member
  • HR-Worker - Every HR department member
  • HR-Manager - Sure it's the HR-boss

Users:

  • employee@xyz.com
  • hrworker@xyz.com
  • hrmanager@xyz.com

Namespace Microsoft.AspNetCore.Identity contains RoleManager<IdentityRole> and is ready to use without explicit configuration. You don't read much about it in examples or documentation. It's a bit of a missed chance because the class is really useful for managing the roles in the system.

C#
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
  public void Configure(IApplicationBuilder app,
         IHostingEnvironment env, ILoggerFactory loggerFactory)
  {
   ...
    // Fill empty inmemory database during development
    if (env.IsDevelopment())
      app.InitDb();
   ...
C#
  public static class InitDbExtensions
  {
    public static IApplicationBuilder InitDb(this IApplicationBuilder app)
    {
      var roleManager = 
          app.ApplicationServices.GetService<RoleManager<IdentityRole>>();
      var userManager = 
          app.ApplicationServices.GetService<UserManager<ApplicationUser>>();

      if (userManager.Users.Count() == 0)
      {
        Task.Run(() => InitRoles(roleManager)).Wait();
        Task.Run(() => InitUsers(userManager)).Wait();
      }

      return app;
    }

    private static async Task InitRoles(RoleManager<IdentityRole> roleManager)
    {
      var role = new IdentityRole("Employee");
      await roleManager.CreateAsync(role);

      role = new IdentityRole("HR-Worker");
      await roleManager.CreateAsync(role);
      await roleManager.AddClaimAsync(role, new Claim("Department", "HR"));

      role = new IdentityRole("HR-Manager");
      await roleManager.CreateAsync(role);
      await roleManager.AddClaimAsync(role, new Claim("Department", "HR"));
    }

    private static async Task InitUsers(UserManager<ApplicationUser> userManager)
    {
      var user = new ApplicationUser() { UserName = "employee", 
                                         Email = "employee@xyz.com" };
      await userManager.CreateAsync(user, "password");
      await userManager.AddToRoleAsync(user, "Employee");

      user = new ApplicationUser() 
             { UserName = "hrworker", Email = "hrworker@xyz.com" };
      await userManager.CreateAsync(user, "password");
      await userManager.AddToRoleAsync(user, "Employee");
      await userManager.AddToRoleAsync(user, "HR-Worker");

      user = new ApplicationUser() 
             { UserName = "hrmanager", Email = "hrmanager@xyz.com" };
      await userManager.CreateAsync(user, "password");
      await userManager.AddToRoleAsync(user, "Employee");
      await userManager.AddToRoleAsync(user, "HR-Worker");
      await userManager.AddToRoleAsync(user, "HR-Manager");
    }
  }
}

Testing JWT Claim

I added Swagger by adding package Swashbuckle.AspNetCore for testing. You can read more here about how to configure swagger. In short, it comes to this:

C#
public void ConfigureServices(IServiceCollection services)
    {
     ...
      // Register the Swagger generator, defining one or more Swagger documents
      services.AddSwaggerGen(c =>
      {
        c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
        {
          Description = "Authorization format : Bearer {token}",
          Name = "Authorization",
          In = "header",
          Type = "apiKey"
        });

        c.SwaggerDoc("v1", new Info { Title = "Security Api", Version = "v1" });
      });
     ...
C#
public void Configure(IApplicationBuilder app,
                      IHostingEnvironment env, ILoggerFactory loggerFactory)
   {
   ...
   // Enable middleware to serve generated Swagger as a JSON endpoint.
     app.UseSwagger();

     // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.),
     // specifying the Swagger JSON endpoint.
     app.UseSwaggerUI(c =>
     {
       c.SwaggerEndpoint("/swagger/v1/swagger.json", "Security Api v1");
     });
   ...

Swagger can now be tested at http://localhost:49842/swagger/.

We can test the response at https://jwt.io/.

and all looks fine and we can start securing the REST service.

Visual Studio Startup Projects

Sometimes, the Visual Studio startup Project is lost and prevents running the application. Right click on the solution and choose 'Set Startup Projects...'.

And repair the startup setting:

Conclusion

This blog demonstrates how you can setup a JWT (JSON Web Token) issuer. Stateless, self contained, scalable and other features makes JWT a smart design. With help from packages integrates JWT well with .NET Core and takes little effort to setup.

Next Post: JWT Security Part 2 - Secure REST Service

Further Reading

Versions

  • 1.0 31st August, 2017: Initial release
  • 1.1 5th September, 2017: Source code upgraded for .NET Core 2.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
Netherlands Netherlands
I graduated as Bachelor of Mechanical Engineering. Soon I moved from mechanical to software engineering. With more than 20 years of experience in software design, development, and architecture I love building software that users enjoy en suit their needs.

Comments and Discussions

 
QuestionAwesome explanation Pin
satalaj17-May-21 15:46
satalaj17-May-21 15:46 
QuestionImages? Pin
Dino Bavaro9-Dec-19 12:31
Dino Bavaro9-Dec-19 12:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.