###### tags: `Back-end`,`Restful API`,`ASP.NET CORE`,`Titanium`,`Security`
# Week 08/09 - Authentication & Authorization
## Introduction
This session will cover the essentials of JWT (JSON Web Tokens) for authentication in a .NET Core 8 RESTful API, Will use EF Identity package, diving into how JWT works, its structure, best practices, and how to implement it with middleware and role-based route protection.
Authentication is the process of verifying who a user is. In a RESTful API, protecting endpoints ensures only authorized users can access certain data or perform specific actions.
## Identity EF Package and Its Role
ASP.NET Core Identity provides the tools to handle authentication and authorization. The Identity framework, when combined with Entity Framework Core (EF Core), offers built-in database support for managing user data.
Supports secure password storage, user management, roles, and claims-based authentication.
## Installing and Setting Up ASP.NET Core Identity
### Install the required NuGet packages:
`dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
`
### Configure services in Program.cs:
```csharp!
builder.Services.AddDbContext<MainAppContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddIdentity<User, IdentityRole<int>>()
.AddEntityFrameworkStores<MainAppContext>()
.AddDefaultTokenProviders();
```
### Extending IdentityUser with Integer Primary Key
```csharp!
public class ApplicationUser : IdentityUser<int>
{
// Additional custom properties can go here
}
```
### Update ApplicationDbContext to use IdentityDbContext:
```csharp!
public class MainAppContext : IdentityDbContext<ApplicationUser, IdentityRole<int>, int>
{
public MainAppContext(DbContextOptions<MainAppContext> options)
: base(options) { }
}
```
### Database Migration and Update
Use the following commands to apply migrations and update the database:
```
dotnet ef migrations add IdentityMig
dotnet ef database update
OR
Add-Migration IdentityMig
Update-Database
```
## Implementing an Authentication Controller (AuthController)
The AuthController will include a Login endpoint to authenticate users by checking their credentials.
```csharp!
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly JwtService _jwtService;
public AuthController(UserManager<ApplicationUser> userManager, JwtService jwtService)
{
_userManager = userManager;
_jwtService = jwtService;
}
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] AuthRequest model)
{
var user = await _userManager.FindByNameAsync(model.Username);
if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
{
var role = (await _userManager.GetRolesAsync(user)).FirstOrDefault();
var token = _jwtService.GenerateJWT(user, role);
return Ok(new { Token = token });
}
return Unauthorized();
}
}
```
DTO for Login:
```csharp!
public class AuthRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
```
## JWT
JWT is a secure, compact token format that’s commonly used to manage authentication between a client and server. Once a user logs in, the server generates a JWT that’s sent to the client. This token, which is self-contained and includes claims about the user (like roles and permissions), enables the client to make authenticated requests without re-authenticating.
### JWT Structure
A JWT has three main parts:
* Header: Defines the token type and the signing algorithm (e.g., HS256).
* Payload: Contains claims (statements about the user, such as username, roles, exp for expiry, etc.).
* Signature: Ensures the token's integrity by signing the encoded header and payload with a secret key.
The format of a JWT is:
`{Header}.{Payload}.{Signature}`
### Install JwtBearer for JWT implementation
Install JwtBearer package from Nuget Manager.
### Setup JWT
In ConfigureServices, add authentication and configure JWT bearer settings. Make sure to inject UserManager and SignInManager for user authentication.
```csharp
public void ConfigureServices(IServiceCollection services)
{
// Add Identity and configure it
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// JWT Authentication configuration
var jwtSettings = Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"],
ValidAudience = jwtSettings["Audience"],
IssuerSigningKey = new SymmetricSecurityKey(key)
};
});
services.AddControllers();
}
```
Jwt options setup:
```csharp
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "yourapi.com",
ValidAudience = "yourapi.com",
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("YourSecretKey"))
};
});
```
### Implementing JwtService
```csharp
public class JwtService
{
private readonly IConfiguration _config;
public JwtService(IConfiguration config)
{
_config = config;
}
public string GenerateJWT(ApplicationUser user, string role)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _config["Jwt:Issuer"],
audience: _config["Jwt:Audience"],
claims: new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(ClaimTypes.Role, role)
},
expires: DateTime.Now.AddMinutes(Convert.ToDouble(_config["Jwt:ExpireInMinutes"])),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
```
Register JwtService in Program.cs:
```jsonld
"Jwt": {
"Key": "asdkjkejr##$@daslkw[efkf]fadirai",
"Issuer": "Titanium",
"Audience": "Trainers.Trainees",
"ExpireInMinutes": 30
}
```
### Testing the Login Endpoint
Use Postman or a similar tool to send a POST request to /api/auth/login with the correct username and password. If successful, a JWT token will be returned in the response.
### Enable Jwt Middleware to protect the endpoints
Middleware for extracting and validating Jwt Token
```csharp!
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
// Add Authentication and Authorization middleware
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
```
### Using [Authorize] Attribute to Protect Endpoints
Apply [Authorize] on controllers or actions to restrict access to authenticated users:
```csharp
[Authorize]
[ApiController]
[Route("api/[controller]")]
public class ProgramsController : ControllerBase
{
// Only accessible to authenticated users
}
```
Checking controller:
```csharp=
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class SampleController : ControllerBase
{
[HttpGet]
public IActionResult GetSecureData()
{
return Ok("This is a secure endpoint");
}
}
```
You can specify roles if you need role-based authorization:
```csharp!
[Authorize(Roles = "Admin")]
public class AdminController : ControllerBase
{
[HttpGet]
public IActionResult GetAdminData()
{
return Ok("This is an Admin-only endpoint");
}
}
```
## Implementing Role-Based Access Control
```csharp
[ApiController]
[Route("api/[controller]")]
public class RolesController : ControllerBase
{
private readonly RoleManager<ApplicationRole> _roleManager;
public RolesController(RoleManager<ApplicationRole> roleManager)
{
_roleManager = roleManager;
}
[HttpPost("create")]
public async Task<IActionResult> CreateRole([FromBody] string roleName)
{
var result = await _roleManager.CreateAsync(new ApplicationRole { Name = roleName });
return result.Succeeded ? Ok() : BadRequest(result.Errors);
}
[HttpGet("getAll")]
public IActionResult GetAllRoles()
{
var roles = _roleManager.Roles.ToList();
return Ok(roles);
}
}
```
UsersController to Assign Roles:
```csharp
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
public UsersController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpPost("assignRole")]
public async Task<IActionResult> AssignRole(string userName, string roleName)
{
var user = await _userManager.FindByNameAsync(userName);
if (user == null) return NotFound();
var result = await _userManager.AddToRoleAsync(user, roleName);
return result.Succeeded ? Ok() : BadRequest(result.Errors);
}
}
```