What is Authentication?
A server uses authentication when it needs to know exactly who is accessing its information or site. Similarly, a client utilizes authentication when it needs assurance that the server is the system it claims to be. In authentication, the user or computer must prove its identity to the server or client.
What is Authorization?
Authorization, on the other hand, is a process through which a server determines if the client has permission to use a resource or access a file. It usually links with authentication, granting the server some understanding of the requesting client’s identity. The authentication method required for authorization may vary; while passwords may be necessary in some instances, they may not be required in others.
Need of Authentication and Authorization
Authorization and authentication are fundamental components of ensuring secure access to resources and information in computer systems and networks. They serve distinct yet complementary purposes in safeguarding sensitive data and systems from unauthorised access and misuse.
- Authentication: Authentication is crucial because it verifies the identity of users or systems attempting to access resources or information. Without proper authentication mechanisms in place, there is no reliable way to confirm the identity of individuals or entities accessing a system or network. Authentication prevents unauthorised users from gaining access to sensitive data or performing actions that they are not permitted to do. It ensures accountability by associating actions with specific authenticated identities, thereby enabling traceability and auditing.
- Authorization: Authorization complements authentication by determining the permissions and privileges granted to authenticated users or systems. Once a user or system is authenticated, authorization mechanisms define what actions they are allowed to perform and what resources they can access. Authorization helps enforce the principle of least privilege, ensuring that users or systems only have access to the resources and functionalities necessary for their roles or tasks. This reduces the risk of unauthorised activities, data breaches, and system compromises by limiting the scope of potential harm that authenticated entities can inflict.
JSON Web Token
JWT, or JSON Web Token, is a compact, self-contained means of securely transmitting information between parties as a JSON object. It is commonly used for authentication and authorization in web applications and APIs. JWTs consist of three parts: a header, a payload, and a signature, each encoded as Base64. The header typically specifies the token type and the cryptographic algorithm used to create the signature. The payload contains claims or statements about the user or entity, such as user ID or permissions. The signature is generated by combining the header, payload, and a secret key and can be used to verify the integrity of the token. JWTs are stateless, which makes them efficient for distributed systems, and their compact size makes them suitable for passing between different components of an application.
In its compact form, JSON Web Tokens consist of three parts separated by dots (.), which are:
- Header
- Payload
- Signature
Therefore, a JWT typically looks like the following.
xxxxx.yyyyy.zzzzz
Implementation
Add the following nuget package
Microsoft.AspNetCore.Authentication.JwtBearer
Add these line to appsetting.json
"ConnectionStrings": { "conn": "data source=.;initial catalog=BookStoreBackend;integrated security=True;TrustServerCertificate=True" }, "JWT": { "ValidAudience": "https://localhost:7062", "ValidIssuer": "https://localhost:7062", "Secret": "ByYM000OLlMQG6VVVp1OH7Xzyr7gHuw1qvUC5dcGt3SNM" }
“ConnectionStrings” represents the connection string value to connect with database.
ValidAudience is the valid audience for the app.
ValidIssuer is the issuer of token.
Add a ApplicationUser class
public class ApplicationUser : IdentityUser{ /// <summary> /// Gets or sets the name of the user. Maximum length is 30 characters. /// </summary> [MaxLength(30)] public string? Name { get; set; } }
Add the ApplicationDbContext class
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace BookStore.Data.Models; /// <summary> /// Provides a database context for the BookStore application using Entity Framework Core and Identity Framework. /// </summary> public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { /// <summary> /// Initializes a new instance of the ApplicationDbContext class with the specified DbContext options. /// </summary> /// <param name="options">The options to be used by the DbContext.</param> public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { // The base constructor handles initializing the DbContext with the provided options. } }
Update the code in the Program.cs
using BookStore.Data.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.Text; builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("conn"))); // For Identity builder.Services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Adding Authentication builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) // Adding Jwt Bearer .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidAudience = builder.Configuration["JWT:ValidAudience"], ValidIssuer = builder.Configuration["JWT:ValidIssuer"], ClockSkew = TimeSpan.Zero, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"])) }; }); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
Add a class that will define all the user roles
public static class UserRoles { /// <summary> /// The role name for the administrator. /// </summary> public const string Admin = "Admin"; /// <summary> /// The role name for regular users. /// </summary> public const string User = "User"; }
This class defines the payload model for the registration page.
public class RegistrationModel { [Required(ErrorMessage = "User Name is required")] public string? Username { get; set; } [Required(ErrorMessage = "Name is required")] public string? Name { get; set; } [EmailAddress] [Required(ErrorMessage = "Email is required")] public string? Email { get; set; } [Required(ErrorMessage = "Password is required")] public string? Password { get; set; } }
For LoginModel
using System.ComponentModel.DataAnnotations; namespace BookStore.Data.Models; public class LoginModel { [Required(ErrorMessage = "User Name is required")] public string? Username { get; set; } [Required(ErrorMessage = "Password is required")] public string? Password { get; set; } }
Now to implement registration and login functionality, let’s create authorization service.
IAuthService.cs
public interface IAuthService { Task<(int, string)> Registeration(RegistrationModel model, string role); Task<(int, string)> Login(LoginModel model); }
AuthService.cs
using BookStore.Data.Models; using Microsoft.AspNetCore.Identity; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace BookStore.Api.Services { public class AuthService : IAuthService { private readonly UserManager<ApplicationUser> userManager; private readonly RoleManager<IdentityRole> roleManager; private readonly IConfiguration _configuration; public AuthService(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration) { this.userManager = userManager; this.roleManager = roleManager; _configuration = configuration; } public async Task<(int,string)> Registeration(RegistrationModel model,string role) { var userExists = await userManager.FindByNameAsync(model.Username); if (userExists != null) return (0, "User already exists"); ApplicationUser user = new ApplicationUser() { Email = model.Email, SecurityStamp = Guid.NewGuid().ToString(), UserName = model.Username, Name = model.Name }; var createUserResult = await userManager.CreateAsync(user, model.Password); if (!createUserResult.Succeeded) return (0,"User creation failed! Please check user details and try again."); if (!await roleManager.RoleExistsAsync(role)) await roleManager.CreateAsync(new IdentityRole(role)); if (await roleManager.RoleExistsAsync(UserRoles.User)) await userManager.AddToRoleAsync(user, role); return (1,"User created successfully!"); } public async Task<(int,string)> Login(LoginModel model) { var user = await userManager.FindByNameAsync(model.Username); if (user == null) return (0, "Invalid username"); if (!await userManager.CheckPasswordAsync(user, model.Password)) return (0, "Invalid password"); var userRoles = await userManager.GetRolesAsync(user); var authClaims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), }; foreach (var userRole in userRoles) { authClaims.Add(new Claim(ClaimTypes.Role, userRole)); } string token = GenerateToken(authClaims); return (1, token); } private string GenerateToken(IEnumerable<Claim> claims) { var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])); var tokenDescriptor = new SecurityTokenDescriptor { Issuer = _configuration["JWT:ValidIssuer"], Audience = _configuration["JWT:ValidAudience"], Expires = DateTime.UtcNow.AddHours(3), SigningCredentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256), Subject = new ClaimsIdentity(claims) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } } }
Let’s implement the controller responsible for Authentication. Through this controller, the user can register an account by providing a username and a password.
[Route("api/[controller]")] [ApiController] public class AuthenticationController : ControllerBase { private readonly IAuthService _authService; private readonly ILogger<AuthenticationController> _logger; public AuthenticationController(IAuthService authService, ILogger<AuthenticationController> logger) { _authService = authService; _logger = logger; } [HttpPost] [Route("login")] public async Task<IActionResult> Login(LoginModel model) { try { if (!ModelState.IsValid) return BadRequest("Invalid payload"); var (status, message) = await _authService.Login(model); if (status == 0) return BadRequest(message); return Ok(message); } catch(Exception ex) { _logger.LogError(ex.Message); return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); } } [HttpPost] [Route("registeration")] public async Task<IActionResult> Register(RegistrationModel model) { try { if (!ModelState.IsValid) return BadRequest("Invalid payload"); var (status, message) = await _authService.Registeration(model, UserRoles.User); if (status == 0) { return BadRequest(message); } return CreatedAtAction(nameof(Register), model); } catch (Exception ex) { _logger.LogError(ex.Message); return StatusCode(StatusCodes.Status500InternalServerError, ex.Message); } } }
To learn how to protect the api resources, let’s implement a controller.
[Route("api/fruits")] [ApiController] [Authorize] public class FruitController : ControllerBase { [HttpGet] public async Task<IActionResult> Get() { var fruits= await Task.FromResult(new string[] { "apple", "bananana", "kiwi" }); return Ok(fruits); } }
Conclusion
To conclude, we learned the significance and the difference between authentication and authorization in a .NET web API application, how they can make the application more secure, the significance of a JSON Web Token, to implement sign up and log in functionality using JSON Web Token and protection of API resources.
