C# JWT Authentication .NET 6
In this article, we will work on implementing C# JWT Authentication using .NET 7 - which also works for .NET 6, and preview .NET 8 - using ASP.NET Core.
Security is a significant concern today, with so much sensitive information, and not so much sensitive ones, being transmitted across the internet. One way to ensure that only authorized users access restricted information is through authentication.
OAuth 2 and JWT Tokens (JSON Web Token) are the most common ways to ensure modern Web Applications and Mobile Applications authentication.
๐ข Table of Contents
In this issue, we're going to cover the following topics:
- What are Access Token, JWT Token, and Bearer Authorization
- Set Up Authentication and Authorization (skip to this section if you want just the code)
- Generate the JWT Token / Access Token
- Testing the Endpoints
- Conclusion
๐งพ What are Access Token, JWT Token, and Bearer Authorization?
Access tokens, JWT tokens, and Bearer Authorization are commonly used in web applications to authenticate users and provide access to protected resources.
An Access Token is a ticket to authenticate a user and access a protected resource.
JWT Token, on the other hand, is a specific type of Access Token encoded as a JSON object, hence JSON Web Token.
It consists of a header, a payload, and a signature, and it is commonly used in modern web applications.
> This is a Raw JWT Token
> This is the Decoded JWT Token
In addition, the JWT token can contain various claims, such as the user's id, expiration date, and application permissions.
Bearer Authorization is a mechanism for transmitting Access Tokens in HTTP requests. It involves including the Access Token in the Authorization header of the HTTP request using the Bearer Authentication Scheme.
The Bearer Scheme indicates that the token being transmitted is an Access Token, allowing the server to authenticate the user and grant access to protected resources.
For example:
- The Resource Owner sends a request to
/tokens/connect/
with his username and password - The server validates the request and generates a valid Access Token in the format of a JWT Token, and returns it to the client
- The client includes the token in the Authorization header (with the Bearer scheme) in the subsequent requests to the server
- The server verifies the token before granting access to protected resources
Set Up Authentication and Authorization
From this point, we are going to implement the JWT Authentication using C# .NET Web API.
Step 1: Create the Project
For our project, we will use C# .NET 7 and start from the Empty Web Project, which is enough for our POC (proof of concept).
Let's name our project Labs.JwtAuthentication.
After completion of creation, our project should look like this:
Step 2: Installing the dependency packages
Our next step is to install the necessary NuGet packages:
You should use the package version according to the framework restriction.
After the installation of all of the packages, we should end with something like this:
Step 3: Adding Variables to the Configuration File
In this next step, let's create the configurations for the Audience, Issuer, and Signing Key in our appsettings.json.
We will use them to generate the JWT Token (Access Token) and to validate it in the Authentication process.
Next, let's create a record class to hold this configuration and use it in the application:
namespace Labs.JwtAuthentication;
public record class JwtOptions(
string Issuer,
string Audience,
string SigningKey,
int ExpirationSeconds
);
More on C# Records
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record
Finally, we will read the configuration and map it to JwtOptions
as a Singleton lifetime. Of course, you can also use the Options pattern if you prefer.
This allows us access to the JwtOptions
object at any time in the application.
Step 4: Configure the Authentication and Authorization Services
In action, we will configure the Authentication and Authorization services to validate the JWT token when it exists in the HTTP Authorization header.
// ๐ Configuring the Authentication Service
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opts =>
{
//convert the string signing key to byte array
byte[] signingKeyBytes = Encoding.UTF8
.GetBytes(jwtOptions.SigningKey);
opts.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtOptions.Issuer,
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(signingKeyBytes)
};
});
// ๐ Configuring the Authorization Service
builder.Services.AddAuthorization();
In the code snippet above, we have configured the Authentication service to:
- Adds the Bearer Scheme as the default scheme for authentication
- ValidateIssuer: validate who token generated the token (the Issuer)
- ValidateAudience: validate for whom it was generated (the audience)
- ValidateToken: check if it is not expired
- ValidateIssuerSigningKey: validate the token signature
This is the most common and minimal validation setup we should use when working with JWT Tokens.
Last, we have added the Authorization service with the default configuration.
Step 5: Set Up Authentication and Authorization Middleware
In this final step, we will include the Authentication and Authorization middlewares in the pipeline to validate the requests with the JWT Token.
More on Minimal APIs
And finally, let's indicate which route should be authorized (non-public) and allow anonymous (public) requests.
var app = builder.Build();
// ๐ This add the Authentication Middleware
app.UseAuthentication();
// ๐ This add the Authorization Middleware
app.UseAuthorization();
// ๐ The routes / and /public allow anonymous requests
app.MapGet("/", () => "Hello World!");
app.MapGet("/public", () => "Public Hello World!")
.AllowAnonymous();
// ๐ The routes /private require authorized request
app.MapGet("/private", () => "Private Hello World!")
.RequireAuthorization();
// ๐ handles the request token endpoint
app.MapPost("/tokens/connect", (HttpContext ctx, JwtOptions jwtOptions)
=> TokenEndpoint.Connect(ctx, jwtOptions));
app.Run();
Until here, we have completed the setup of the Authentication and Authorization process.
Our application can authenticate and authorize our requests with an Access Token.
Generate the JWT Token / Access Token
From this step forward, we will implement the endpoint to create the JWT Tokens (Access Tokens).
The endpoint skeleton should have this format:
And the response should be:
Implementing a Route to Create the JWT Token
Let's create a static class TokenEndpoint. This class will contain all the logic to handle the /connect/token
request and generate the tokens.
Let's implement the method CreateAccessToken:
The CreateAccessToken
method will generate the token with claims:
- sub: Unique identifier for the end-user
- name: End-user full name
- aud: Audience(s) that this Token is intended for.
- role: User role(s)
A JWT Token can handle various pre-defined and custom claims.
Next, let's add the implementation of the method Connect:
The Connect method reads the form and validates each property before generating the access token. Once everything is set, we call the method CreateAccessToken method.
Finally, let's include Map the route /tokens/connect
POST.
app.MapPost("/tokens/connect", (HttpContext ctx, JwtOptions jwtOptions)
=> TokenEndpoint.Connect(ctx, jwtOptions));
With all of this, we have a complete application capable of generating and validating access tokens in the format of JWT Tokens.
Testing the Endpoints
Create a valid HTTP Request using Visual Studio, Visual Studio, Postman, Insomnia, or any tool that you like.
https://github.com/ricardodemauro/Labs.JwtAuthentication/blob/master/http-test.http
A Sample Request to Get a Valid Access Token:
POST https://localhost:7004/tokens/connect HTTP/1.1
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3wr
Result of Connect Endpoint:
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json; charset=utf-8
Date: Thu, 27 Apr 2023 17:35:45 GMT
Server: Kestrel
Transfer-Encoding: chunked
{
"access_token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJz......",
"expiration": 3600,
"type": "bearer"
}
Making a Request Without Access Token to Private Route:
GET https://localhost:7004/private
HTTP 401 - Unauthorized Result of Private Route:
HTTP/1.1 401 Unauthorized
Content-Length: 0
Connection: close
Date: Thu, 27 Apr 2023 17:36:27 GMT
Server: Kestrel
WWW-Authenticate: Bearer
Making a Request With Access Token to Private Route
GET https://localhost:7004/private
Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJz......
HTTP 200 - Ok Result of Authorized Route:
HTTP/1.1 200 OK
Connection: close
Content-Type: text/plain; charset=utf-8
Date: Thu, 27 Apr 2023 17:37:10 GMT
Server: Kestrel
Transfer-Encoding: chunked
Private Hello World!
Conclusion
JWT authentication is a secure and effective way to authenticate users in web applications.
C# .NET provides a simple and easy-to-implement way to use JWT Authentication and Authorization.
Following the steps outlined in this article, you can implement JWT Authentication in your C# .NET 8 Web Applications and ensure that only authorized users can access protected resources.