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:
- Part 1:https://dev.to/moe23/asp-net-core-5-rest-api-step-by-step-2mb6
- Part 3: https://dev.to/moe23/refresh-jwt-with-refresh-tokens-in-asp-net-core-5-rest-api-step-by-step-3en5
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.
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
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"
},
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; }
}
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"));
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>();
After updating the ConfigureServices we need to update the Configure method by adding authentication
app.UseAuthentication();
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
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
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
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; }
}
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; }
}
Model/Dto/Response/RegistrationResponse.cs
// We are inheriting from AuthResult class
public class RegistrationResponse : AuthResult
{
}
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;
}
}
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; }
}
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"
}});
}
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
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 (47)
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.
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
Thanks for sharing the information, I usually see public arrest records at
Arrests org.
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.
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
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
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
You need to add Dependency Injection to your proj
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
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
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.
This knowledge not only enhances application security but also prepares developers for future challenges in web application development. Learn more about GMGlobalConnect portal online
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! ππ¨βπ»
Some comments may only be visible to logged-in visitors. Sign in to view all comments.