Using JWT tokens to access an UmbracoApiController
Published: 2023-07-24 10:46:53

So far, JWT has been eluding me, I haven't had the time to look into them, I just knew they existed and worked by sending a header. 

Today, I was following a [question from Navorski](https://www.linen.dev/s/umbraco/t/13262507/what-is-the-correct-way-to-implement-a-umbracoapicontroller-) about using JWT authentication on an `UmbracoApiController`, I enjoyed the responses [from Dennis](https://dev.to/d_inventor) who had clearly done this before.

It seemed like this was easy enough to follow, so I decided to try it out for myself and see how far I could get. As it turns out, I was fearing them more than I should have done! Let's have a look.

First we need to tell our Web App (Umbraco) that JWT is one of the way to authenticate. For that, we need to install a NuGet package first to help us out with that:

```
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
```

The next thing to do is to set up some configuration items, which can be added after the `"Umbraco":` section.

```json
  "Jwt": {
    "Key": "ihuEAzo7BxW09LTTNKCz",
    "Issuer": "https://localhost:44336/",
    "Audience": "https://localhost:44336/"
  }
```

In the appSettings.json it looks like this: 

![Screenshot of the config addition to the appSettings.json file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4r3yw3wcf0k5tvyauu9.png)

The `Key` I generated here is a secret, please don't copy it but create a unique key for yourself.

Once that is installed, the [linked tutorial](https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/) tells us to add some things to `Startup.cs`. Personally, I like adding things using an `IComposer` to make my code more portable in case I want to reuse it or put it in a package, for example.

So I end up with the following code, that does the same thing as it would have done if I had added it to `Startup.cs`.

```csharp
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Umbraco.Cms.Core.Composing;

namespace JwtTest;

public class JwtComposer : IComposer
{
    public void Compose(IUmbracoBuilder builder)
    {
        builder.Services.AddAuthentication().AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = builder.Config["Jwt:Issuer"],
                ValidAudience = builder.Config["Jwt:Audience"],
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Config["Jwt:Key"]))
            };
        });
    }
}
```

Whenever we request JWT token authentication, this code will run.

Next up, we can make an `UmbracoApiController` with an example endpoint:

```csharp
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common.Controllers;

namespace JwtTest;

public class TestApiController : UmbracoApiController
{
    public ActionResult<string> GetAuthenticatedMessage()
    {
        return "You need to be authenticated to see this message.";
    }
}
```

Now, when we run the site and go to `https://localhost:44336/umbraco/api/TestApi/GetAuthenticatedMessage`, we see the message `You need to be authenticated to see this message.`. Great! But the message is a lie, we didn't request or provide any authentication. Let's add some.

```csharp
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Umbraco.Cms.Web.Common.Controllers;

namespace v12final;

public class TestApiController : UmbracoApiController
{
    [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public ActionResult<string> GetAuthenticatedMessage()
    {
        return "You need to be authenticated to see this message.";
    }
}
```

The `Authorize` attribute now requires us to provide a JWT bearer token in order for the request to succeed. When we now try to access the same endpoint, we get a nice `401 Unauthorized` error. 

We're getting there! We need a way to obtain a JWT authorization token now. Normally you'd do that by verifying a user is authenticated in the system, I'll leave that part as a exercise to the reader and focus on generating the token we need. So in the next snippet of code, we allow anyone who provides us with any username and password to get a token. 

**⚠️ Don't do this in real life, make sure you only hand out tokens to trusted parties, verify you can trust them.**

The following code was added to the same `TestApiController` we had above:

```csharp
private readonly IConfiguration _config;

public TestApiController(IConfiguration config)
{
    _config = config;
}

[HttpPost]
public ActionResult<string> GenerateJwtToken([FromBody] UserModel user)
{
    // TODO: Do something to verify username/password
    
    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
    var claims = new[]
    {
        new Claim(ClaimTypes.NameIdentifier,user.Username),
        new Claim(ClaimTypes.Role,"apiuser")
    };
    var token = new JwtSecurityToken(_config["Jwt:Issuer"],
        _config["Jwt:Audience"],
        claims,
        expires: DateTime.Now.AddMinutes(15),
        signingCredentials: credentials);


    return new JwtSecurityTokenHandler().WriteToken(token);
}

public class UserModel
{
    public string Username { get; set; }
    public string Password { get; set; }
}
```

We've also added the following `using`s:

```csharp
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
```

We're injecting an `IConfiguration` because we need some values from `appSettings` and other than that it's mostly copied code from the tutorial. As said, there's no verification of any credentials, we just generate a token and spit it out. 

Now let's try it out in Postman by sending a `POST` to the new endpoint at `https://localhost:44336/umbraco/api/TestApi/GenerateJwtToken`, adding a username and password.

![Example of the request in Postman, which returns the JWT token we need](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9c79yikiz1oljumofuh.png)

Success! We get a token that we can hopefully use in the `GetAuthenticatedMessage` call.

Going back to Postman, we can add a header to the `GetAuthenticatedMessage` request named `Authorization` and as the value we add `Bearer`, then a space and then the token we received from the `GenerateJwtToken` endpoint.

![Example of the request in Postman, which adds the Bearer token and shows the expected success response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wrirfvdubtphimntqxh.png)


And as you can see in the response body, we do indeed get our message back, this time it's not lying to us: `You need to be authenticated to see this message.`. Correct!

And there you have it, JWT tokens demystified (for me at least 😅).