Tutorial built with ASP.NET Core 3.0
In this tutorial we’ll go through a simple example of how to implement JWT (JSON Web Token) authentication in an ASP.NET Core 3.0 API with C#.
The example API has just two endpoints/routes to demonstrate authenticating with JWT and accessing a restricted route with JWT:
/users/authenticate
- public route that accepts HTTP POST requests containing the username and password in the body. If the username and password are correct then a JWT authentication token and the user details are returned./users
- secure route that accepts HTTP GET requests and returns a list of all the users in the application if the HTTP Authorization header contains a valid JWT token. If there is no auth token or the token is invalid then a 401 Unauthorized response is returned.To develop and run ASP.NET Core applications locally, download and install the following:
dotnet run
from the command line in the project root folder (where the WebApi.csproj file is located), you should see the message Now listening on: http://localhost:4000
. Follow the instructions below to test with Postman or hook up with one of the example single page applications available (Angular, React or Vue).NOTE: You can also start the application in debug mode in VS Code by opening the project root folder in VS Code and pressing F5 or by selecting Debug -> Start Debugging from the top menu. Running in debug mode allows you to attach breakpoints to pause execution and step through the application code.
Postman is a great tool for testing APIs, you can download it at https://www.getpostman.com/.
Below are instructions on how to use Postman to authenticate a user to get a JWT token from the api, and then make an authenticated request with the JWT token to retrieve a list of users from the api.
To authenticate a user with the api and get a JWT token follow these steps:
Open a new request tab by clicking the plus (+) button at the end of the tabs.
Change the http request method to “POST” with the dropdown selector on the left of the URL input field.
In the URL field enter the address to the authenticate route of your local API - http://localhost:4000/users/authenticate
.
Select the “Body” tab below the URL field, change the body type radio button to “raw”, and change the format dropdown selector to “JSON (application/json)”.
Enter a JSON object containing the test username and password in the “Body” textarea:
{
"username": "test",
"password": "test"
}
Click the “Send” button, you should receive a “200 OK” response with the user details including a JWT token in the response body, make a copy of the token value because we’ll be using it in the next step to make an authenticated request.
Here’s a screenshot of Postman after the request is sent and the user has been authenticated:
To make an authenticated request using the JWT token from the previous step, follow these steps:
http://localhost:4000/users
.Here’s a screenshot of Postman after making an authenticated request to get all users:
For full details about the example Angular 8 application see the post Angular 8 - JWT Authentication Example & Tutorial. But to get up and running quickly just follow the below steps.
npm install
from the command line in the project root folder (where the package.json is located).// provider used to create fake backend
located in the /src/app/app.module.ts
file.npm start
from the command line in the project root folder, this will launch a browser displaying the Angular example application and it should be hooked up with the ASP.NET Core JWT Auth API that you already have running.For full details about the example React application see the post React - JWT Authentication Tutorial & Example. But to get up and running quickly just follow the below steps.
npm install
from the command line in the project root folder (where the package.json is located).// setup fake backend
located in the /src/index.jsx
file.npm start
from the command line in the project root folder, this will launch a browser displaying the React example application and it should be hooked up with the ASP.NET Core JWT Auth API that you already have running.For full details about the example VueJS JWT application see the post Vue.js + Vuex - JWT Authentication Tutorial & Example. But to get up and running quickly just follow the below steps.
npm install
from the command line in the project root folder (where the package.json is located).// setup fake backend
located in the /src/index.js
file.npm start
from the command line in the project root folder, this will launch a browser displaying the VueJS example application and it should be hooked up with the ASP.NET Core JWT Auth API that you already have running.The tutorial project is organised into the following folders:
Controllers - define the end points / routes for the web api, controllers are the entry point into the web api from client applications via http requests.
Models - represent request and response models for controller methods, request models define the parameters for incoming requests, and response models can be used to define what data is returned.
Services - contain business logic, validation and data access code.
Entities - represent the application data.
Helpers - anything that doesn’t fit into the above folders.
Click any of the below links to jump down to a description of each file along with its code:
Path: /Controllers/UsersController.cs
The ASP.NET Core users controller defines and handles all routes / endpoints for the api that relate to users, this includes authentication and standard CRUD operations. Within each route the controller calls the user service to perform the action required, this enables the controller to stay ‘lean’ and completely separated from the business logic and data access code.
The controller actions are secured with JWT using the [Authorize]
attribute, with the exception of the Authenticate
method which allows public access by overriding the [Authorize]
attribute on the controller with [AllowAnonymous]
attribute on the action method. I chose this approach so any new action methods added to the controller will be secure by default unless explicitly made public.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using WebApi.Services;
using WebApi.Models;
using System.Linq;
namespace WebApi.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[AllowAnonymous]
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]AuthenticateModel model)
{
var user = _userService.Authenticate(model.Username, model.Password);
if (user == null)
return BadRequest(new { message = "Username or password is incorrect" });
return Ok(user);
}
[HttpGet]
public IActionResult GetAll()
{
var users = _userService.GetAll();
return Ok(users);
}
}
}
Path: /Entities/User.cs
The user entity class represents the data for a user in the application. Entity classes are used to pass data between different parts of the application (e.g. between services and controllers) and can be used to return http response data from controller action methods. If multiple types of entities or other custom data is required to be returned from a controller method then a custom model class should be created in the Models
folder for the response.
namespace WebApi.Entities
{
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Token { get; set; }
}
}
Path: /Helpers/AppSettings.cs
The app settings class contains properties defined in the appsettings.json file and is used for accessing application settings via objects that are injected into classes using the ASP.NET Core built in dependency injection (DI) system. For example the User Service accesses app settings via an IOptions<AppSettings> appSettings
object that is injected into the constructor.
Mapping of configuration sections to classes is done in the ConfigureServices
method of the Startup.cs file.
namespace WebApi.Helpers
{
public class AppSettings
{
public string Secret { get; set; }
}
}
Path: /Helpers/ExtensionMethods.cs
Extension methods are used to add convenience methods and extra functionality to existing types in C#.
The extension methods class adds a couple of simple convenience methods for removing passwords from User
instances and IEnumerable<User>
collections. These methods are called by the Authenticate
and GetAll
methods in the UserService to ensure the user objects returned don’t include passwords.
using System.Collections.Generic;
using System.Linq;
using WebApi.Entities;
namespace WebApi.Helpers
{
public static class ExtensionMethods
{
public static IEnumerable<User> WithoutPasswords(this IEnumerable<User> users) {
return users.Select(x => x.WithoutPassword());
}
public static User WithoutPassword(this User user) {
user.Password = null;
return user;
}
}
}
Path: /Models/AuthenticateModel.cs
The authenticate model defines the parameters for incoming requests to the /users/authenticate
route of the api, because it is set as the parameter to the Authenticate
method of the UsersController. When an HTTP POST request is received to the route, the data from the body is bound to an instance of the AuthenticateModel
, validated and passed to the method.
ASP.NET Core Data Annotations are used to automatically handle model validation, the [Required]
attribute sets both the username and password as required fields so if either are missing a validation error message is returned from the api.
using System.ComponentModel.DataAnnotations;
namespace WebApi.Models
{
public class AuthenticateModel
{
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
}
}
Path: /Services/UserService.cs
The user service contains a method for authenticating user credentials and returning a JWT token, and a method for getting all users in the application.
I hardcoded the array of users in the example to keep it focused on JWT authentication, in a production application it is recommended to store user records in a database with hashed passwords.
The top of the file contains an interface that defines the user service, below that is the concrete user service class that implements the interface.
On successful authentication the Authenticate method generates a JWT (JSON Web Token) using the JwtSecurityTokenHandler
class which generates a token that is digitally signed using a secret key stored in appsettings.json. The JWT token is returned to the client application which must include it in the HTTP Authorization header of subsequent requests to secure routes.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using WebApi.Entities;
using WebApi.Helpers;
namespace WebApi.Services
{
public interface IUserService
{
User Authenticate(string username, string password);
IEnumerable<User> GetAll();
}
public class UserService : IUserService
{
// users hardcoded for simplicity, store in a db with hashed passwords in production applications
private List<User> _users = new List<User>
{
new User { Id = 1, FirstName = "Test", LastName = "User", Username = "test", Password = "test" }
};
private readonly AppSettings _appSettings;
public UserService(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public User Authenticate(string username, string password)
{
var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
// return null if user not found
if (user == null)
return null;
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
user.Token = tokenHandler.WriteToken(token);
return user.WithoutPassword();
}
public IEnumerable<User> GetAll()
{
return _users.WithoutPasswords();
}
}
}
Path: /appsettings.Development.json
Configuration file with application settings that are specific to the development environment.
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}
Path: /appsettings.json
Root configuration file containing application settings for all environments.
IMPORTANT: The "Secret"
property is used by the api to sign and verify JWT tokens for authentication, update it with your own random string to ensure nobody else can generate a JWT to gain unauthorised access to your application.
{
"AppSettings": {
"Secret": "THIS IS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Path: /Program.cs
The program class is a console app that is the main entry point to start the application, it configures and launches the web api host and web server using an instance of IHostBuilder
. ASP.NET Core applications require a host in which to execute.
Kestrel is the web server used in the example, it’s a new cross-platform web server for ASP.NET Core that’s included in new project templates by default. Kestrel is fine to use on it’s own for internal applications and development, but for public facing websites and applications it should sit behind a more mature reverse proxy server (IIS, Apache, Nginx etc) that will receive HTTP requests from the internet and forward them to Kestrel after initial handling and security checks.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
namespace WebApi
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>()
.UseUrls("http://localhost:4000");
});
}
}
Path: /Startup.cs
The startup class configures the request pipeline of the application and how all requests are handled.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using WebApi.Helpers;
using WebApi.Services;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
namespace WebApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddControllers();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
// configure DI for application services
services.AddScoped<IUserService, UserService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
// global cors policy
app.UseCors(x => x
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
});
}
}
}
Path: /WebApi.csproj
The csproj (C# project) is an MSBuild based file that contains target framework and NuGet package dependency information for the application.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.5.0" />
</ItemGroup>
</Project>
The tutorial project is available on GitHub at https://github.com/cornflourblue/aspnet-core-3-jwt-authentication-api.
#asp-net #c-sharp #security #javascript #web-development