JWT Authentication with Asymmetric Encryption using certificates in ASP.NET Core
Tags: asp, dotnet, authentication, encryption
Published: 2021-03-07 11:45:33

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

---

In the previous article I wrote about JWT Authentication using a single security key, this being called Symmetric Encryption. The main disadvantage of using this encryption type is that anyone that has access to the key that the token was encrypted with, can also decrypt it. Instead, this article will cover the Asymmetric Encryption for JWT Token.
In the first part of this article, the Asymmetric Encryption concept will be explained, and in the second part, there will be the implementation of the JWT Token-based Authentication using the Asymmetric Encryption approach by creating an "Authentication" Provider in ASP.NET Core.

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

The JWT Token concepts were explained in the previous article, so if you want to find more before continuing reading this article, check out the introduction of the previous one: <https://stefanescueduard.github.io/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Introduction>.
Asymmetric Encryption is based on two keys, a public key, and a private key. The public key is used to validate, in this case, the JWT Token. And the private key is used to sign the Token. Maybe the previous statement is a little bit fuzzy, but I hope that will make sense in a moment.

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

For using Asymmetric Encryption, two keys have to be generated, these two keys have to come from the same root. In this case for this article, there will be a certificate - the root - from which the private and the public key will be generated. These keys will be also certificates, so the first thing that has to be done is to generate the private certificate - key - and the second one to generate the public certificate - key - from the private certificate.

[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#Generating-the-keys "Generating the keys")Generating the keys
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

To generate certificates I chose to use the OpenSSL toolkit. If you are on Windows, OpenSSL can be downloaded as an executable and installed where ever you want. I recommend being installed on the C:\ root.
OpenSSL download link: <https://slproweb.com/products/Win32OpenSSL.html>
The tool has to be used from the Terminal, so there are two choices:

1.  Run the executable from where the tool was installed.
2.  Add an environment variable to have access to it from everywhere as a CLI.

To add the tool as an environment variable the following entry has to be inserted into the User variables:

  `Variable name: OPENSSL_CONF
  Variable value: <PATH_TO_OPEN_SSL>\bin\cnf\openssl.cnf`

After configuring OpenSSL, the private and public key have to be generated using the following commands:

-   For the private key: `openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048`

    -   `genpkey` specifying that we'll generate a private key;
    -   `-algorithm RSA` the algorithm used, in this case RSA;
    -   `-out private_key.pem` the output argument and path;
    -   `-pkeyopt rsa_keygen_bits:2048` set the public key algorithm and the key size;
-   For the public key: `openssl rsa -pubout -in private_key.pem -out public_key.pem`

    -   `rsa` specifying that the command will process RSA keys;
    -   `-pubout -in private_key.pem` the private key and the path of it;
    -   `-out public_key.pem` the output argument and path;

    Before starting into code, the generated PEM keys have to be converted into XML files. That was the easiest way to read them using the `System.Security.Cryptography` package.
    To convert them into XML you can use this site: <https://superdry.apphb.com/tools/online-rsa-key-converter>, then copy the converted text into two files with the XML extension in the project folder.

The Setup is the same as in the previous article, so check it out here: <https://stefanescueduard.github.io/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Setup>. TL;DR you have to install the following package: `Microsoft.AspNetCore.Authentication.JwtBearer`.

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

As in the previous article, the Authentication service has to be added in the `ConfigureServices` method from the `Startup` class. For Authentication, an extension method called `AddAsymmetricAuthentication` will set up the service with the basic settings.
It may be a little bit confusing to switch between this and the previous article, but the only thing that is changed here compared to the previous article is the `IssuerSigningKey` property, which now receives the `SigningKey`. The previous article contains a comprehensive explanation of each property that it's used: <https://stefanescueduard.github.io/2020/04/11/jwt-authentication-with-symmetric-encryption-in-asp-dotnet-core/#Startup>.

The `SigningIssuerCertificate` is used to get the `IssuerCertificate` or the public key; I will return to this class in a moment. The code below contains only what is necessary to use the public key in the Authentication service.
```
public static IServiceCollection AddAsymmetricAuthentication(this IServiceCollection services)
{
    var issuerSigningCertificate = new SigningIssuerCertificate();
    RsaSecurityKey issuerSigningKey = issuerSigningCertificate.GetIssuerSigningKey();
    services.AddAuthentication(authOptions =>
        {
          ...
        })
        .AddJwtBearer(options =>
        {
            ...
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ...
                IssuerSigningKey = issuerSigningKey,
                ...
            };
        });

    return services;
}
```
After the `Authentication` service was added, in the `Configure` method the `Authorization` and `Authentication` middleware needs to be added to the pipeline.
```
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...
    app.UseAuthentication();
    app.UseAuthorization();
    ...
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#SigningIssuerCertificate "SigningIssuerCertificate")`SigningIssuerCertificate`
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

In this class, the RSA class is used to create a `RsaSecurityKey` with the public key generated before.
```
public RsaSecurityKey GetIssuerSigningKey()
{
    string publicXmlKey = File.ReadAllText("./public_key.xml");
    rsa.FromXmlString(publicXmlKey);

    return new RsaSecurityKey(rsa);
}
```
`FromXmlString` initializes the `rsa` object with parameters from the XML files.
If we dig down in this method - <https://git.io/JvbVm> - we can see that the `RSAParameters` are the same as they are in the XML file converted before.

The `rsa` is created on the constructor, this object must be disposed because there might be some resources that will run after the process ends.
```
public void Dispose()
{
    rsa?.Dispose();
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#SigningAudience-Certificate "SigningAudience Certificate")`SigningAudience Certificate`
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

`SigningAudienceCertificate` is very similar to the `SigningIssuerCertificate`, the only differences are that, is using the private key to initialize the `rsa` object and is returning `SigningCredentials` constructed with the `RsaSecurityKey` and the `SecurityAlgorithms`. For this, the `RsaSha256` algorithm is used because is the most recommended one. If you want to find what algorithm to use for each type of encryption, check out this article: <https://auth0.com/blog/json-web-token-signing-algorithms-overview/>.
```
public SigningCredentials GetAudienceSigningKey()
{
    string privateXmlKey = File.ReadAllText("./private_key.xml");
    rsa.FromXmlString(privateXmlKey);

    return new SigningCredentials(
        key: new RsaSecurityKey(rsa),
        algorithm: SecurityAlgorithms.RsaSha256);
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#AuthenticationService "AuthenticationService")`AuthenticationService`
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

This service is used by the `AuthenticationController` to authenticate the user. It is like a middleware because it's using the `UserService` to validate the received `UserCredentials` and the `TokenService` to generate the JWT Token if the credentials were valid.

The `UserService` and `UserCredentials` were created in the previous article so I will use them from there. The `UserService` is a more likely a mock service, that has an internal list of users and checks if the given credentials are on that list. And the `UserCredentials` contains two properties `Username` and `Password`.
```
public string Authenticate(UserCredentials userCredentials)
{
    userService.ValidateCredentials(userCredentials);
    string securityToken = tokenService.GetToken();

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

`TokenService` initializes on the constructor the `SigningAudienceCertificate` class created before. With this object, the `SigningCredentials` for the `TokenDescriptor` will be created.
```
private readonly SigningAudienceCertificate signingAudienceCertificate;

public TokenService()
{
    signingAudienceCertificate = new SigningAudienceCertificate();
}
```
The `GetToken` method is used to generate the `TokenDescriptor` by using the `GetTokenDescriptor` method that will be explained in a moment; to create a `SecurityToken` from that descriptor and to get the token as a string from that object.
```
public string GetToken()
{
    SecurityTokenDescriptor tokenDescriptor = GetTokenDescriptor();
    var tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken securityToken = tokenHandler.CreateToken(tokenDescriptor);
    string token = tokenHandler.WriteToken(securityToken);

    return token;
}
```
`GetTokenDescriptor` method creates a token with the minimum required properties: `Expires` and `SigningCredentials`. Also, the `Expires` property here is used because on the `Authentication` method the `LifetimeValidator` was set, but it doesn't need to be specified.
All `SecurityTokenDescriptor` properties can be found on the Microsoft website: <https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.securitytokendescriptor>.
The `GetAudienceSigningKey` method created before is used to generate the Token `SigningCredentials`, to validate that the Token was signed with the same private key from which the public key was generated.
```
private SecurityTokenDescriptor GetTokenDescriptor()
{
    const int expiringDays = 7;

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Expires = DateTime.UtcNow.AddDays(expiringDays),
        SigningCredentials = signingAudienceCertificate.GetAudienceSigningKey()
    };

    return tokenDescriptor;
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#AuthenticationController "AuthenticationController")`AuthenticationController`
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

In the `AuthenticationController` an endpoint is created to authenticate the user with `UserCredentials` and get the JWT Token by using the `AuthenticationService` described earlier.
```
[HttpPost]
public IActionResult Authenticate([FromBody] UserCredentials userCredentials)
{
    try
    {
        string token = authenticationService.Authenticate(userCredentials);
        return Ok(token);
    }
    catch (InvalidCredentialsException)
    {
        return Unauthorized();
    }
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#ValidationController "ValidationController")`ValidationController`
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

And the `ValidationController` contains a plain endpoint that it's using the `Authorize` attribute to validate the Token. Note that the Authentication Scheme must be used on the Authorize attribute and for the Authentication service.
```
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Validate()
{
    return Ok();
}
```
[](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#The-result "The result")The result
-----------------------------------------------------------------------------------------------------------------------------------------------

### [](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#Authentication "Authentication")Authentication

Firstly the Authentication happy flow will be tested, so the combination of the username and password will match and the endpoint should provide the generated Token.

![](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/authorized-authentication.png)

And secondly let's test the unauthorized flow, where the provided credentials are wrong.

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

### [](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/#Validation "Validation")Validation

Before checking that the Token is valid using the `ValidationController`, Auth0 crafted <https://jwt.io/> that decode the Token and check whether or not the Token is valid.

![](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/jwt-token-validation.png)

On the Verify Signature section, both keys must be entered to validate the signature of the certificate.
Now the `ValidationController` will be used to check whether the token is valid or not, but this will happen internally, on the Authorize attribute. Firstly, the happy flow.

![](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/valid-token.png)

And in the second test, the wrong token is validated.

![](https://eduardstefanescu.dev/2020/04/25/jwt-authentication-with-asymmetric-encryption-in-asp-dotnet-core/invalid-token.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.