Implement JWT Authentication in ASP.NET Core APIs
If you developed web applications using ASP.NET before, chances are you are
already familiar with cookie authentication. Although cookie based
authentication is still available under ASP.NET Core, JSON Web Token or JWT
based authentication is becoming more and more common. To that end this article
aims at introducing you to JWT and JWT based authentication.
What is JWT?
Before we delve into the code level implementation details let's briefly
understand a few concepts related to JWT.
As mentioned earlier JWT stands for JSON Web Tokens. It's an open standard to
pass user data between client and server. JWT has many advantages over
traditional cookie authentication. JWT is more secure and can also be used with
non-browser clients. JWT is a prefferred choice for implementing authentication
in Single Page Applications (SPA). A JWT token consists of three parts namely
header, payload, and signature. You will learn about them in the later part of
this article.
Unlike cookies, which are passed automatically to the server, JWT needs to be
explicitly passed to the server. So, a simplified flow of operations would be:
- Client sends security credentials such as user name and password to the
server for validation.
- Server validates the user name and password.
- If found correct the server generates and issues a JWT token to the
client.
- The client receives the token and stores it somewhere.
- While requesting any resource or action from the server, the client adds
the JWT token issued earlier in the Authorization header.
- Server reads the authorization header to retrieve the JWT token.
- If the token is valid, the server performs the action requested by the
client
So, simply put JWT acts like a ticket. If the incoming request has a ticket,
it is allowed to access a resource.
Steps required to implement JWT based authentication
In order to implement JWT based authentication you need to perform the
following steps:
- Store JWT details in a configuration file.
- Enable JWT authentication scheme in the application startup.
- Create some mechanism that validates user name and password, and issues
a JWT.
- Create a secured API
- Invoke the API from a client
Let's perform these steps one-by-one.
Begin by creating a new ASP.NET Core API project.
Notice that I am using Visual Studio 2019 and ASP.NET Core 3.0 but you can
use Visual Studio 2017 and ASP.NET Core 2.2 also.
Store JWT details in a configuration file
Once the project is created, add appSettings.json file to it and add the Jwt
section as shown below:
"Jwt": {
"Key": "SomeReallySecretKey",
"Issuer": "SomeIssuer",
"Audience": "SomeAudience"
}
Of course, the section name need not be Jwt. You can give any name of your
choice. The Jwt section defines three keys - Key, Issuer, and Audience.
The Key is supposed to be a secret string that is used while signing the
token. Issuer indicates the party that is issuing the JWT and audience indicates
the intended recipients of the JWT.
Enable JWT authentication scheme in the application startup
Now open the Startup class and modify the ConfigureServices() method as shown
below:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion
(CompatibilityVersion.Version_2_2);
services.AddAuthentication
(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
}
We call AddAuthentication() and specify JWT bearer scheme to be the
authentication scheme. We also specify various options for the JWT bearer
scheme. If you carefully observe the TokenValidationParameters object you will
find that it indicates whether issuer, audience, lifetime, and signature key is
to be validated or not. Additionally we also specify a valid issuer, a valid
audience, and a valid signing key. These values are retrieved from the
configuration file.
Now go to Configure() method and add the following code:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
We call UseAuthentication() to wire the authentication middleware into the
HTTP pipeline.
Create an API that validates a user and issues a JWT
We need some mechanism that validates a user name and password. We will go
with an API that does that for us. So, add a new API controller called
SecurityController in the Controllers folder. The SecurityController will have
two private helper methods and a public action.
The first private helper method is called GenerateJWT() and it generates a
JWT token for us. The token is then sent to the client. The GenerateJWT() method
is shown below:
private string GenerateJWT()
{
var issuer = _config["Jwt:Issuer"];
var audience = _config["Jwt:Audience"];
var expiry = DateTime.Now.AddMinutes(120);
var securityKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var credentials = new SigningCredentials
(securityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: issuer,
audience:audience,
expires: DateTime.Now.AddMinutes(120),
signingCredentials: credentials);
var tokenHandler = new JwtSecurityTokenHandler();
var stringToken = tokenHandler.WriteToken(token);
return stringToken;
}
The above code retrieves the issuer, audience, and key from the configuration
file. It then creates a new
SymmetricSecurityKey based on the Key.
SigningCredentials object is then generated based on the
SymmetricSecurityKey. Notice that we use HS256 algorithm while generating the
digital signature.
Now we can move ahead and create a JWT token. This is done using
JwtSecurityToken class. We pass the issuer, audience, an expiry DateTime for the
token, and the signing credentials in the constructor.
We want the JWT in a string form so that it can be easily sent to the client.
This is done using JwtSecurityTokenHandler class. The WriteToken() method
accepts a JwtSecurityToken created earlier and returns it as a JSON compact
serialized format string.
The second private helper method is ValidateUser() and is shown below:
private bool ValidateUser(User loginDetails)
{
if (loginDetails.UserName == "User1" &&
loginDetails.Password=="pass$word")
{
return true;
}
else
{
return false;
}
}
The ValidateUser() accepts User object:
public class User
{
public string UserName { get; set; }
public string Password { get; set; }
}
Inside, it checks the user name and password. In the above example, we simply
check against hard-coded values. But you could use ASP.NET Core Identity or any
custom technique to validate a user. If the user credentials are valid we return
true, otherwise we return false. Instead of returning true you could have also
returned user details such as user name and roles. Here, for the sake of
simplicity we don't return any such details.
Finally, we need Login() action that will invoke ValidateUser() and
GenerateJWT() helper methods.
[HttpPost]
public IActionResult Login([FromBody]User loginDetails)
{
bool result = ValidateUser(loginDetails);
if (result)
{
var tokenString = GenerateJWT();
return Ok(new { token = tokenString });
}
else
{
return Unauthorized();
}
}
Note that Login() action of SecurityController is marked with [HttpPost]
attribute. It accepts User object as the parameter. The Login() action will be
invoked by the client application and the client, by some means, will supply the
User details such as user name and password.
Inside, we call ValidateUser() helper method to check whether the user name
and password are valid. If user credentials are valid, we call GenerateJWT() to
generate a JWT token. The string token is returned to the client with HTTP
status of Ok (status code - 200).
Before we go ahead let's quickly test our Login() action.
Run the API project either through Visual Studio or using .NET CLI. Start
Postman and enter URL
as http://localhost:5000/api/security.
Make sure to select the HTTP verb to be POST. And also specify the JSON body
that wraps UserName and Password as shown above.
Now click on Send button. If all goes well, you will see a response as shown
below:
This is the JWT token! Notice that the token takes the form of --
header.payload.signature.
Ok. Now let's see what's the actual content of this token.
Open your favorite browser and navigate to
jwt.io. Locate Encoded section
under Debugger and copy-paste just the token value in the encoded textbox. Also
locate Verify Signature section and copy-paste your Key from the appsettings
file. You should see the decoded form of the token as shown below:
Have a look at the header and payload.
Create a secured API
Ok. So far so good. Now let's create an API that requires security. Add
another API in the Controllers folder - EmployeeController. For the sake of
simplicity we will have only GET method in it.
[Authorize]
[HttpGet]
public IActionResult Get()
{
List<Employee> data = new List<Employee>();
data.Add(new Employee() {EmployeeID=1,
FirstName="Nancy", LastName="Davolio" });
data.Add(new Employee() { EmployeeID = 2,
FirstName = "Andrew", LastName = "Smith" });
data.Add(new Employee() { EmployeeID = 3,
FirstName = "Janet", LastName = "Rollings" });
return new ObjectResult(data);
}
The Get() action simply creates a few Employee objects and returns them as an
ObjectResult. The Employee class looks like this:
public class Employee
{
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
What's important is that this method is marked with [Authorize] attribute.
The [Authorize] attribute indicates that only authenticated users can invoke
Get() action.
Let's confirm whether it works that way or now.
Open man tool again. This time make a GET request to
http://localhost:5000/api/employee.
Recollect that under JWT based security, the client needs to explicitly send
the token to the server through Authorization header. So, while making the GET
request we need to add the Authorization header and set its value to the JWT
token we generated earlier.
To add the authorization header, go to Headers tab and enter KEY as
Authorization and VALUE as Beader <string_token_here>. Notice that it's Bearer,
followed by a white space, followed by the actual token.
Now click on Send button. If all goes well, you should see a list of
employees.
In this part we used Postman to invoke the API. In the next installment of
this article we will build a simple JavaScript client to call the API.
That's it for now! Keep coding!!