Introduction
Authentication is the process of validating user credentials and authorization is the process of checking privileges for a user to access specific modules in an application. In this article, we will see how to protect an ASP.NET 5 Web API application by implementing JWT authentication.
Swagger
Swagger is an Interface Description Language for describing RESTful APIs expressed using JSON. Swagger is used together with a set of open-source software tools to design, build, document, and use RESTful web services. Swagger includes automated documentation, code generation, and test-case generation. Swashbuckle is an open-source project for generating Swagger documents for Web APIs.
We will enable authorization of swagger in this application, so that we can execute authentication protected API requests using swagger.
Create ASP.NET 5 Web API using Visual Studio 2019
Before creating .NET 5 applications, you must install .NET 5 SDK in your system. You should upgrade your Visual Studio 2019 also.
You must choose .NET core version to 5 from the dropdown and also deselect the default Open API support. We can enable swagger manually later.
We must install below libraries using NuGet package manager.
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.AspNetCore.Identity.EntityFrameworkCore
- Microsoft.AspNetCore.Identity
- Microsoft.AspNetCore.Authentication.JwtBearer
- Swashbuckle.AspNetCore
We can modify the appsettings.json with below SQL connection string values and authentication details.
appsettings.json
- {
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- },
- "AllowedHosts": "*",
- "ConnectionStrings": {
- "ConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SarathlalDB;Integrated Security=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
- },
- "JWT": {
- "ValidAudience": "http://localhost:4200",
- "ValidIssuer": "http://localhost:59921",
- "Secret": "StrONGKAutHENTICATIONKEy"
- }
- }
We have added a database connection string and also added valid audience, valid issuer and secret key for JWT authentication in above settings file.
Create an “ApplicationUser” class inside a new folder “Authentication” which will inherit the IdentityUser class. IdentityUser class is a part of Microsoft Identity framework. We will create all the authentication related files inside the “Authentication” folder.
ApplicationUser.cs
- using Microsoft.AspNetCore.Identity;
-
- namespace JWTAuthenticationWithSwagger.Authentication
- {
- public class ApplicationUser : IdentityUser
- {
- }
- }
We can create the “ApplicationDbContext” class and add below code.
ApplicationDbContext.cs
- using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
- using Microsoft.EntityFrameworkCore;
-
- namespace JWTAuthenticationWithSwagger.Authentication
- {
- public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
- {
- public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
- {
- }
- }
- }
Create class “RegisterModel” for new user registration.
RegisterModel.cs
- using System.ComponentModel.DataAnnotations;
-
- namespace JWTAuthenticationWithSwagger.Authentication
- {
- public class RegisterModel
- {
- [Required(ErrorMessage = "User Name is required")]
- public string Username { get; set; }
-
- [EmailAddress]
- [Required(ErrorMessage = "Email is required")]
- public string Email { get; set; }
-
- [Required(ErrorMessage = "Password is required")]
- public string Password { get; set; }
- }
- }
Create class “LoginModel” for user login.
LoginModel.cs
- using System.ComponentModel.DataAnnotations;
-
- namespace JWTAuthenticationWithSwagger.Authentication
- {
- public class LoginModel
- {
- [Required(ErrorMessage = "User Name is required")]
- public string Username { get; set; }
-
- [Required(ErrorMessage = "Password is required")]
- public string Password { get; set; }
- }
- }
We can create a class “Response” for returning the response value after user registration and user login. It will also return error messages, if the request fails.
Response.cs
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
-
- namespace JWTAuthenticationWithSwagger.Authentication
- {
- public class Response
- {
- public string Status { get; set; }
- public string Message { get; set; }
- }
- }
We can create an API controller “AuthenticateController” inside the “Controllers” folder and add below code.
AuthenticateController.cs
- using JWTAuthenticationWithSwagger.Authentication;
- using Microsoft.AspNetCore.Http;
- using Microsoft.AspNetCore.Identity;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.Extensions.Configuration;
- using Microsoft.IdentityModel.Tokens;
- using System;
- using System.Collections.Generic;
- using System.IdentityModel.Tokens.Jwt;
- using System.Security.Claims;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace JWTAuthenticationWithSwagger.Controllers
- {
- [Route("api/[controller]")]
- [ApiController]
- public class AuthenticateController : ControllerBase
- {
- private readonly UserManager<ApplicationUser> userManager;
- private readonly IConfiguration _configuration;
-
- public AuthenticateController(UserManager<ApplicationUser> userManager, IConfiguration configuration)
- {
- this.userManager = userManager;
- _configuration = configuration;
- }
-
- [HttpPost]
- [Route("login")]
- public async Task<IActionResult> Login([FromBody] LoginModel model)
- {
- var user = await userManager.FindByNameAsync(model.Username);
- if (user != null && await userManager.CheckPasswordAsync(user, model.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));
- }
-
- var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));
-
- var token = new JwtSecurityToken(
- issuer: _configuration["JWT:ValidIssuer"],
- audience: _configuration["JWT:ValidAudience"],
- expires: DateTime.Now.AddHours(3),
- claims: authClaims,
- signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
- );
-
- return Ok(new
- {
- token = new JwtSecurityTokenHandler().WriteToken(token),
- expiration = token.ValidTo
- });
- }
- return Unauthorized();
- }
-
- [HttpPost]
- [Route("register")]
- public async Task<IActionResult> Register([FromBody] RegisterModel model)
- {
- var userExists = await userManager.FindByNameAsync(model.Username);
- if (userExists != null)
- return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });
-
- ApplicationUser user = new ApplicationUser()
- {
- Email = model.Email,
- SecurityStamp = Guid.NewGuid().ToString(),
- UserName = model.Username
- };
- var result = await userManager.CreateAsync(user, model.Password);
- if (!result.Succeeded)
- return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });
-
- return Ok(new Response { Status = "Success", Message = "User created successfully!" });
- }
-
- }
- }
We have added two methods “login” and “register” inside the controller class. Register method will be used to create new user information. In login method, we have returned a JWT token after successful login.
We can make below changes in “ConfigureServices” and “Configure” methods in “Startup” class.
Startup.cs
- using JWTAuthenticationWithSwagger.Authentication;
- using Microsoft.AspNetCore.Authentication.JwtBearer;
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.Identity;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Microsoft.Extensions.Hosting;
- using Microsoft.Extensions.Logging;
- using Microsoft.IdentityModel.Tokens;
- using Microsoft.OpenApi.Models;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
-
- namespace JWTAuthenticationWithSwagger
- {
- public class Startup
- {
- public Startup(IConfiguration configuration)
- {
- Configuration = configuration;
- }
-
- public IConfiguration Configuration { get; }
-
-
- public void ConfigureServices(IServiceCollection services)
- {
-
- services.AddControllers();
- services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("ConnStr")));
-
-
- services.AddIdentity<ApplicationUser, IdentityRole>()
- .AddEntityFrameworkStores<ApplicationDbContext>()
- .AddDefaultTokenProviders();
-
-
- services.AddAuthentication(options =>
- {
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
- options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
- options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
- })
-
-
- .AddJwtBearer(options =>
- {
- options.SaveToken = true;
- options.RequireHttpsMetadata = false;
- options.TokenValidationParameters = new TokenValidationParameters()
- {
- ValidateIssuer = true,
- ValidateAudience = true,
- ValidAudience = Configuration["JWT:ValidAudience"],
- ValidIssuer = Configuration["JWT:ValidIssuer"],
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]))
- };
- });
-
- services.AddSwaggerGen(swagger =>
- {
-
- swagger.SwaggerDoc("v1", new OpenApiInfo
- {
- Version = "v1",
- Title = "ASP.NET 5 Web API",
- Description = "Authentication and Authorization in ASP.NET 5 with JWT and Swagger"
- });
-
- swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
- {
- Name = "Authorization",
- Type = SecuritySchemeType.ApiKey,
- Scheme = "Bearer",
- BearerFormat = "JWT",
- In = ParameterLocation.Header,
- Description = "Enter 'Bearer' [space] and then your valid token in the text input below.\r\n\r\nExample: \"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\"",
- });
- swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
- {
- {
- new OpenApiSecurityScheme
- {
- Reference = new OpenApiReference
- {
- Type = ReferenceType.SecurityScheme,
- Id = "Bearer"
- }
- },
- new string[] {}
-
- }
- });
- });
- }
-
-
- public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
- {
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
-
- app.UseSwagger();
- app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ASP.NET 5 Web API v1"));
-
- app.UseHttpsRedirection();
-
- app.UseRouting();
-
- app.UseAuthentication();
- app.UseAuthorization();
-
- app.UseEndpoints(endpoints =>
- {
- endpoints.MapControllers();
- });
- }
- }
- }
We can add “Authorize” attribute inside the existing “WeatherForecast” controller (This controller is created automatically).
We must create a database and required tables before running the application. As we are using entity framework, we can use below database migration command with package manger console to create a migration script.
“add-migration Initial”
Use below command to create database and tables.
“update-database”
We can run the application and try to access get method in weatherforecast controller using swagger.
You have got a 401 – Unauthorized error. Because, you have not yet passed a valid token to this method in this controller.
We can create new user using register method in Authenticate control using swagger.
After successful registration, we can get a valid token from login method using the above credentials.
After successful login, you have got a valid token.
We can copy the token value and use in our swagger to authenticate our entire application.
We can see an Authorize button in the top of the swagger screen. Please click that button.
As mentioned in the description, please add the previously copied token value along with “Bearer “and click “Authorize” button to proceed further.
Authorization will be applied to entire application now. We can close the button and check the weatherforecast controller again.
We have successfully received data from weatherforecast controller now.
Conclusion
In this post, we have seen how to create JSON web tokens in .NET 5 Web API and use this token to authenticate and authorize our application. We have enabled swagger documentation and also enabled authentication in swagger. So that, we can check our entire Web API application without using Postman or any other third-party tools.