Plain and simple token authentication in ASP.NET Core 2.2

I am going to go over using JWTBearer authentication and using your own custom token-based authentication in Core 2.2.

I am going to go over using JWTBearer authentication and using your own custom token-based authentication in Core 2.2.

This is a follow up to a previous post that was for core 1.x where I already took care of figuring out the hard parts. It just needed to be updated for Core 2.2, it took a while for me to get around to it. https://www.frakkingsweet.com/plain-and-simple-token-authentication-in-asp-net-core/

For this post I am keeping things simple, I started with a clean Dot Net Core 2.2 web application with authentication that is stored in the application, which really means using the Entity Framework.

There were 2 changes from my previous post that needed to be worked through.

  1. The place you register the authentication pipeline moved from Configure to ConfigureServices.
  2. With the move you don't have access to the final ServiceProvider which is needed to gain access to the UserManager and other services.

Both issues are pretty simple to resolve. The fix for the first was to use the AddJwtBearer extension method. The fix for the second is to use the HttpContextAccessor. It would have been nice if the framework would have passed the HttpContext into the ValidateToken method.

If you just want to go to the code and see what was needed to do it:

Full source: https://github.com/veccsolutions/Vecc.TokenAuthentication

Only the commit containing the changes: https://github.com/veccsolutions/Vecc.TokenAuthentication/commit/43ac7c02cec9dbd42287172a89b0c9f37accc9dd

In my example I added 3 additional actions to the home controller, ListLogins, AddToken and TestToken.

  • ListLogins will return a JSON object containing all of login entries in the database for the currently logged in user.
  • AddToken will add a new login with the optionally specified value. If no value is specified, a new GUID will be generated and that will be used. It returns the Authorization header that you can copy/paste.
  • TestToken is configured to use the Bearer token and dumps out a JSON object of the user that owns the token. That user is set through the authentication pipeline to prove it all works.

Now to go into detail on how I implemented the ISecurityTokenValidator.

You don't have dependency injection handed to you like you do everywhere else. To gain access to the services you will need to use the HttpContextAccessor.

For my implementation, I used the multiple Logins feature of the identity framework from Microsoft. It comes with the standard framework and handles everything I needed without any extra code or libraries. In order to use it, I need the UserManager<IdentityUser> service. Calling the FindUserByLoginAsync got me the user.

Once I have the user, I need to create the claims principal for that user. There is another service for that, the IUserClaimsPrincipalFactory<IdentityUser>. Calling the CreateAsync method will handle that.

Now that we have a ClaimsPrincipal from that token we need to create our security token object. It has some built in security in the class you need to inherit so you'll need to set the SecurityKey and SigningKey properties to a security key. Microsoft provides a SymmetricSecurityKey that handles that, so that's what I used.

Because the ValidateToken method is not async I used Task.WaitAll and run the async code in there.

            Task.WaitAll(Task.Run(async () =>
            {
                var applicationUser = await userManager.FindByLoginAsync(LoginProvider, securityToken.Substring(TokenPrefix.Length));

                if (applicationUser == null)
                {
                    // In Core 2.2 you need to throw an exception to signal that validation failed, core 1.x was return null.
                    throw new SecurityTokenException("Token not recognized");
                }

                result = await principalFactory.CreateAsync(applicationUser);
                token = new CustomSecurityToken(applicationUser.Id, LoginProvider,
                                                new SymmetricSecurityKey(this._signingSecurityKey),
                                                new SymmetricSecurityKey(this._signingSecurityKey),
                                                DateTime.MinValue,
                                                DateTime.MaxValue);
            }));

The underlying handler uses exceptions as the way to skip validators that failed for any reason. If you return null it thinks it's the token was validated successfully and that a claims principal was created. It has to be an exception. Here's the chunk of code in the underlying framework that handles it: https://github.com/aspnet/AspNetCore/blob/69feac263332424ceccfb673cbf205e31855abdc/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs#L108-L129

Here is an example raw request I made to my example project using Fiddler:

GET https://localhost:44314/Home/TestToken HTTP/1.1
User-Agent: Fiddler
Host: localhost:44314
Authorization: Bearer Vecc-7353a697-c8dd-4d24-b46b-0b3c29e4ca09

That's really all there is too it. It's not very difficult once you go diving through the security framework and get lost in there for a while.

Again, the links to the source code:

Full source: https://github.com/veccsolutions/Vecc.TokenAuthentication

Only the commit containing the changes: https://github.com/veccsolutions/Vecc.TokenAuthentication/commit/43ac7c02cec9dbd42287172a89b0c9f37accc9dd