Introduction
In this article, I am going to demonstrate how to set up a back-end application to validate a client request through tokenization and secure an endpoint to be accessible only by a valid token.
I am going to use JWT (JSON Web Token) as a tokenization standard for authentication.
JWT is a very popular and widely used tokenization standard. It is an open standard (RFC 7519) and the information received by this is trusted as it is digitally signed.
I will demonstrate how it works in my article.
I will be using .NET Core 2.1 for API framework.
Background
As you know, token-based authentication is trendy these days. Is it worth it? To answer this question, we need to understand the concept of cookie-based authentication.
A cookie-based authentication requires a client to provide valid credentials (username and password). The server validates it and creates a session and sends the session id to the client (browser). The server also saves this session id in its repository so that it can compare for each incoming request the next time. The client saves this session id as cookie and attaches it with resource request to the server.
You can see cookie-based authentication requires a server to keep track of all active sessions, this is known as “stateful” sessions. Once the session id is lost or destroyed, a new session needs to be created. Imagine if a client is dealing with multiple resource servers? In this case, that client needs to remember the cookie from each server and keep track of it.
The token-based method overcomes the shortcomings of cookie-based authentication.
In token-based authentication, a client is given token instead of a cookie. The tokens are light-weight JSON (JavaScript Object Notation) and contain encoded information about the user and expiry time.
Benefit
The server only needs to validate the incoming request token instead of storing sessions for each client. So, it is the best fit for “stateless” implementation of services, which is a good fit for RESTful WebAPI.
JWT (JSON Web Token) is a very common format of token-based implementation. It is so popular right now that it has become a de-facto standard for token-based authentication.
JWT is composed of three components, separated by a dot (.)
Header
Header contains standard information, i.e., type of token and the name of the algorithm. The information is coded in base64 format.
Example -
- {
- "alg": "HS256",
- "typ": "JWT"
- }
Payload
Payload is JSON data that contains information of the user. It can but does not have to be limited to the data regarding user, claims and any other necessary data can also be there.
Example -
- {
- “issuer”: “http:
- “expires”: “2015-11-18T18:25:43.511Z”
- }
Signature
Signature is a “digital signature” of the combination of header and payload. Signature helps the server to verify the authenticity of the content of JWT against malicious alteration of content.
Implementation
Let’s implement this concept through ASP.NET Core. Open Visual Studio and select .NET Core->ASP.NET Core Web Application.
Select “API” project type…
Run the application and probably you will be getting this output
This indicates thatGET in “values” controller is open to everyone. There are no restrictions at all. Let’s assume we have a scenario that only authenticated users can access this URL. To hide this from the public and be accessible to an authenticated user I will decorate with “Authorize” attribute.
Also, add authentication in HTTP Request Pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- .
- .
- .
- app.UseAuthentication();
- app.UseMvc();
- }
Now, if you want to access this end, you are probably getting this error.
It suggests (ignore the poor implementation of error handling), we are allowing the URL to authenticated users only.
Great, the securing of resource part is done. Next, we need a mechanism to generate a token for valid users. For that, we will add a controller, AuthController, which will ingest login credentials (username and password), validate the user and generate a token.
For that, add a model class “LoginModel” to hold “UserName” and “Password” and a new controller, “AuthController”.
Add this code to “LoginModel”.
- public class LoginModel
- {
- public string UserName { get; set; }
- public string Password { get; set; }
- }
Add this code in “AuthController”.
- Route("api/[controller]")]
- [ApiController]
- public class AuthController : ControllerBase
- {
- // GET api/values
- [HttpPost, Route("login")]
- public IActionResult Login([FromBody]LoginModel user)
- {
- if (user == null)
- {
- return BadRequest("Invalid request");
- }
-
- if (user.UserName == "johncitizen" && user.Password == "abc@123")
- {
- var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("KeyForSignInSecret@1234"));
- var signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
-
- var tokeOptions = new JwtSecurityToken(
- issuer: "http://localhost:2000",
- audience: "http://localhost:2000",
- claims: new List<Claim>(),
- expires: DateTime.Now.AddMinutes(30),
- signingCredentials: signinCredentials
- );
-
- var tokenString = new JwtSecurityTokenHandler().WriteToken(tokeOptions);
- return Ok(new { Token = tokenString });
- }
- else
- {
- return Unauthorized();
- }
- }
- }
As you can see “AuthController" has a login action which requires “LoginModel” as input for username and password.
Login action validates the user with hardcoded username and password (for the sake for simplicity, although practically this validation should be done through the database). This “token” action generates token and sends to the client if credentials are valid. It will send “unauthorized” error if credentials are not matching.
If you look closely at the structure of the token it contains some necessary information. It has issuer, audience, claims, and expiry time which is part of the payload. “JwtSecurityTokenHandler” takes care of adding header and adding a signature. Note that claims list is empty as I am not implementing role-based authorization in this article.
With this, we have completed the implementation of token generation and sending part.
Let’s test this application. We will use “Postman” tool to access the “Login”, pass valid credentials, and get the token.
Now, run the application and to test login, open postman. Change the header, content-type to “application/json”.
Add valid username and password (JSON format) in body.
- {
- "UserName":"johncitizen",
- "Password": "abc@123"
- }
If you send these details, the server will provide a token.
So, by this, we have configured a token providing server. Although we have a token the application is not equipped to handle and interpret the token. We need to add capability, so client request token can be understood by the server.
Add this code in “ConfigureServices” method, in "startup.cs class".
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
- .AddJwtBearer(options =>
- {
- options.TokenValidationParameters = new TokenValidationParameters
- {
- ValidateIssuer = true,
- ValidateAudience = true,
- ValidateLifetime = true,
- ValidateIssuerSigningKey = true,
-
- ValidIssuer = "http://localhost:2000",
- ValidAudience = "http://localhost:2000",
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("KeyForSignInSecret@1234"))
- };
- });
-
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- }
If you notice we are making the application aware of JWT authentication and asking to validate token contents; i.e., Issuer, Audience, Lifetime (expiry of the token) and digital signature. Also note we need to supply ValidIssuer, ValidAudience, and IssuerSigningKey exactly the same as we did at the time of writing token generation. The reason behind is token extraction and generation should share the same logic.
This completes the second part of the request, i.e. sending a token to the resource server, authentication and returning resource if the token is valid.
To test this, we will first generate the token (through login URL and user credentials) in postman (like we did before), copy the token.
Open another postman tab/instance, put values URL, select type “Bearer Token” and paste the above-generated token. Send the request and you should get the response.
Happy coding!!!