JWT authentication with ASP.NET Core

In a previous post, I've written about using cookie authentication for an ASP.NET Core web site. Authenticating user by using a cookie is common for a web site. However, for an API, it's more common to use a token for authentication. Json Web Token (JWT) is a way to create and validate a token. In this post, we'll see how to use JWT with ASP.NET Core to authenticate the users. While the client can be any kind of application, I'll use a front-end application with JavaScript/TypeScript.

What's 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 by using the Authorization header:

Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtZXppYW50b3UiLCJpc3MiOiJtZXppYW50b3UubmV0In0.LR3RSg_y4xtsvc7vnKh3JXySCQtEsSNo4xkDBW9J2r4

Note that the authority that deliver the token and the one that validate the token may be differents. The only requirement is to be able to validate the signature, so you are sure the token is generated by the trusted authority.

Now, you have a better understanding of what is Json Web Token. 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 must use a secret key. From the following Information Security Stack Exchange post, the length of the key should be 256 bits for the HmacSha256 algorithm (read carefully this thread because it may change depending on the algorithm). Using .NET it's very easy to generate a random key. Create a console application and copy the following code:

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 web site. Open the appsettings.json file and add the following section:

{
  "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:

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"));
    }
}

By the way, it can be a good idea to look at Azure Key Vault, AWS KMS or their competitors to store this secret key.

Generate a token for a user

The first part is to generate a token for a client. Before issuing a token, you must validate the user is valid. 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 password.

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 part is to authenticate the user using the token. ASP.NET Core already contains everything for that. It will get the value of the Authorization header and parse its value. Then, it will check the token is valid.

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

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:

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 this token to authenticate the users. It's time to create a client. 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

 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:

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

It's so easy using the fetch API 😃

Protecting the whole website / api

This part is optional.

If you want the users to be authenticated to access the API, you can decorate all the controllers with the [Authorize] attribute. But, 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.

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 documenation

Security considerations

It's strongly recommended to use HTTPS for your web api. The JWT token is like the username/password of the user. So, you must prevent man-in-the-middle attack. It's very easy to get a free certificate with Let's Encrypt.

The RFC7518 section 8 contains many security considerations dependant on the used algorithms. You should read them before implementing JWT to be sure to follow the best practices.

JWT is signed and encoded only, not encrypted. This means you should not store sensitive information into 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.

Hide your email address on GitHub

When you create a commit on git, the username and email is associated with the commit. So when someone clones the repository, they can see the list of commits and the associated user. Thus, you can get a list of email address. People often use their private email address, so you can be spam. For instance, here's the output of git log on the repo corefx. You can see some email address

Configure git

GitHub provides a noreply address for every user. For instance, my email is meziantou@users.noreply.github.com. You can find it on the settings page: https://github.com/settings/emails.

Then, you can change your git configuration:

git config --global user.email meziantou@users.noreply.github.com
git config --global user.name meziantou

If you want to configure only a single repo,

cd "path to the git repository"
git config user.email meziantou@users.noreply.github.com
git config user.name meziantou

Rewrite history

Then, you want to replace your email in the previous commits. git provides a functionality to rewrite the history of the repository. If you are on Windows, I advise you to use bash as the escape characters are not the same on both system. If you have not yet configured bash, read this documentation.

git filter-branch --commit-filter '
    if [ "$GIT_COMMITTER_EMAIL" = "<Your old email address>" ];
    then
        GIT_COMMITTER_NAME="<Your name>";
        GIT_AUTHOR_NAME="<Your name>";
        GIT_COMMITTER_EMAIL="<Your noreply email address>";
        GIT_AUTHOR_EMAIL="<Your noreply email address>";
        git commit-tree "$@";
    else
         git commit-tree "$@";
    fi' HEAD

Then, you have to push your changes to the remote repository. You must use --force to overwrite the remote repository.

git push --force

Change GitHub settings to block commits containing your email address

Finally, you can configure GitHub to block commits that contains your actual email address. Go into the settings / email section and check the box Block command line pushes that expose my email

Cancelling Console.Read

Console.ReadLine and other read methods block the current thread until the user actually write something in the console. Sometimes you want to cancel the read operation when a condition is met. For instance, you can cancel the read operation after a few seconds, or after receiving an event.

Windows provides a function to cancel an IO request: CancelIoEx. To use it, you must get the handle of the IO stream. In our case, it's the handle of the console input stream. You can get this handle by using GetStdHandle and the STD_INPUT_HANDLE constant. By combining the two functions, you can easily cancel a read request. Once the request is canceled, Console.Read throws an exception that you should handle. Depending on the read method used, you must catch the InvalidOperationException or the OperationCanceledException.

The following code start a timer of 10 seconds and call Console.Read. If the user doesn't write anything, the timer cancel the IO request:

class Program
{
    const int STD_INPUT_HANDLE = -10;

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool CancelIoEx(IntPtr handle, IntPtr lpOverlapped);

    static void Main(string[] args)
    {
        // Start the timeout
        var read = false;
        Task.Delay(10000).ContinueWith(_ =>
        {
            if (!read)
            {
                // Timeout => cancel the console read
                var handle = GetStdHandle(STD_INPUT_HANDLE);
                CancelIoEx(handle, IntPtr.Zero);
            }
        });

        try
        {
            // Start reading from the console
            Console.WriteLine("Do you want to continue [Y/n] (10 seconds remaining):");
            var key = Console.ReadKey();
            read = true;
            Console.WriteLine("Key read");
        }
        // Handle the exception when the operation is canceled
        catch (InvalidOperationException)
        {
            Console.WriteLine("Operation canceled");
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Operation canceled");
        }
    }
}

Documentation:

Detecting console closing in .NET

In a console application I developped, I use FiddlerCore to intercept the http requests of an application. FiddlerCore modifies the network settings of Windows. So, before closing the application, you must restore the original network settings. In .NET you can register the Ctrl+C and Ctrl+Break signals with Console.CancelKeyPress. This is the classic way of closing a console application. You can also close the console itself. which will of course kill your application. However, the CancelKeyPress event doesn't handle the closing of the console. Thus, the cleanup code won't run in this case.

Windows lets you register to the console events (Ctrl+C, Ctrl+Break and also the close event) using SetConsoleCtrlHandler. At the beginning of your code, you can call SetConsoleCtrlHandler, so you will be able to cleanup your changes. Here's the complete code:

class Program
{
    // https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms686016.aspx
    [DllImport("Kernel32")]
    private static extern bool SetConsoleCtrlHandler(SetConsoleCtrlEventHandler handler, bool add);

    // https://msdn.microsoft.com/fr-fr/library/windows/desktop/ms683242.aspx
    private delegate bool SetConsoleCtrlEventHandler(CtrlType sig);

    private enum CtrlType
    {
        CTRL_C_EVENT = 0,
        CTRL_BREAK_EVENT = 1,
        CTRL_CLOSE_EVENT = 2,
        CTRL_LOGOFF_EVENT = 5,
        CTRL_SHUTDOWN_EVENT = 6
    }

    static void Main(string[] args)
    {
        // Register the handler
        SetConsoleCtrlHandler(Handler, true);

        // Wait for the event
        while (true)
        {
            Thread.Sleep(50);
        }
    }

    private static bool Handler(CtrlType signal)
    {
        switch (signal)
        {
            case CtrlType.CTRL_BREAK_EVENT:
            case CtrlType.CTRL_C_EVENT:
            case CtrlType.CTRL_LOGOFF_EVENT:
            case CtrlType.CTRL_SHUTDOWN_EVENT:
            case CtrlType.CTRL_CLOSE_EVENT:
                Console.WriteLine("Closing");
                // TODO Cleanup resources
                Environment.Exit(0);
                return false;

            default:
                return false;
        }
    }
}

Generate an HTML form from an object in TypeScript

In the previous post about TypeScript decorators, I used decorators to quickly add validation rules. In this post, we'll use another features of the decorators. TypeScript can automatically add the type of the property to the metadata. Let's see how we can use this information and other custom attributes to automatically generate a form from a class.

The idea is to be able to use the following code:

class Person {
    @editable()
    @displayName("First Name")
    public firstName: string;
    @editable()
    @displayName("Last Name")
    public lastName: string;
    @editable()
    public dateOfBirth: Date;
    @editable()
    public size: number;
}

var author = new Person();
author.firstName = 'Gérald';
generateForm(document.body, author);

Generated form

Let's configure TypeScript to enable decorators and metadata.

  • experimentalDecorators allows to use the decorators in your code.
  • emitDecoratorMetadata instructs the compiler to add a metadata design:type for each property with a decorator.

The project.json should contains the 2 attributes:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
  }
}

The compiler will translate the TypeScript class to JavaScript, and decorates the properties with the required, displayName and design types. Here's an extract of the generated code:

Person = /** @class */ (function () {
    function Person() {
    }
    __decorate([
        editable(),
        displayName("First Name"),
        __metadata("design:type", String) // Added by emitDecoratorMetadata: true
    ], Person.prototype, "firstName", void 0);
    // ...
    return Person;
}());

Let's declare the editable and displayName decorators. You can look at the previous post to get a better understanding of TypeScript decorators.

function editable(target: any, propertyKey: string) {
    let properties: string[] = Reflect.getMetadata("editableProperties", target) || [];
    if (properties.indexOf(propertyKey) < 0) {
        properties.push(propertyKey);
    }

    Reflect.defineMetadata("editableProperties", properties, target);
}

function displayName(name: string) {
    return function (target: any, propertyKey: string) {
        Reflect.defineMetadata("displayName", name, target);
    }
}

Now, you can use the metadata to generate the form. You have to find the editable properties, get their display name to create the label, and their type to create the right input type. For instance, you have to use <input type="number"/> for a property of type number, and <input type="checkbox"/> for a property of type boolean. Then, you have to bind the inputs to the model, so changes in the UI are propragated to the model. The input event should be ok for that.

function generateForm(parentElement: HTMLElement, obj: any) {
    const form = document.createElement("form");

    let properties: string[] = Reflect.getMetadata("editableProperties", obj) || [];
    for (let property of properties) {
        const dataType = Reflect.getMetadata("design:type", obj, property) || property;
        const displayName = Reflect.getMetadata("displayName", obj, property) || property;

        // create the label
        const label = document.createElement("label");
        label.textContent = displayName;
        label.htmlFor = property;
        form.appendChild(label);

        // Create the input
        const input = document.createElement("input");
        input.id = property;
        if (dataType === String) {
            input.type = "text";
            input.addEventListener("input", e => obj[property] = input.value);
        } else if (dataType === Date) {
            input.type = "date";
            input.addEventListener("input", e => obj[property] = input.valueAsDate);
        } else if (dataType === Number) {
            input.type = "number";
            input.addEventListener("input", e => obj[property] = input.valueAsNumber);
        } else if (dataType === Boolean) {
            input.type = "checkbox";
            input.addEventListener("input", e => obj[property] = input.checked);
        }

        form.appendChild(input);
    }

    parentElement.appendChild(form);
}

You can now use the code at the beginning of the post, and it should work:

var author = new Person();
author.firstName = 'Gérald';
generateForm(document.body, author);

Conclusion

Decorators and metadata are very similar to what C# provides out of the box with attributes. It allows you to enrich the code with additional information, and get those information at runtime. In the previous post, I created a generic validation system. Today, I write a generic form generator in a few lines of code. They are lots of possibilities! If you are familiar with reflection in C#, I think you already have hundreds of ideas 😃