JWT Authentication with Symmetric Encryption in ASP.NET Core
Tags: asp, dotnet, encryption
Published: 2021-03-07 11:37:50

Originally published at https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/.

---

JWT Authentication is becoming one of the most used authentication types in modern web applications or services. This article covers the JWT Authentication with a Symmetric Key in ASP.NET Core. In the first part, there will be a short introduction to what Symmetric Key represents and the second part contains the prerequisites for this project and the actual implementation of this authentication type.
This article is the first article from of series of two, the second one will contain the authentication with an Asymmetric Key using a certificate.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Introduction "Introduction")Introduction
----------------------------------------------------------------------------------------------------------------------------------------------------

JWT Token is a common way of creating access tokens that can contain several claims (e.g. Username, Roles). JWT Token means JSON (JavaScript Object Notation) Web Token. Every JWT Token has the following structure:

-   Header, containing the encryption algorithm;
-   Payload, containing custom Claims, plus at least two required claims:
    -   `exp` representing the expiration time when the Token will become unavailable;
    -   `iat` or Issued at Time, the time when the Token was created;\
        The times are formatted using the Unix Timestamp format (e.g. 1582784721).
-   Signature, representing the encoded header, plus `a dot`, plus the encoded payload, plus a secret key. The concatenated result will be run through the encryption algorithm specified on the Header to validate the Token.

If you want to read more about JWT Token, this comprehensive paper covers all the concepts: <https://tools.ietf.org/html/rfc7519>.

### [](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Symmetric-Key "Symmetric Key")Symmetric Key

The Symmetric Key is used both for signing and validation. For example, let's say John wants to share a secret with Jane, when the secret is told, John also tells Jane a password - the key - in order for the secret to being understood. In this way, John - the identity provider or the service - ensures that his secret is well kept by using the given password.

![Symmetric Key](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/symmetric-key.svg)

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Setup "Setup")Setup
-------------------------------------------------------------------------------------------------------------------------------

ASP.NET Core 3.1 will be used for this project. Microsoft also offers a great package that provides all that is needed to create a JWT Token-based authentication. The package is called `Microsoft.AspNetCore.Authentication.JwtBearer`, this is the only package that the project needs, and can be found here: <https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer>.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Creating-a-secret-key "Creating a secret key")Creating a secret key
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The signing and validation key will be a user secret key. ASP.NET provides the user secret key feature to store all the confidential data that doesn't have to be committed or shared outside the user or developer environment. For the production or testing environments, the keys need to be store in a cloud vault, like Microsoft Azure offers through Key Vault - <https://azure.microsoft.com/en-us/services/key-vault/> -, but this will be a topic for another article.\
Firstly, the project needs to be initiated for using user secrets, by running the following command in the project folder:
```
dotnet user-secrets init
```
Then the user secret key is added, using the following command:
```
dotnet user-secrets set "AppSettings:EncryptionKey" "POWERFULENCRYPTIONKEY"
```
This command will add to the `secrets.json` file the `AppSettings:EncryptionKey` key with the value `POWERFULENCRYPTIONKEY`.\
If there are multiple values for the `AppSettings` then this key can become more readable by using a JSON format like:
```
"AppSettings": {
  "EncryptionKey": "POWERFULENCRYPTIONKEY",
  "Key2": "Value2" 
} 
```
The `POWERFULENCRYPTIONKEY` will be encoded in an array of bytes and then this binary will be Base64 encoded, this is required for both signing and validation.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Startup "Startup")Startup
-------------------------------------------------------------------------------------------------------------------------------------

In the `ConfigureServices` method from the `Startup` class, the `AppSettings` section needs to be read. To read a type from the configuration file, a class must be created, so for the `AppSettings` section an equivalent class needs to exists, as is shown below. This class can be seen as a Data Transfer Object, which contains plain properties.
```
public class AppSettings
{
    public string EncryptionKey { get; set; }
}
```
After the section is read, the `EncryptionKey` needs to be converted into bytes.
```
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    IConfigurationSection settingsSection = configuration.GetSection("AppSettings");
    AppSettings settings = settingsSection.Get<AppSettings>();
    byte[] signingKey = Encoding.UTF8.GetBytes(settings.EncryptionKey);

    services.AddAuthentication(signingKey);

    services.Configure<AppSettings>(settingsSection);
    services.AddTransient<AuthenticationService>();
    services.AddTransient<UserService>();
    services.AddTransient<TokenService>();
}
```
On `line 9` the `Authentication` service is added into the App container, this service is responsible, for managing `Authentication` settings, like `IssuerSigningKey` or `LifeTimeValidation`.\
For this step, an extension method is created called `AddAuthentication`, which receives the `service` and the `signingKey` converted earlier.\
From `line 11` to `14`, the services are configured for the Dependency Injection, we will return to the implementation of these services in a moment.
```
public static IServiceCollection AddAuthentication(this IServiceCollection services,
    byte[] signingKey)
{
    services.AddAuthentication(authOptions =>
        {
            authOptions.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            authOptions.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
        .AddJwtBearer(jwtOptions =>
        {
            jwtOptions.SaveToken = true;
            jwtOptions.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false,
                ValidateIssuer = false,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(signingKey),
                ValidateLifetime = true,
                LifetimeValidator = LifetimeValidator
            };
        });

    return services;
}
```
The `authenticationOptions` need to configure the `Authenticate` and `Challenge` Schemes, in order to verify that the endpoint(s) which receives a JWT Token will go through the validation step, as is described below starting from `line 12`. The same Schema will be seen on the endpoints that use the `AuthorizeAttribute`.\
Then the `JwtBearer` is added to the `Authentication` process, using the following properties:

-   `SaveToken` is self-explanatory. It's used to persist the Token into local storage. The token will be valid even if the service restarts, so its lifetime is different from the application;
-   `ValidateAudience` and `ValidateIssuer` must be used for the service to skip or to validate the Audience or the Issuer. The Audience refers to the server or the Identity Provider, in this case, our ASP.NET Service. And the `Issuer` refers to the client(s) that makes HTTP request(s). For the sake of this example, both are set `false`. Please note that even if you don't want to validate the `Audience` or/and the `Issuer` these values must be set;
-   `ValidateIssuerSigningKey` needs to be set to `true`, in order to validate the received Token;
-   For `IssuerSigningKey` will use the `SymmetricSecurityKey`, the same approach will be also used when the Token will be created.
-   `LifeTimeValidator` is important if the generated `Token` has set an expiration time.

All the JWT Bearer Options can be found on the Microsoft website: <https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions>.

The `LifeTimeValidator` handler is checking if the expiration date is greater than the current Date, as follows:
```
private static bool LifetimeValidator(DateTime? notBefore,
    DateTime? expires,
    SecurityToken securityToken,
    TokenValidationParameters validationParameters)
{
    return expires != null && expires > DateTime.Now;
}
```
After the services were configured, the `Authentication` and `Authorization` middleware must be added to the App pipeline in the `Configure` method.
```
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseAuthentication();
    app.UseAuthorization();
    ...
}
```
[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#UserCredentials "UserCredentials")`UserCredentials`
---------------------------------------------------------------------------------------------------------------------------------------------------------------

User's Credentials will be used as a Data Transfer Object, this class will be received on the authentication endpoint and sent to the `AuthenticationService`. It's a plain class that contains only the `Username` and the `Password` of the user.
```
public class UserCredentials
{
    public string Username { get; set; }
    public string Password { get; set; }
}
```
[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#AuthenticationService "AuthenticationService")`AuthenticationService`
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The `AuthenticationService` is used like a middleware that receives the `UserCredentials` from the `Controller`, validates them using the `UserService` and if the credentials are valid, it creates a Token using the `TokenService`. Both the `User` and `Token` services are injected into the constructor.
```
public string Authenticate(UserCredentials userCredentials)
{
    userService.ValidateCredentials(userCredentials);
    string securityToken = tokenService.GetToken();

    return securityToken;
}
```
[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#UserService "UserService")`UserService`
---------------------------------------------------------------------------------------------------------------------------------------------------

For the sake of this example, the `UserService` contains a list of users created on the constructor. In a real-life scenario, this will be read from storage or from a service.
```
public class UserService
{
    private readonly IEnumerable<UserCredentials> users;

    public UserService()
    {
        users = new List<UserCredentials>
        {
            new UserCredentials
            {
                Username = "john.doe",
                Password = "john.password"
            }
        };
    }
...
```
This is more like a `UserValidation` service, but to better illustrate that it also reads the users, the `UserService` name will be kept.
```
...
    public void ValidateCredentials(UserCredentials userCredentials)
    {
        bool isValid =
            users.Any(u =>
                u.Username == userCredentials.Username &&
                u.Password == userCredentials.Password);

        if (!isValid)
        {
            throw new InvalidCredentialsException();
        }
    }
}
```
The `ValidateCredentials` method checks if the `username` and `password` pair exists, and if it doesn't it will throw the `InvalidCredentialsException` which will be caught on the `Controller`.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#TokenService "TokenService")`TokenService`
------------------------------------------------------------------------------------------------------------------------------------------------------

`TokenService` is receiving on the constructor the `AppSettings`, which will be used on the `GetTokenDescriptor` method to set up the Token.
```
public class TokenService
{
    private readonly AppSettings appSettings;

    public TokenService(IOptions<AppSettings> options)
    {
        appSettings = options.Value;
    }
...
```
The public `GetToken` method is used to get the token description, to create the Token and write it into a string, that will be returned to the calling service, in this case to the `AuthenticationService`.
```
public string GetToken()
{
    SecurityTokenDescriptor tokenDescriptor = GetTokenDescriptor();
    var tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
    string token = tokenHandler.WriteToken(securityToken);

    return token;
}
```
On the `GetTokenDescriptor` method, the token is constructed. In this method, the `ExpirationTime` and `SigningCredentials` are set. Because the Claims are not in the main focus of this article, I will create another one, in which I will explain how the Claims can be set on the Token and how they can be used.
```
private SecurityTokenDescriptor GetTokenDescriptor()
{
    const int expiringDays = 7;

    byte[] securityKey = Encoding.UTF8.GetBytes(appSettings.EncryptionKey);
    var symmetricSecurityKey = new SymmetricSecurityKey(securityKey);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Expires = DateTime.UtcNow.AddDays(expiringDays),
        SigningCredentials = new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256Signature)
    };

    return tokenDescriptor;
}
```
All the Token Descriptors can be found on the Microsoft website: <https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.securitytokendescriptor>.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#AuthenticationController "AuthenticationController")`AuthenticationController`
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Now, all we have to do, is to create an `AuthenticationController` which receives the `UserCredentials` and uses the previously created `AuthenticationService`.\
On the constructor the `AuthenticationService` is injected, to be used on the `Authentication` endpoint.
```
[Route("identity/[controller]")]
public class AuthenticationController : ControllerBase
{
    private readonly AuthenticationService authenticationService;

    public AuthenticationController(AuthenticationService authenticationService)
    {
        this.authenticationService = authenticationService;
    }
...
```
The authentication endpoint accepts HTTP Post requests, receives the `UserCredentials` as previously mentioned, and uses the `AuthenticationService` to authenticate and create the Token.
```
...
    [HttpPost]
    public IActionResult Authenticate([FromBody] UserCredentials userCredentials)
    {
        try
        {
            string token = authenticationService.Authenticate(userCredentials);
            return Ok(token);
        }
        catch (InvalidCredentialsException)
        {
            return Unauthorized();
        }
    }
}
```
If the credentials are valid, then the endpoint will return an `OK` HTTP Status code and the generated token. Otherwise, if the `InvalidCredentialsException` is thrown, the `Unauthorized` HTTP Status code is returned.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#ValidationController "ValidationController")`ValidationController`
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

The purpose of the `ValidationController` is to check that the signing process is working, in order to validate the Token.
```
Route("identity/[controller]")]
public class ValidationController : ControllerBase
{
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public IActionResult Validate()
    {
        return Ok();
    }
}
```
You may notice that the `Validate` endpoint has the `AuthorizeAttribute` which has on its constructor the same `AuthenticationSchemes` as was set on the `Authentication` service.

[](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#The-result "The result")The result
----------------------------------------------------------------------------------------------------------------------------------------------

Firstly, the happy flow for the `AuthenticationController` is tested, so we'll provide the correct combination of the username and password, in order to receive the token.

![Happy flow authentication](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/happy-flow-authentication.png)

Let's test with credentials that are not correct, the response should be Unauthorized.

![Unauthorized authentication](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/unauthorized-authentication.png)

And secondly, the generated token needs to be tested using the `Validation` controller. The first test will be with the generated token, to see that the validation is passed.

![Happy flow validation](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/happy-flow-validation.png)

And the second test is when the wrong token is provided for validation.

![Unauthorized flow validation](https://eduardstefanescu.dev/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/unauthorized-validation.png)

* * * * *

The source code from this article can be found on my GitHub account: <https://github.com/StefanescuEduard/JwtAuthentication>.

Thanks for reading this article, if you find it interesting please share it with your colleagues and friends. Or if you find something that can be improved please let me know.