Agenda
- Why do we need token in RESTful APIs
- What is JWT
- How to Create JWT in ASP.NET Core API
- How to validate JWT in ASP.NET Core API
Why do we need a Token in RESTful APIs?
Restful APIs or Web APIs are stateless by default. Every request is a new request to the server. This makes Web APIs easily scalable. But what if we want to provide some authorization on our Web APIs? We can issue a token to the requester and then the requester can present that token in future requests to authorize itself.
Now we have two options,
- Create a random but unique token and keep track of that token on the server side. This is how Server Side Session works. We have "Session Id", a unique & random string.
- Create a token which contains everything in it and then you don't track anything on server.
The first option is not very scalable but the second option is. Now if our token is going to contain the data in itself, what issues do we see?
- What will be format of token & how to represent data in it?
- How to secure the content of token so the end user can't read it?
- How to detect if token is tempered by end user?
We can develop our own mechanism to 1) Create a token 2) Validate a token and extract information from it when someone presents a token to us. But do we have any solution already available for us? Yes, we do.
Following are two popular token types for which we currently have support/libraries in ASP.NET or ASP.NET Core,
- oAuth Bearer Token
- It stores data in the form of claims (key/value pairs)
- Token is encrypted.
- End User needs algorithm & key to decrypt it.
- Json Web Token
Json Web Tokens (check https://jwt.io/ for example)
- JWT token is a string and has three parts separated by dot (.) a) Header b) Payload c) Signature
- Header & Payload are JSON objects
- Header contains algorithm & type of token which is jwt
- Payload contains claims (key/value pairs) + expiration date + aud/issuer etc.
- Signature is HASH value computed using Base64(Header) +"." + Base64(Payload). This information is passed to an algorithm with a secret key.
- Token structure is base64(header) + "." + base64(payload) + "." + hash
This is a quick workflow using JWT,
- Client sends a request to server for token
- Server generates a JWT (which contains a hash). Hash is generated using a secret key.
- Client receives the token and stores it somewhere locally.
- Client sends the token in future requests.
- Server gets the token from request header, computes Hash again by using a) Header from token b) payload from token c) secret key which server already has.
- If ("newly computed hash" = "hash came in token"), token is valid otherwise it is tempered or not valid.
User can decode JWT and see what is in header & in payload. Therefore we should not keep any confidential information in token.
In this article, we'll learn,
- How we can create Json Web Token in ASP.NET Core Web API
- How to validate a JWT bearer token if it comes in a request
- How to get claims data
Creating & Validating JWT in ASP.NET Core Web API
In Visual Studio 2019,
Step 1
On Startup Window: Choose 'Create a New Project' -> 'ASP.NET Core Web Application' -> Provide Name to Project -> .NET Core, ASP.NET Core 3.0 and API from template and "No Authentication" from right panel. -> Click on 'Create'
OR
Step 2
On Startup Window: Choose 'Continue without code' -> File -> New Project -> 'ASP.NET Core Web Application' -> Provide Name to Project -> .NET Core, ASP.NET Core 3.0 and API from template and "No Authentication" from right panel. -> Click on 'Create'
Creating JWT Token
- Add the following nuget Package
Microsoft.AspNetCore.Authentication.JwtBearer 3.0.0
- Right click on 'Controllers' -> Add -> Controller -> 'API Controller - Empty' - Name it 'MainController'
- Open MainController.cs file and add following namespaces
- using Microsoft.IdentityModel.Tokens;
-
using System.IdentityModel.Tokens.Jwt;
-
using System.Security.Claims;
- using System.Text;
Add the following action in MainController. Here we've made it very simple.
Note
Normally we'll expose this method with POST verb + we'll receive some credentials for authentication. Once user will be authenticated, token will be generated accordingly.
- [HttpGet("gettoken")]
- public Object GetToken()
- {
- string key = "my_secret_key_12345";
- var issuer = "http://mysite.com"; //normally this will be your site URL
-
- var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
- var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
-
-
- var permClaims = new List<Claim>();
- permClaims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
- permClaims.Add(new Claim("valid", "1"));
- permClaims.Add(new Claim("userid", "1"));
- permClaims.Add(new Claim("name", "bilal"));
-
-
- var token = new JwtSecurityToken(issuer,
- issuer,
- permClaims,
- expires: DateTime.Now.AddDays(1),
- signingCredentials: credentials);
- var jwt_token = new JwtSecurityTokenHandler().WriteToken(token);
- return new { data = jwt_token };
- }
Now let's run the application and test the following in browser/postman (considering https://localhost:44334 is base URL of our application).
https://localhost:44334/api/main/gettoken
Response in browser should be something like this. "data" contains the token.
- {"data":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI4NjA3ZWIxNC1mNzcyLTRlN2UtYTExNS05YmIzMTQ4NzhmNmEiLCJ2YWxpZCI6IjEiLCJ1c2VyaWQiOiIxIiwibmFtZSI6ImJpbGFsIiwiZXhwIjoxNTgzNDExNTAxLCJpc3MiOiJodHRwOi8vbXlzaXRlLmNvbSIsImF1ZCI6Imh0dHA6Ly9teXNpdGUuY29tIn0.PUxjfs9n_M4CXYKcMuwYO0q03ugJX9KvOIlXQc8fcIc"}
You may copy token from here and decode it on http://jwt.io to see what it contains.
How to Validate JWT Token?
Note
JWT Creator App & JWT Validator App can be two different applications. If validator is different than creator, add nuget package Microsoft.AspNetCore.Authentication.JwtBearer 3.0.0
Step 1
Add following namespaces in Startup.cs file
- using Microsoft.IdentityModel.Tokens;
- using Microsoft.AspNetCore.Authentication.JwtBearer;
- using System.Text;
Step 2
Create a new function in Startup class to register JWT service & then call this function in 'ConfigureServices' method. We are telling framework how to check if a request is authorized or not.
- public void ConfigureServices(IServiceCollection services)
- {
- SetupJWTServices(services);
- services.AddControllers();
- }
- private void SetupJWTServices(IServiceCollection services)
- {
- string key = "my_secret_key_12345";
- var issuer = "http://mysite.com"; //this should be same which is used while creating token
-
- services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
- .AddJwtBearer(options =>
- {
- options.TokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuer = true,
- ValidateAudience = true,
- ValidateIssuerSigningKey = true,
- ValidIssuer = issuer,
- ValidAudience = issuer,
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
- };
-
- options.Events = new JwtBearerEvents
- {
- OnAuthenticationFailed = context =>
- {
- if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
- {
- context.Response.Headers.Add("Token-Expired", "true");
- }
- return Task.CompletedTask;
- }
- };
- });
- }
Step 3
Add the following 'app.UseAuthentication()' in 'Configure' method
- app.UseAuthentication();
- app.UseAuthorization();
Note
Now when a request will come to server, It will find token & will try to validate it. If token is valid, It will set User.Identity.IsAuthenticated to true and it will also set claims in 'User.Identity'.
Step 4
Add the following Actions in API Controller (e.g. MainController) for testing.
GetName1
It has no authorization enabled on it. This function will be called whether we've received a token or not but we are checking if user is authenticated (means a valid token has been received) inside the function. User.Identity contains the claims (which are constructed from token)
GetName2
It has Authorize attribute. This function will not be called if a valid token is not received.
- [HttpPost("getname1")]
- public String GetName1() {
- if (User.Identity.IsAuthenticated) {
- var identity = User.Identity as ClaimsIdentity;
- if (identity != null) {
- IEnumerable < Claim > claims = identity.Claims;
- }
- return "Valid";
- } else {
- return "Invalid";
- }
- }
-
- [Authorize]
- [HttpPost("getname2")]
- public Object GetName2() {
- var identity = User.Identity as ClaimsIdentity;
- if (identity != null) {
- IEnumerable < Claim > claims = identity.Claims;
- var name = claims.Where(p => p.Type == "name").FirstOrDefault() ? .Value;
- return new {
- data = name
- };
-
- }
- return null;
- }
Now let's run the application and test it using Postman (https://getpostman.com) by providing token, by providing invalid token, without token etc.
Method Type: POST
URL: https://localhost:44334/api/main/getname1
URL: https://localhost:44334/api/main/getname2
Headers
Authorization: Bearer <token>
Content-Type: application/json
Body
Select "raw"
Select "JSON" from last dropdown
Provide data in JSON format if any.
Note
Requester/Consumer of token can be browser/desktop app/mobile app/postman etc. as long it allows creating HTTP requests.
Summary
JSON web tokens have gotten quite popular and there are reasons for this popularity. The main reason is its simplicity. End application/consumer should consider security of tokens as important as login/password security. JWT are not encrypted, but rather encoded. It means anyone who has access to JWT can decode and get information from it. Confidential data should not be part of it or it should be encrypted if it is required. Size of payload should be small. Keep only required claims with small names.