DEV Community

Mohamad Lawand
Mohamad Lawand

Posted on • Updated on

Asp Net Core 5 Rest API Authentication with JWT Step by Step

In this post i will be showing you How to add JWT authentication to our Asp.Net Core REST API

Some of the topics we will cover are registration, login functionalities and utilising JWTs ("Json Web Tokens") and Bearer authentication.

You can also watch the full step by step video on YouTube:

As well download the source code:
https://github.com/mohamadlawand087/v7-RestApiNetCoreAuthentication

This is Part 2 of API dev series you can check the different parts by following the links:

We will be basing our current work on our previous Todo REST API application that we have created in our last article (https://dev.to/moe23/asp-net-core-5-rest-api-step-by-step-2mb6).

You can follow along by either going through that article and building the application with me as we go or you can get the source code from github, https://github.com/mohamadlawand087/v6-RestApiNetCore5.

Alt Text

Once we have our code ready lets get started.

The first thing we need to install some package to utilise authentication

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer 
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore 
dotnet add package Microsoft.AspNetCore.Identity.UI 
Enter fullscreen mode Exit fullscreen mode

then we need to do is we need to update our appsettings.json, in our appsettings we will need to add a JWT settings section and within that settings we need to add a JWT secret

"JwtConfig": {
    "Secret" : "ijurkbdlhmklqacwqzdxmkkhvqowlyqa"
  },
Enter fullscreen mode Exit fullscreen mode

In order for us to generate our secret we are going to use a free web tool to generate a random 32 char string https://www.browserling.com/tools/random-string

After adding the randomly generate 32 char string in our app settings now we need to create a new folder in our root directory called configuration.

Inside this configuration folder we will create a new class called JwtConfig

public class JwtConfig
{
    public string Secret { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to update our startup class, inside our ConfigureServices method we need to add the below in order to inject our JwtConfiguration in our application

services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
Enter fullscreen mode Exit fullscreen mode

Adding these configuration in our startup class register the configurations in our Asp.Net core middlewear and in our IOC container.

The next step is adding and configuring authentication in our startup class, inside our ConfigureServices method we need to add the following

// within this section we are configuring the authentication and setting the default scheme
services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt => {
    var key = Encoding.ASCII    .GetBytes(Configuration["JwtConfig:Secret"]);

    jwt.SaveToken = true;
    jwt.TokenValidationParameters = new TokenValidationParameters{
        ValidateIssuerSigningKey= true, // this will validate the 3rd part of the jwt token using the secret that we added in the appsettings and verify we have generated the jwt token
        IssuerSigningKey = new SymmetricSecurityKey(key), // Add the secret key to our Jwt encryption
        ValidateIssuer = false, 
        ValidateAudience = false,
        RequireExpirationTime = false,
        ValidateLifetime = true
    }; 
});

services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApiDbContext>();
Enter fullscreen mode Exit fullscreen mode

After updating the ConfigureServices we need to update the Configure method by adding authentication

app.UseAuthentication();
Enter fullscreen mode Exit fullscreen mode

Once we add the configurations we need to build the application to see if everything is still building as it should.

dotnet run
dotnet build
Enter fullscreen mode Exit fullscreen mode

The next step is to update our ApiDbContext to take advantage of the Identity provider that Asp.Net provide for us, will navigate to our ApiDbContext in the Data folder and we update the ApiDbContext class as the following

public class ApiDbContext : IdentityDbContext
Enter fullscreen mode Exit fullscreen mode

by inheriting from IdentityDbContext instead of DbContext, EntityFramework will know that we are using authentication and it will build the infrastructure for us to utilise the default identity tables.

To Generate the identity tables in our database we need to prepare migrations scripts and run them. to do that inside the terminal we need to type the following

dotnet ef migrations add "Adding authentication to our Api"
dotnet ef database update
Enter fullscreen mode Exit fullscreen mode

Once our migrations is completed we can open our database app.db with Dbeaver and we can see that our identity tables has been created for us by Entity Framework

The next step will be to setup out controllers and build the registration process for the user. Inside out controller folder will need to create a controller and our DTOs (data transfer objects).

Will start by adding a new folder called Domain in our root directory, and we add a class called AuthResult

public class AutResult
{
    public string Token {get;set;}
    public bool Result { get; set; }
        public List<string> Errors { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Will start by adding some folders to organise our DTOs, inside the Models folder will add a folder called DTO and within the DTO folder will create 2 folders Requests/Responses

We need to add the UserRegistrationRequestDto which will be used by our registration action in the Controller. Then will navigate to Models/DTO/Requests and add a new class called UserRegistrationRequestDto

Models/Dto/Requests/UserRegistrationRequestDto.cs

// For simplicity we are only adding these 3 feilds we can change it and make it as complex as we need
public class UserRegistrationRequestDto
{
    [Required]
    public string Name { get; set; }
    [Required]
    public string Email { get; set; }
    [Required]
    public string Password { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Model/Dto/Response/RegistrationResponse.cs

// We are inheriting from AuthResult class
public class RegistrationResponse : AuthResult
{

}
Enter fullscreen mode Exit fullscreen mode

Now we need to add our user registration controller, inside our controller folder we add a new class we call it AuthManagementController and we update it with the code below

[Route("api/[controller]")] // api/authmanagement
[ApiController]
public class AuthManagementController : ControllerBase
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly JwtConfig _jwtConfig;

    public AuthManagementController(UserManager<IdentityUser> userManager, IOptionsMonitor<JwtConfig> optionsMonitor)
    {
        _userManager = userManager;
        _jwtConfig = optionsMonitor.CurrentValue;
    }

    [HttpPost]
    [Route("Register")]
    public async Task<IActionResult> Register([FromBody] UserRegistrationRequestDto user)
    {
        // Check if the incoming request is valid
        if(ModelState.IsValid)
        {
            // check i the user with the same email exist
            var existingUser = await _userManager.FindByEmailAsync(user.Email);

            if(existingUser != null) 
            {
                return BadRequest(new RegistrationResponse() {
                                        Result = false,
                                        Errors = new List<string>(){
                                            "Email already exist"
                                        }});
            }

            var newUser = new IdentityUser(){Email = user.Email, UserName = user.Email};
            var isCreated = await _userManager.CreateAsync(newUser, user.Password);
            if(isCreated.Succeeded)
            {
                var jwtToken = GenerateJwtToken(newUser);

                return Ok(new RegistrationResponse() {
                        Result = true, 
                        Token = jwtToken
                });
            }

            return new JsonResult(new RegistrationResponse(){
                    Result = false,
                    Errors = isCreated.Errors.Select(x => x.Description).ToList()}
                    ) {StatusCode = 500};
        }

        return BadRequest(new RegistrationResponse() {
                                        Result = false,
                                        Errors = new List<string>(){
                                            "Invalid payload"
                                        }});
    }

        private string GenerateJwtToken(IdentityUser user)
    {
        // Now its ime to define the jwt token which will be responsible of creating our tokens
        var jwtTokenHandler = new JwtSecurityTokenHandler();

        // We get our secret from the appsettings
        var key = Encoding.ASCII.GetBytes(_jwtConfig.Secret);

        // we define our token descriptor
            // We need to utilise claims which are properties in our token which gives information about the token
            // which belong to the specific user who it belongs to
            // so it could contain their id, name, email the good part is that these information
            // are generated by our server and identity framework which is valid and trusted
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new []
            {
                new Claim("Id", user.Id),
                new Claim(JwtRegisteredClaimNames.Sub, user.Email),
                new Claim(JwtRegisteredClaimNames.Email, user.Email),
                // the JTI is used for our refresh token which we will be convering in the next video
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
            }),
            // the life span of the token needs to be shorter and utilise refresh token to keep the user signedin
            // but since this is a demo app we can extend it to fit our current need
            Expires = DateTime.UtcNow.AddHours(6),
            // here we are adding the encryption alogorithim information which will be used to decrypt our token
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature)
        };

        var token = jwtTokenHandler.CreateToken(tokenDescriptor);

        var jwtToken = jwtTokenHandler.WriteToken(token);

        return jwtToken;
    }
}
Enter fullscreen mode Exit fullscreen mode

Once we finish the registration action we can now test it in postman and get the jwt token

So the next step will be creating the user login request.

public class UserLoginRequest
{
    [Required]
    public string Email { get; set; }
    [Required]
    public string Password { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

After that we need to add our login action in the AuthManagementControtller

[HttpPost]
[Route("Login")]
public async Task<IActionResult> Login([FromBody] UserLoginRequest user)
{
    if(ModelState.IsValid)
    {
        // check if the user with the same email exist
        var existingUser = await _userManager.FindByEmailAsync(user.Email);

        if(existingUser == null) 
        {
            // We dont want to give to much information on why the request has failed for security reasons
            return BadRequest(new RegistrationResponse() {
                                    Result = false,
                                    Errors = new List<string>(){
                                        "Invalid authentication request"
                                    }});
        }

        // Now we need to check if the user has inputed the right password
        var isCorrect = await _userManager.CheckPasswordAsync(existingUser, user.Password);

        if(isCorrect)
        {
            var jwtToken = GenerateJwtToken(existingUser);

            return Ok(new RegistrationResponse() {
                    Result = true, 
                    Token = jwtToken
            });
        }
        else 
        {
             // We dont want to give to much information on why the request has failed for security reasons
            return BadRequest(new RegistrationResponse() {
                                    Result = false,
                                    Errors = new List<string>(){
                                         "Invalid authentication request"
                                    }});
        }
    }

    return BadRequest(new RegistrationResponse() {
                                    Result = false,
                                    Errors = new List<string>(){
                                        "Invalid payload"
                                    }});
}
Enter fullscreen mode Exit fullscreen mode

now we can test it out and we can see that our jwt tokens has been generated successfully, the next step is to secure our controller, to do that all we need to do is add the Authorise attribute to the controller

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")] // api/todo
[ApiController]
public class TodoController : ControllerBase
Enter fullscreen mode Exit fullscreen mode

And now if we test it we are not able to execute any request since we are not authorised, in order for us to send authorised requests we need to add the authorisation header with the bearer token so that Asp.Net can verify it and give us permission to execute the actions

Thank you for taking the time and reading the article

This is Part 2 of API dev series you can check the different parts by following the links:

Discussion (8)

Collapse
taylorwatson99 profile image
taylorwatson99

Thank you for sharing this code step by step. It became very easy to understand. I have to do a custom HND assignment, and I am taking inspiration from here to do it.

Collapse
hamidrezashahnazari profile image
HamidrezaShahnazari

hi mohamad,special thanks to you because of great toturial ,i have this error and i dont know how to solve it .
Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' while attempting to activate 'Accounting.Service.Controllers.AuthManagmentController'.
my project is exactly like what you did

Collapse
theaciday profile image
theaciday

You need to add Dependency Injection to your proj

Collapse
wahidd profile image
David Wahid

Great! Easy to follow and precise.

Collapse
mafeof profile image
María Orozco

Hello, thanks for the step by step, it is very clear. A question, as I enter or validate that the token is correct to be able to consult the methods since they give me error 401

Collapse
moe23 profile image
Mohamad Lawand Author

Thank you, for your comment did you check the expiry of the token

Collapse
mafe__fd386455aea20deea0e profile image
Mafe

Hello, I have the same case but my token is valid and also I have another problem, loading my web api from postman works perfect but to consume it from a url it gives me an error, sometimes a 404 error and 401 others

Collapse
antonpodkur profile image
Anton Podkur

Super cool tutorial!!! It is really easy to get into dotnet auth following it. I am really grateful! Do not stop doing tutorials, pls! :-*