Implementation of JWT Authentication in ASP NET Core with Identity and EF Core
Mbark Belkhdar February 25, 2023
We talked before about :
What is JWT?
What is JWT Bearer in ASP.Net Core?
How to Generate a JWT in ASP.NET Core API with Identity and EF Core?
In this article, we will take a complete example of using JWT in ASP.NET Core with Identity and EF Core.
Note: It is necessary to read the JWT articles I mentioned above, as well as having a basic understanding of ASP.NET
Core
with Identity
and Entity Framework Core (EF Core)
, and also a basic knowledge of Angular
. All of the aforementioned basic understanding is required to fully comprehend this article.
Setting up the Project
To get started, create a new ASP.NET Core web application and select the "Web API" template. Then, install the following packages:
-
Microsoft.AspNetCore.Identity
-
Microsoft.AspNetCore.Authentication.JwtBearer
-
Microsoft.EntityFrameworkCore
-
Microsoft.EntityFrameworkCore.SqlServer
-
Microsoft.AspNetCore.Identity.EntityFrameworkCore
-
Microsoft.EntityFrameworkCore.Design
We will be using SQL Server as the database for this example, but EF Core supports many other database providers as well.
Creating the Student Entity
Next, create a new folder called "Entities" and add the following class for the Student entity:
public class Student
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
Then, create a new folder called "Data" and add a new class called "ApplicationDbContext" that inherits from IdentityDbContext
and also has a DbSet for the Student entity.
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
public DbSet Students { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=JWTExample;Trusted_Connection=True;");
}
}
Configuring JWT Authentication
In the Startup
class, add the following code to the ConfigureServices
method to configure JWT authentication:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "yourdomain.com",
ValidAudience = "yourdomain.com",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yoursecretkey"))
};
});
Don't forget to replace "yourdomain.com" and "yoursecretkey" with the actual values for your application.
Creating the StudentController
Create a new folder called "Controllers" and add a new class called "StudentController" that inherits from ControllerBase
and has the following actions:
[HttpPost("register")]
public async Task Register([FromBody] Student student)
{
// create user
var user = new IdentityUser { UserName = student.Email, Email = student.Email };
var result = await _userManager.CreateAsync(user, "P@ssw0rd");
if (!result.Succeeded)
{
return BadRequest();
}
// add student to db
_dbContext.Students.Add(student);
await _dbContext.SaveChangesAsync();
// generate jwt token
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, student.Email),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: "yourdomain.com",
audience: "yourdomain.com",
claims: claims,
expires: DateTime.UtcNow.AddMinutes(30),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes("yoursecretkey")), SecurityAlgorithms.HmacSha256)
);
// return jwt token
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
This action will handle the registration of new students. It first creates a new user with the Identity framework, then adds the student to the database, and finally generates a JWT token and returns it to the client.
Using the Authorize
Attribute
In order to restrict access to a specific action, you can use the Authorize
attribute. This attribute can be applied to a controller class or to an individual action method. When applied to a controller class, all actions within that controller will be protected by the authorization rules. When applied to an action method, only that specific action will be protected.
For example, if you have an action in your StudentController
that returns a list of students, you can use the Authorize
attribute to restrict access to that action to only authenticated users:
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
This action will now only be accessible to users who have been authenticated and have a valid JWT token. If an unauthenticated user attempts to access this action, they will receive a 401 Unauthorized
response.
You can also use the Authorize
attribute with roles or policies.For example, if you have a role called "admin", you can use the Authorize
attribute with the role name to allow only users with "admin" role to access this action.
[Authorize(Roles = "admin")]
[HttpGet("students")]
public IActionResult GetStudents()
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
This way, only users with the "admin" role will be able to access this action. Other users will receive a 401 Unauthorized
response.
By using the Authorize
attribute, you can easily control access to your actions and ensure that only authorized users can access sensitive data or perform specific actions.
Checking and Validating Access to the GetStudents
Action
There are several ways to check and validate access to the GetStudents
action, which is protected by the Authorize
attribute.
Using the User
Property
The User
property of the Controller
or ControllerBase
class provides access to the current user's claims and identity. You can use this property to check if the user is authenticated and to check for specific roles or claims.
For example, you can use the IsInRole
method of the User
property to check if the user has a specific role before returning the students list:
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
if (User.IsInRole("admin"))
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
else
{
return Unauthorized();
}
}
This way, only users with the "admin" role will be able to access this action and see the students list, other users will receive a 401 Unauthorized
response.
You can also use the Claims
property to check for specific claims:
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
if (User.Claims.Any(c => c.Type == ClaimTypes.Email && c.Value == "admin@example.com"))
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
else
{
return Unauthorized();
}
}
This way, only users with claim type "email" and value "admin@example.com" will be able to access this action and see the students list, other users will receive a 401 Unauthorized
response.
Using the IAuthorizationService
You can also use the IAuthorizationService
to check if a user has access to a specific action or resource. This service can be injected into a controller or service and provides methods for checking access and handling authorization failures.
For example, you can use the AuthorizeAsync
method to check if a user has access to the GetStudents
action:
[Authorize]
[HttpGet("students")]
public async Task GetStudents()
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, null, "viewStudentsPolicy");
if (authorizationResult.Succeeded)
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
else
{
return new ChallengeResult();
}
}
This way, the AuthorizeAsync
method will check if the user has the "viewStudentsPolicy" policy, if the user has it, the method will return the students list, otherwise it will return a 401 Unauthorized
response.
Checking and Validating Access to the GetStudents
Action with JWT
When using JSON Web Tokens (JWT) for authentication, you can use the Authorize
attribute to restrict access to the GetStudents
action to only authenticated users who have a valid JWT token. Additionally, you can also use the claims stored in the JWT token to check for specific roles or permissions.
Using the Authorize
Attribute
The Authorize
attribute can be applied to a controller class or to an individual action method. When applied to a controller class, all actions within that controller will be protected by the authorization rules. When applied to an action method, only that specific action will be protected.
For example, you can use the Authorize
attribute to restrict access to the GetStudents
action to only authenticated users:
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
This action will now only be accessible to users who have been authenticated and have a valid JWT token. If an unauthenticated user attempts to access this action, they will receive a 401 Unauthorized
response.
Using Claims from JWT Token
You can also use the claims stored in the JWT token to check for specific roles or permissions. For example, you can check for the role
claim to check if the user has the admin
role:
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
var role = User.Claims.FirstOrDefault(c => c.Type == "role")?.Value;
if (role == "admin")
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
else
{
return Unauthorized();
}
}
This way, only users with the admin
role will be able to access this action and see the students list, other users will receive a 401 Unauthorized
response.
You can also check for any other claim that you've added to the JWT token, for example you can check for a custom claim called permissions
that hold an array of permissions.
[Authorize]
[HttpGet("students")]
public IActionResult GetStudents()
{
var permissions = User.Claims.FirstOrDefault(c => c.Type == "permissions")?.Value;
if (permissions.Contains("view_students"))
{
var students = _dbContext.Students.ToList();
return Ok(students);
}
else
{
return Unauthorized();
}
}
This way, only users with the view_students
permission will be able to access this action and see the students list, other users will receive a 401 Unauthorized
response.
Consuming the Register
Method from an Angular Component
In order to consume the Register
method from an Angular component, you can use the HttpClient
module to make a POST request to the endpoint that the method is exposed on, along with the student's data that you want to register.
First, you need to import the HttpClient
module in your component:
import { HttpClient } from '@angular/common/http';
Then you need to inject it in your component's constructor:
constructor(private http: HttpClient) {}
Then you can use the http.post
method to make a POST request to the endpoint that the Register
method is exposed on, along with the student's data that you want to register.
register(student: Student) {
this.http.post('/api/student/register', student).subscribe(
data => {
console.log(data);
// you can extract the token and expiration time from the data object
// and store it in the local storage
},
error => {
console.log(error);
}
);
}
It's important to note that the above code snippet assumes that the Register
method is exposed on the /api/student/register
endpoint.
It's also important to note that the above code snippet assumes that the student
object passed to the register
method has the same properties that the Student
model in the server side, otherwise the server will not be able to deserialize the student object from the request.
In order to extract the token and expiration time from the response of the register
method and store them in the local storage, you can access the data
object in the subscribe
method and extract the values of the token
and expiration
properties.
You can then use the localStorage.setItem
method to store the token and expiration time in the local storage.
register(student: Student) {
this.http.post('/api/student/register', student).subscribe(
data => {
console.log(data);
const token = data.token;
const expiration = data.expiration;
localStorage.setItem('token', token);
localStorage.setItem('expiration', expiration);
},
error => {
console.log(error);
}
);
}
You can also use the JSON.stringify()
method to store the expiration date as a string, then you can parse it later on.
register(student: Student) {
this.http.post('/api/student/register', student).subscribe(
data => {
console.log(data);
const token = data.token;
const expiration = data.expiration;
localStorage.setItem('token', token);
localStorage.setItem('expiration', JSON.stringify(expiration));
},
error => {
console.log(error);
}
);
}
By storing the token and expiration time in the local storage, you can use them later to authenticate requests and keep the user logged in even after the browser is closed.
It's important to note that the localStorage
is vulnerable to XSS attacks, you should consider using a secure way to store the token like using the HttpOnly
and secure
flags on the cookie or using an alternative storage like sessionStorage
or an in-memory storage.
Consuming the GetStudents
Method from an Angular Component
In order to consume the GetStudents
method from an Angular component, you can use the HttpClient
module to make a GET request to the endpoint that the method is exposed on.
First, you need to import the HttpClient
module in your component:
import { HttpClient } from '@angular/common/http';
Then you need to inject it in your component's constructor:
constructor(private http: HttpClient) {}
Then you can use the http.get
method to make a GET request to the endpoint that the GetStudents
method is exposed on. You can also subscribe to the observable returned by the get
method to handle the response and any errors that may occur.
ngOnInit() {
this.http.get('/api/student/students').subscribe(
data => {
this.students = data;
},
error => {
console.log(error);
}
);
}
It's important to note that the above code snippet assumes that the GetStudents
method is exposed on the /api/student/students
endpoint.
Also, you need to make sure that you are sending the JWT token with the request, this could be done by adding the token in the header of the request.
ngOnInit() {
this.http.get('/api/student/students', {
headers: new HttpHeaders({
'Authorization': `Bearer ${localStorage.getItem('token')}`
})
}).subscribe(
data => {
this.students = data;
},
error => {
console.log(error);
}
);
}
This way, the Authorization
header is added to the request with the token stored in the local storage, this ensures that the request is authorized and the server will return the students list.
You also can handle errors or unauthorized requests by using the catchError
operator and the throwError
function of the rxjs
library
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
ngOnInit() {
this.http.get('/api/student/students', {
headers: new HttpHeaders({
'Authorization': `Bearer ${localStorage.getItem('token')}`
})
}).pipe(
catchError(err => {
if (err.status === 401) {
// handle unauthorized request
} else {
return throwError(err);
}
})
).subscribe(
data => {
this.students = data;
},
error => {
console.log(error);
}
);
}
By consuming the GetStudents
method from an Angular component, you can retrieve the students list and display it in the view. This allows you to create a dynamic and interactive user interface that is updated in real-time with the data from the server.
You can access and download an example project that I personally created to supplement this topic.