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:

Top comments (24)

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
 
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
 
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
 
johnsons profile image
johnsons Ellen

I'm thrilled to hear that you found the tutorial on .NET authentication so accessible and helpful! Your enthusiasm is the kind of encouragement that fuels my passion for teaching and sharing knowledge. Rest assured, DGME Login Employee I have no plans to stop creating tutorials. Keep an eye out for more content, and please don't hesitate to reach out if there's a specific topic you'd like to see covered. Happy coding! 😊👨‍💻

Collapse
 
markedmonds78 profile image
Academic Assignments

Incredible. All the tutorials or informations you share are so increadibly helpful. Cant thank you enough for this .

However, our mission is to help students academically by catering professionals and best dissertation help . All your dissertation will be written ethically and professionally by top writers to help you achieve higher grades.

Collapse
 
alexnew3334 profile image
Alex John

This is an excellent, thorough tutorial on implementing JWT authentication in an ASP.NET Core API. The step-by-step instructions make it easy to follow along and understand the process. One suggestion to make this an even more well-rounded learning resource would be to include a section on online report writing about the finalized API. For example, provide some guidance on how developers could summarize the API architecture, endpoints, and overall functionality through online report writing. This would reinforce key learnings and help cement developers' understanding. Overall though, a very helpful guide to securing APIs with JWT and role-based authorization.

Collapse
 
wahidustoz profile image
Wahid Abduhakimov

Great! Easy to follow and precise.

Collapse
 
liteblueinsider1 profile image
kevien33333

"I'm presently addressing a technical problem related to my web API. It operates flawlessly during Postman testing, but when accessed through a URL, I'm experiencing 404 and 401 errors. While I'm actively working on resolving this issue, I'd like to extend an invitation for you to explore DGME, where I'm exploring similar subjects. You might encounter a 404 error there, which I'm actively working to rectify. Your input and experiences in dealing with such issues would be highly valuable and appreciated USPS Liteblue Login

Collapse
 
dgmegeneral profile image
Dgme • Edited

"I'm currently dealing with a technical issue involving my web API. It functions perfectly when tested via Postman, but when accessed through a URL, I'm encountering 404 and 401 errors. While I'm actively troubleshooting this, I'd like to invite you to visit the DGME where I'm delving into similar topics. You may come across a 404 error, which I'm in the process of resolving. I'd greatly appreciate your insights and experiences regarding such matters."

Collapse
 
heather138 profile image
Heather Anderson

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

Collapse
 
bretmooney profile image
Breet Mooney

I'm facing a technical issue where my web API works seamlessly via Postman but encounters 404 and 401 errors when accessed through a URL. While I troubleshoot this, I'd appreciate your visit to DGME Login Portal, where I explore similar topics. You might encounter a 404 error, which I'm in the process of fixing. Your thoughts and experiences on such matters would be very welcome

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

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! :-*

Collapse
 
jkangular profile image
JKAngular

Github url not working

Collapse
 
heather138 profile image
Heather Anderson

You are right.
Visit Liteblue official for more information.