DEV Community

Mohamad Lawand
Mohamad Lawand

Posted on • Edited 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:

Top comments (51)

Collapse
 
albertbarkley profile image
Albert Barkley

Your guide for Rest API authentication is the one I was looking for since last month as I was working on my computer dissertation and there were some unforeseen Rest API authentication errors. However, one of my friends suggested me to get computer dissertation help from someone so I got my all issues resolved. Still, it is worth to read your guide for my viva preparation.

Collapse
 
prostand profile image
Patterson

This method is widely used due to its efficiency in handling user sessions and maintaining security without the need for server-side session storage. Learn more about Ky Arrest Records online

Collapse
 
nabiullah748957 profile image
Nabi Ullah

Thanks for sharing the information, I usually see public arrest records at
Arrests org.

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
 
johnflee profile image
JohnFlee

It seems you're encountering a dependency injection issue in an ASP.NET Core project. The error message indicates that the UserManager service cannot be resolved when trying to activate AuthManagmentController. This usually happens when the necessary service is not registered in the Dependency Injection (DI) container www-ncedclouds.com

Collapse
 
elvinew profile image
Thome Jhonsan

The step-by-step approach ensures that developers, whether beginners or experienced, can grasp the concepts and apply them effectively, leading to robust authentication practices that safeguard user data and maintain application integrity. Visit website

Collapse
 
prestonmiller profile image
Eliza Beth

This knowledge not only enhances application security but also prepares developers for future challenges in web application development. Learn more about GMGlobalConnect portal online

Collapse
 
elvinew profile image
Thome Jhonsan

Step-by-step implementation typically involves setting up token generation, configuring middleware for JWT validation, and ensuring proper token storage on the client-side. Learn more about website

Collapse
 
louisarthur1 profile image
louis arthur

Ah, I am glad I don’t have to get into this technical stuff because I can’t get my head around it. I provide Dissertation Writing Services London- based and I just have to research and write. I guess my brother may find this post useful as he is into coding and development. I am sharing this link with him.

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
 
elvinew profile image
Thome Jhonsan

ASP.NET Core 5 provides a robust framework for building REST APIs, and JWT (JSON Web Token) is a popular approach for handling authentication. Dr. Mohammad Raza Podiatrist McKinney and Plano, Texas

Collapse
 
prestonmiller profile image
Eliza Beth

Step-by-step guidance in this area is invaluable, especially as it clarifies setting up the middleware, configuring authentication schemes, and generating tokens. Visit now

Some comments may only be visible to logged-in visitors. Sign in to view all comments.