Welcome to today’s post.
In today’s post I will be discussing how we can retrieve user details from JWT bearer tokens within a .NET Core Web API.
In a previous post I showed how we can retrieve user details from a JWT bearer token by decoding a token. However, this method relies on having to use the secret to decode the token.
At this point you would already be aware of how we encode and decode JWT tokens from an identity service and from a protected consuming web API. I will proceed to show how we setup the extensions in our .NET Core to allow us to obtain the HTTP Context and the JWT authorization token.
I will go through the necessary checklists that we need in our ASP.NET Core application before we can read the user context and retrieve useful information from it. These include ensuring that authentication is enabled, the appropriate authentication scheme is specified, and the HTTP context is injected within the services collection. This will also include which namespaces to include in our code to ensure the necessary authentication and authorization middleware is available for use.
Checklist of Application Changes to Enable JWT Authentication
As part of our checklist, in ConfigureServices() make the following changes:
To enable JWT authentication services we are required to inject it as follows:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(..)
In our classes, to make use of the JWT Bearer authentication we will need the following namespace:
using Microsoft.AspNetCore.Authentication.JwtBearer;
And to utilize the token validation of signing keys we need to following namespace:
using Microsoft.IdentityModel.Tokens;
To enable HTTP context to be injected safely into our dependent controllers and services we use the HttpContextAccessor service as follows:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In our classes, to use the HttpContextAccessor, we will need to use the namespace:
using Microsoft.AspNetCore.Http;
In Configure() make the following changes:
To enable the authentication of the token to be validated in the HTTP request header enable the following extension middleware:
app.UseAuthentication();
To enable authorization of methods and controllers in our API controller we enable the following extension middleware:
app.UseAuthorization();
In the above sequence, we must ensure the authentication is declared before authorization. The reason is the authentication of the user comes before they are authorized to call an API method that is protected with the [Authorize] attribute.
From this point on we will have the user identity and claims available in the HttpContext of the request header after we have run and executed one of our HTTP REST API methods successfully.
Assuming that the user roles are stored in the claims within:
Request.HttpContext.User.Claims
or
Request.HttpContext.User.Identity.Claims
In a routine debug of one of the API calls we can see the structure as shown:
The ClaimsIdentity object contains a property, Claims, which will contain the claims of type name and role that correspond to the username/email and roles. These are straightforward to retrieve. I will show how this is done in the next section.
Retrieval of User Details from User Context Claims
From here we can implement a rudimentary service that retrieves both the username and claims which contain the roles the user is under:
using System;
using System.Collections.Generic;
using System.Linq;
using BookLoan.Catalog.API.Models.UserViewModels;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
namespace BookLoan.Catalog.API.Services
{
public class UserContextService: IUserContextService
{
private readonly HttpContext _context;
public UserContextService(IHttpContextAccessor httpContextAccessor)
{
_context = httpContextAccessor.HttpContext;
}
public UserContextModel GetUserDetails()
{
var userContext = new UserContextModel();
string userName = _context.Request.HttpContext.User.Identity.Name;
List<Claim> claims = _context.Request.HttpContext.User.Claims.Where(
c => c.Type == ClaimTypes.Role.ToString()).ToList();
if ((claims == null) || (claims?.Count == 0))
throw new Exception("Cannot retrieve user claims.");
userContext.roles = new List<string>();
userContext.userName = userName;
foreach (Claim claim in claims)
{
userContext.roles.Add(claim.Value);
}
return userContext;
}
…
}
}
Implementation of User Role Membership Functions
In our application we may require logic to test whether the current user is authorized to access features or data within the application. In addition to what we have implemented in the previous section in retrieving user details and roles, we can use the roles within the user details to test for membership of a particular role.
We add some additional methods to test role memberships. These are shown below:
private bool IsMemberOfRole(string role)
{
return GetUserDetails().roles.Contains(role);
}
public bool IsAdminUser()
{
return IsMemberOfRole("Admin");
}
public bool IsMemberUser()
{
return IsMemberOfRole("Member");
}
public bool IsGuestUser()
{
return IsMemberOfRole("Guest");
}
I will then show how to use the above custom user details service within our application in the next section.
Application Integration and the User Details Service
To integrate the custom user details into our application, we will need to inject it into the service collection of our application.
To inject the user context service we use the following in ConfigureServices():
services.AddTransient<IUserContextService, UserContextService>();
Once this is all setup, we have quite a modular, cleaner consumption of the user context properties which can allow us to use this information to provide the testing of user role memberships within our service classes.
For example, we can make the logical tests of role membership within service methods. An example of one of these is shown below:
public async Task SaveBook(BookViewModel vm)
{
if (!this._userContextService.IsAdminUser())
throw new Exception("Only Admin users can create new books!");
_db.Add(vm);
vm.DateCreated = DateTime.Now;
…
}
We have seen from the above how to extract the following useful properties from a user context that contains a bearer token in .NET Core:
- User login name
- User claims
- User roles
We also saw how to encapsulate these user properties and roles within an application.
That is all for today’s post.
I hope you found this post useful and informative.
Andrew Halil is a blogger, author and software developer with expertise of many areas in the information technology industry including full-stack web and native cloud based development, test driven development and Devops.