Authentication with ASP.NET Identity and JWT in the same project


This article describes how to add JWT Tokens to a project that is already configured for using ASP.NET Identity. This will allow you to build a website that will also support a mobile app or SPA pointing to API's in the same solution.

To test the implementation it is a good idea to setup up a test API controller that just returns a couple of values. Here is an example of the base test controller functionality.

[Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public List<string> GetValues()
        {
            return new List<string>() { "Value1", "Value2" };
        }
    }

Since this doesn't have any authentication configured yet, you can test this in a standard browser and you should receive the list of values on the page.

Next you will want to add the [Authorize] attribute to the controller and test that you are redirected to the identity login.

The next step is to add the System.IdentityModel.Tokens.Jwt to your project from NuGet.

After installing the NuGet Package, you will want to create a model in your models directory and add the following constants.

class JwtConfigConstants
    {
        public const string Issuer = "Kartech"; 
        public const string Audience = "ApiUser";
        public const string Key = "KT(SecureKeyString)";
    
    	public const string AuthSchemes = "Identity.Application," + JwtBearerDefaults.AuthenticationScheme;
    }
  • The Issuer constant is who your are it can be your company or app name.

  • The Audience is who is going to consume your API's. You can create multiple models to support different audiences.

  • The Key is your secure key for generating the token. This key needs to be at least 16 characters long and should be hidden from the users.

Now you will want to create a view model for users to pass their UserName and Password to the Identity system to get their token. In your ViewModels folder create a class called JwtIdentityViewModel with the following code.

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

    }

Next you will want to create an API account controller with a Create method to generate the token. Create the controller and add the following code.

[Route("api/[controller]")]
    [ApiController]
    public class AccountController : ControllerBase
    {
        private readonly SignInManager<ApplicationUser> _signInManager;
        private readonly UserManager<ApplicationUser> _userManager;        
        private readonly ILogger<AccountController> _logger;

        public AccountController(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager,ILogger<AccountController> logger)
        {
            _signInManager = signInManager;
            _userManager = userManager;
            _logger = logger;
        }
        
        [HttpPost]
        public async Task<IActionResult> Create([FromBody] JwtIdentityViewModel model)
        {
            if (ModelState.IsValid)
            {
                var user = await _userManager.FindByNameAsync(model.UserName);

                var signInResult = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);

                if (signInResult.Succeeded)
                {
                    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfigConstants.Key));
                    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

                    var claims = new[]
                    {
                        new Claim(JwtRegisteredClaimNames.Sub,model.UserName),
                        new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()),
                        new Claim(JwtRegisteredClaimNames.UniqueName, model.UserName)
                    };

                    var token = new JwtSecurityToken(
                       JwtConfigConstants.Issuer,
                       JwtConfigConstants.Audience,
                       claims,
                       expires: DateTime.UtcNow.AddMinutes(30),
                       signingCredentials: creds);

                    var results = new
                    {
                        token = new JwtSecurityTokenHandler().WriteToken(token),
                        expiration = token.ValidTo
                    };

                    return Created("", results);
                }
                else
                {
                    return BadRequest();
                }
            }
            else
            {
                return BadRequest();
            }

        }
    }

This code uses the SignInManager and UserManager from Asp.Net Core identity to find the user and verify the password. If the password is verified, it creates a key using the key string from the constants class we created earlier. Once that key is created, it is used to create the signing credentials for the Token using the HmacSha256 encryption. Next we build the claims that will be loaded into the token. This is a simple authentication example, so it just contains the sub, jti, and unique name. If you wanted to do authorization as well, you could populate the claim with roles and other policies. With the credentials and claims created, we then build the token using the constants and adding a 30 minute expiration. With the token built, we return the token with the expiration in an anonymous object to the requester.

You can test this controller using postman. First you will need to add a header

KEY : Content-Type
VALUE : application/json

Next you will need to add a json object to the body that contains the UserName and Password. For this to work you should have already created a user with the web based Identity registration.

{
    "UserName":"TestUser",
    "Password":"TestPass123"
}

Then enter the the URL into the search bar, and select the post method. When you type send, you should get a json response back with the token and expiration in the body.

Next we need to add the JWT authentication to the startup.cs file in the root of your site. To do this insert the following code

services.AddAuthentication()
                .AddJwtBearer(cfg =>
                {
                    cfg.TokenValidationParameters = 
                        new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                    	{
                        ValidIssuer = JwtConfigConstants.Issuer,
                        ValidAudience = JwtConfigConstants.Audience,
                        IssuerSigningKey = 
                            new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtConfigConstants.Key))
                    	};
                });

This example is using Asp.Net Core 3.1. If you are using an earlier version, you will need to chain the AddAuthentication with AddCookies. In 3.1 the cookies are already configured if you use the Microsoft template for generating your site.

Now we should be able to edit the [Authorize] attribute on our ValuesController to include the authentication schemes that we configured in the JwtConfigConstants. The controller should look like this code

[Route("api/[controller]")]
    [Authorize(AuthenticationSchemes =JwtConfigConstants.AuthSchemes)]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        [HttpGet]
        public List<string> GetValues()
        {
            return new List<string>() { "Value1", "Value2" };
        }
    }

With Postman you can test the authorization. Create a new tab and connect to the URL to the values controller, you should get a 401 unauthorized. Now go back to the login where you got a token back and copy the token text. Now go the new tab and click the headers tab at the top of the page and set the KEY = Authorization. In the VALUE field type Bearer followed by a space and then your token. It should look something like this

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqa2Fybm9wcCIsImp0aSI6IjFiNGIyOWExLThhNWUtNDAyYS1hZDY0LTBiMjI1ZDNjYjg2YiIsInVuaXF1ZV9uYW1lIjoiamthcm5vcHAiLCJleHAiOjE1OTQ3ODk5OTgsImlzcyI6IkthcnRlY2giLCJhdWQiOiJBcGlVc2VyIn0.beFGiAWpPxQa6AFTk3iJ_tNbIrgzhZtsSkYsZ2aIveA

Now you should be able to hit send in Postman and retrieve the values from the web API.

With this configuration, you can now access the web API's with the current website using cookie authentication which is useful for JavaScript and jQuery functionality in an MVC or Razor Page implementation, or you can use a framework like Angular or React to access the API's using JWT. You can also use JWT to access the API's for native mobile apps or hybrid apps built on a framework like Ionic.

Comments

Popular posts from this blog

Asp.Net Core with Extended Identity and Jwt Auth Walkthrough

File Backups to Dropbox with PowerShell

Dynamic Expression Builder with EF Core