Implement JWT authentication in ASP.NET Core minimal APIs

In the previous part of this article series we learned to integrate Swagger with ASP.NET Core minimal APIs. So far we created minimal APIs for performing CRUD operations. Now it's time to add authentication and authorization to the minimal APIs. As with controller based APIs the most common approach to implement authentication in minimal APIs is to use JSON Web Token or JWT. To that end this part of this series will cover that and will also tweak Swagger configuration to use JWT while invoking the minimal APIs.

Open the same project that we have been building in the pervious parts of this series.

Since we want to implement JWT authentication add Microsoft.AspNetCore.Authentication.JwtBearer NuGet package to the project.

Then open appsettings.json and add the following configuration section to it:

"Jwt": {
    "Issuer": "example.com",
    "Audience": "example.com",
    "Key": "This is super secret key for example.com"
}

The Jwt section stores three keys Issuer, Audience, and Key. If you are not familiar with the general concepts of JWT authentication I would suggest you to read my earlier articles here, here, and here.

We need these settings while configuring JWT authentication during the application's startup. So, we will read them in the Program.cs as shown below:

builder.Services.AddAuthentication(o =>
{
    o.DefaultAuthenticateScheme = JwtBearerDefaults.
AuthenticationScheme;
    o.DefaultChallengeScheme = JwtBearerDefaults.
AuthenticationScheme;
    o.DefaultScheme = JwtBearerDefaults.
AuthenticationScheme;
}).AddJwtBearer(o =>
{
    o.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = false,
        ValidateIssuerSigningKey = true,
        ValidIssuer = builder.Configuration["Jwt:Issuer"],
        ValidAudience = builder.Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});

The Add Authentication() method specifies the application's authentication scheme to be JWT Bearer. The chained called to AddJwtBearer() configures various token parameters using TokenValidationParameters object. Notice the parameters marked in bold letters. We pick Issuer, Audience, and Key values from appsettings.json and use them to set ValidIssuer, ValidAudience, and IssuerSigningKey properties.

We will also add authorization services by calling:

builder.Services.AddAuthorization();

Before you go ahead, make sure that all the above code is place just before builder.Build() call.

Since we have enabled JWT bearer authentication  for out application we can now authenticate our minimal APIs. To do so, add [Authorize] attribute to all the five minimal APIs we created earlier. As an example, GET and POST endpoints are shown below.

app.MapGet("/minimalapi/employees", 
[Authorize](AppDbContext db) =>
{
    return Results.Ok(db.Employees.ToList());
});
app.MapPost("/minimalapi/employees", 
[Authorize](AppDbContext db, Employee emp) =>
{
    db.Employees.Add(emp);
    db.SaveChanges();
    return Results.Created
($"/minimalapi/employees/{emp.EmployeeID}", emp);
});

As you can see, [Authorize] has been added to the endpoint handler functions to indicate that the underlying handler can be invoked only by authenticated user.

After all the "Map" methods, add this code to wire authentication and authorization middleware.

...
app.UseAuthentication();
app.UseAuthorization();

app.UseSwagger();
...

We haven't written any code that issues a JWT or validates a user yet. But we will go ahead and try to invoke our minimal APIs using Swagger just to check what happens when an unauthenticated user tries to invoke the APIs.

So, run the application and invoke the GET endpoint using the Swagger UI.

This time you will get this error:

As you can see, the GET request is returning HTTP status code 401 (Unauthorized). This is expected because we have decorated [Authorize] attribute on the handler but haven't passed any JWT while making the request.

To fix this we need to device a mechanism that issue us a JWT. We can then pass this JWT while calling the minimal APIs.

Add a new class at the end of Program.cs (you can place it after the Employee and AppDbContext classes) as shown below:

public class User
{
    public string UserName { get; set; }
    public string Password { get; set; }
}

The User class represents a system user and contains UserName and Password properties.

Then add another POST endpoint just below the five minimal APIs we wrote earlier.

app.MapPost("/minimalapi/security/getToken", 
[AllowAnonymous](User user) =>
{

    if (user.UserName=="user1" && user.Password=="password1")
    {
        var issuer = builder.Configuration["Jwt:Issuer"];
        var audience = builder.Configuration["Jwt:Audience"];
        var securityKey = new SymmetricSecurityKey
    (Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]));
        var credentials = new SigningCredentials(securityKey, 
SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(issuer: issuer,
            audience: audience,
            signingCredentials: credentials);

        var tokenHandler = new JwtSecurityTokenHandler();
        var stringToken = tokenHandler.WriteToken(token);

        return Results.Ok(stringToken);
    }
    else
    {
        return Results.Unauthorized();
    }
});

The endpoint URL is /minimalapi/security/getToken and its handler accepts a User object.

Inside, we check the UserName and Password against hard coded values (we will integrate ASP.NET Core Identity in the next part of this series).

We read Issuer, Audience, and Key from the appsettings.json file. Based on these values we create a new JwtSecurityToken object. To convert this JwtSecurityToken into a string token we use JwtSecurityTokenHandler's WriteToken() method.

The string token in wrapped in an OK response and sent back to the client.

To see the getToken handler in action you can run the application and invoke the getToken endpoint using the Swagger UI.

As you can see the response body now contains the JWT sent by the getToken endpoint. This indicates that our getToken endpoint is working as expected.

So far so good.

We need to pass a JWT obtained from the getToken endpoint with all the CRUD calls. Since we are using Swagger UI to invoke the minimal APIs it would be nice if we tell Swagger to pass our JWT while making the API calls. This requires a bit of Swagger configuration. Take a look at this additional configuration below:

var securityScheme = new OpenApiSecurityScheme()
{
    Name = "Authorization",
    Type = SecuritySchemeType.ApiKey,
    Scheme = "Bearer",
    BearerFormat = "JWT",
    In = ParameterLocation.Header,
    Description = "JSON Web Token based security",
};

var securityReq = new OpenApiSecurityRequirement()
                {
                    {
                     new OpenApiSecurityScheme
                     {
                     Reference = new OpenApiReference
                     {
                     Type = ReferenceType.SecurityScheme,
                     Id = "Bearer"
                     }
                     },
                     new string[] {}
                    }
                };

builder.Services.AddEndpointsApiExplorer();

The above code creates objects of OpenApiSecurityScheme and OpenApiSecurityRequirement respectively.

These objects specify the authentication scheme related details such as scheme and bearer format. These objects are then supplied to the AddSwaggerGen() method as shown below:

builder.Services.AddSwaggerGen(o =>
{
    o.SwaggerDoc("v1", info);
    o.AddSecurityDefinition("Bearer", securityScheme);
    o.AddSecurityRequirement(securityReq);
});

We pass these objects using AddSecurityDefinition() and AddSecurityRequirement() methods.

If you run the Swagger UI again, you will notice that it now shows the Authorize button at the top.

Now execute the getToken API again and copy the returned JWT as discussed earlier.

Then click on the Authorize button to open the following dialog.

Paste the JWT copied earlier into the Value textbox. Notice how the JWT is specified in the authorization header. It's Bearer, then a white space, followed by the JWT string. Make sure to remove the double quotes from the JWT upon pasting. Then click on the Authorize button.

Now click on the Close button to close the dialog.

Now that the authorization header has been assigned a value, we can invoke the CRUD operations. The following figures shows a successful run of the GET request.

As you can see a list of employees has been successfully returned from the minimal API.

In this example we used hard coded user name and password values for the sake of quickly testing the JWT authentication. Wouldn't it be nice if we plug-in ASP.NET Core Identity to create and validate user accounts? That's what we will do in the next part of this article series. 

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a yoga mentor, meditation teacher, and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his private online courses on ASP.NET and meditation go here and here.

Posted On : 10 December 2021


Tags : ASP.NET ASP.NET Core MVC .NET Framework C# Visual Studio