Sitemap
Donny

Donny

How to add JWT Authentication to ASP.NET Core 6 API on Visual Studio Code

11 min readOct 13, 2024

--

Press enter or click to view image in full size

According to jwt.io JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

In order the API can use JWT authentication we going to add JWT dependencies using Microsoft.AspNetCore.Authentication.JwtBearer

Step 1 : Install Dependencies

Enter this command to your console :

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer -v 6.0.33 

Because we using .Net 6 so we specify the JWT version to 6.0.33

Step 2 : Setup Appsetting Json

Next step is to add JWT properties in appsettings.Development.json :

 "Jwt": {
"Key": "your-key",
"Audience": "your-target-client",
"Issuer": "your-target-issuer"
},

The Key is use as key to sign-in in your app. This key ensure that only your app can use it. The key value is random string like this :

"Key": "asdgfhqkqQjdLj12383iLUw8wnjj73jjMMu13084kUlern781uajsdbasa7JBJAG&&u9HANKKBGAt6wvk8q2hTA",

You can customize it using random string generator or etc, but in this tutorial we will use this random string as our key.

The Audience is your API client url, which means it can be your frontend application, other API, and so on. In this tutorial we will use our local dummy client :

"Audience": "http://localhost:3000",

And lastly, the Issuer is the target url who serve the JWT key. In this tutorial the JWT is produced by our API so the url will be same as our API :

"Issuer": "http://localhost:5000"

Now the appsetting.Development.json file will looks like this :

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"ConnectionStrings": {
"learnDatabase": "Server=localhost;Port=3306;Username=root;Password=;Database=learn;"
},
"Jwt": {
"Key": "asdgfhqkqQjdLj12383iLUw8wnjj73jjMMu13084kUlern",
"Audience": "http://localhost:3000",
"Issuer": "http://localhost:5000"
}
}

Step 3: Create JWT Model

This model is used for save our JWT data like JWT issuer, audience, and key. First, make new folder in our root project named Configs and create new file name ConfigJwt.cs .

Inside ConfigJwt.cs we define the data we going to save. The data is the issuer, audience, and key. Add this data in ConfigJwt.cs so the class would look like this :

namespace learn.Configs;
public class ConfigJwt
{
public string Issuer { get; set; } = null!;
public string Audience { get; set; } = null!;
public string Key { get; set; } = null!;
}

Step 4: Create Claim Input Model

To save the user login data like username, email, and name in our API we need to create claim model. We will separate the Model and Input Model to prevent over-posting in our API. You can learn more about over-posting in this link .

a. First, create new folder named InputModels in the root project and create new file named ClaimInputModel.cs inside InputModels folder.

b. Write this code in the ClaimInputModel.cs file :

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using learn.Models;

namespace learn.InputModels;

public class ClaimInputModel
{
public int Id { get; set; }
public string Username { get; set; } = null!;
public string Name { get; set; } = null!;
public string Email { get; set; } = null!;

public static ClaimInputModel Save(UserModel user)
{
return new ClaimInputModel
{
Id = user.Id,
Username = user.Username,
Name = user.Name,
Email = user.Email
};
}

public Claim[] Create()
{
return new Claim[] {
new(JwtRegisteredClaimNames.UniqueName, Username),
new("Id", Id.ToString()),
new("Username", Username),
new("Name", Name),
new("Email", Email),
};
}
}

Inside ClaimInputModel.cs we make model class to represent which data we going to use in our claim. The data we going to save is id user, username, name, and user email.

public static ClaimInputModel Save(UserModel user)
{
return new ClaimInputModel
{
Id = user.Id,
Username = user.Username,
Name = user.Name,
Email = user.Email
};
}

In this method we get the user data from user model then pass the data from user model and save it in ClaimInputModel input model object.

public Claim[] Create()
{
return new Claim[] {
new(JwtRegisteredClaimNames.UniqueName, Username),
new("Id", Id.ToString()),
new("Username", Username),
new("Name", Name),
new("Email", Email),
};
}

After we passing the data from user model, next step is to create Claim object. Claim is object from System.Security.Claims namespace. It save information about user identities in the system security.

Claim is different than ClaimInputModel

The Claim object will be created inside Create method. This will be returned as array of Claim .

new(JwtRegisteredClaimNames.UniqueName, Username)

That line of code is the short form of this code :

new Claim("type", "value")

So, from that code we will create new Claim object which contain the type and the value of that claim. In general the array should look like this :

[
{type, value},
{type, value},
{type, value},
so on..
]

Correct me if I am wrong 😅

In the first array we will stored the unique name and the value is from username then id, name, and email. The unique name is usually get from username, but you can get it from another data.

Step 5: Create Login Input Model

In order the user can login, firstly we will determine what data the user needs to input for login. In our API the requirement data will include username and password.

a. Create input model file inside InputModel folder named AuthInputModel.cs , so the InputModel folder will now have 2 file in it.

b. Write this code inside AuthInputModel.cs file :

using System.ComponentModel.DataAnnotations;

namespace learn.InputModels;

public class AuthInputModel
{
public class Login
{
[Required]
public string Username { get; set; } = null!;

[Required]
public string Password { get; set; } = null!;
}
}

In this code, we create new class named AuthInputModel and within this class we create another class named login that uses username and password as user input for login.

Step 6: Create Auth View Model

This model is used to represent the data that we want to show to user. It helps to prevent over-posting in our API. The data we want to show is the status and jwt token string.

Create new folder in root project named ViewModels then create new file inside that folder named AuthViewModel.cs . Write this code in the AuthViewModel.cs :

namespace learn.ViewModels;

public class AuthViewModel
{
public string Status { get; set; } = null!;
public string? Jwt { get; set; }
public string? RefreshToken { get; set; }

private AuthViewModel() { }

private AuthViewModel(string status, string? jwt = null, string? refreshToken = null)
{
Status = status;
Jwt = jwt;
RefreshToken = refreshToken;
}

public static AuthViewModel Success(string jwt, string refreshToken)
{
return new AuthViewModel("Success", jwt, refreshToken);
}

public static AuthViewModel Failed()
{
return new AuthViewModel("Failed");
}
}

We will store and show the status, jwt string, and refresh token to the client using this code :

public string Status { get; set; } = null!;
public string? Jwt { get; set; }
public string? RefreshToken { get; set; }

Create an private constructor to instantiation. The instantiation is like create new class object using new keyword. Here is the example :

Get Donny’s stories in your inbox

Join Medium for free to get updates from this writer.

Without private constructor this will not will success and not return error

var myObject = new SomeClass(); // output success

With private constructor this will return error

var myObject = new SomeClass(); // output error

So, in our code we will create two private constructor to prevent instantiation and also assign the variable value using this code :

private AuthViewModel() { }

private AuthViewModel(string status, string? jwt = null, string? refreshToken = null)
{
Status = status;
Jwt = jwt;
RefreshToken = refreshToken;
}

And lastly we create success and failed method to call this constructor, like this :

public static AuthViewModel Success(string jwt, string refreshToken)
{
return new AuthViewModel("Success", jwt, refreshToken);
}

public static AuthViewModel Failed()
{
return new AuthViewModel("Failed");
}

This will create AuthViewModel object with the different status, jwt, and refresh token value.

Step 7: Create JWT Generator

After we create all the required model the next step is to make JWT generator to generate our JWT code and convert it to string type. Make this generator in separated folder so we can easily manage our project.

Create new folder in our project and give it name Services and also create new file inside that folder named TokenService.cs and write this code in it :

using System.IdentityModel.Tokens.Jwt;
using System.Text;
using learn.Configs;
using learn.InputModels;
using Microsoft.IdentityModel.Tokens;

namespace learn.Services;

public class TokenService
{
public static string Jwt(ConfigJwt configsJwt, ClaimInputModel claim, int timerInMinutes)
{
// create credential
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configsJwt.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512);

// create claim information
var claims = claim.Create();

// create secure token
var jwtToken = new JwtSecurityToken(
issuer: configsJwt.Issuer,
audience: configsJwt.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(timerInMinutes),
signingCredentials: credentials
);

// create token from JWT
var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

return token;
}
}

What happen in this code is we firstly create the credential using this code :

// create credential 
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configsJwt.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512);

And also create claim information with the type System.Security.Claims.Claim using this code :

// create claim information
var claims = claim.Create();

Only then we create JWT object from that variables using this code :

// create secure token
var jwtToken = new JwtSecurityToken(
issuer: configsJwt.Issuer,
audience: configsJwt.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(timerInMinutes),
signingCredentials: credentials
);

And finally we convert that JWT object to string and returned the string using this code :

// create token from JWT 
var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

return token;

So in the end we get the JWT with the type of string that can be use in our client or authorization header.

Step 8: Create Refresh Token Generator

In the TokenService class create refresh token generator bellow Jwt method like this :

public static string RefreshToken(int length)
{
Random random = new Random();
string randomString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12344567890";

string RefreshToken = new string(Enumerable
.Repeat(randomString, length)
.Select(m => m[random.Next(m.Length)])
.ToArray()
);

return RefreshToken;
}

What this method do is create random string character using randomString as the character list and then we create random char array using Enumerable class. Here the detail explanation :

.Repeat(randomString, length) : create new char array using randomString as the character list and length as the length of created array

.Select(m => m[random.Next(m.Length)]) : randomly pick the char using random.Next until specified length

.ToArray() : convert the all the generated char to array

So in the end the TokenService class should look like this :

using System.IdentityModel.Tokens.Jwt;
using System.Text;
using learn.Configs;
using learn.InputModels;
using Microsoft.IdentityModel.Tokens;

namespace learn.Services;

public class TokenService
{
public static string Jwt(ConfigJwt configsJwt, ClaimInputModel claim, int timerInMinutes)
{
// create credential
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configsJwt.Key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512);

// create claim information
var claims = claim.Create();

// create secure token
var jwtToken = new JwtSecurityToken(
issuer: configsJwt.Issuer,
audience: configsJwt.Audience,
claims: claims,
expires: DateTime.Now.AddMinutes(timerInMinutes),
signingCredentials: credentials
);

// create token from JWT
var token = new JwtSecurityTokenHandler().WriteToken(jwtToken);

return token;
}

public static string RefreshToken(int length)
{
Random random = new Random();
string randomString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12344567890";

string RefreshToken = new string(Enumerable
.Repeat(randomString, length)
.Select(m => m[random.Next(m.Length)])
.ToArray()
);

return RefreshToken;
}
}

Step 9: Create Auth Controller

Next step is to create login controller in our API. In this tutorial we going to use scaffold to create AuthController file, but you can also create this controller manually.

Run this command to create new AuthController in your console :

dotnet aspnet-codegenerator controller -name AuthController -async -api -outDir Controllers

This command will create AuthController inside Controllers folder. In the AuthController class add private readonly variables using this code :

private readonly LearnContext _context;
private readonly ConfigJwt _configJwt;

Then create AuthController constructor like this :

public AuthController(
LearnContext context,
IOptions<ConfigJwt> configJwt
)
{
_context = context;
_configJwt = configJwt;
}

Then add this action method :

[HttpPost("[action]")]
public async Task<IActionResult> Login([FromBody] AuthInputModel.Login input)
{
// check is _context null or not
if (_context.User == null) return BadRequest("context.user is null");

// get user data from db
var user = await _context.User
.Where(m => (m.Username == input.Username) &&
(m.Password == input.Password) &&
(!m.IsDelete))
.SingleOrDefaultAsync();

// check if user found or not
if (user == null) return BadRequest("user not found, please check username or the password");

// save user info to claim
var claim = Claim.SaveFromDb(user);

// create JWT token
string Jwt = TokenService.Jwt(_confJwt, claim, 60);

// generate refresh token
var RefreshToken = TokenService.RefreshToken(16);

// save refresh token and access token to model auth
var res = AuthViewModel.Success(Jwt, RefreshToken);

// update data user
user.RefreshToken = RefreshToken;

try
{
_context.Update(user);
await _context.SaveChangesAsync();

return Ok(res);
}
catch (System.Exception e)
{
return BadRequest($"exception occured while save the refresh token in the DB {e}");
}
}

Import the required namespace for each red underlined class using ctrl + . and click the using some.namespace . Here is the example :

The Login action method will return the response with the status, token and refresh token if the response success but it will return bad request if there’s any error in update database process.

Step 10: Configure the Program

The final step is to configure the the main program.cs to register the JWT key, issuer, and audience from appsetting.Development.json to our project. To do this simply add this code to program.cs before initialize app variable :

// add ConfigJwt to the service 
builder.Services.Configure<ConfigJwt>(builder.Configuration.GetSection("Jwt"));

// add appsetting to the program
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Development.json")
.Build();

// add the JWT authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = configuration["Jwt:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"])),
ValidateLifetime = true,
};
});

And also add authentication middleware before app.UseAuthorization() :

The order of middleware is important

// add authentication middleware to the program
app.UseAuthentication(); // Verify the user

app.UseAuthorization(); // check the user permission

Don’t forget to import the required namespace by pressing ctrl + . in the red unlined code. In the end program.cs file should look like this :

using System.Text;
using learn.Configs;
using learn.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register LearnContext to the API
builder.Services.AddDbContext<LearnContext>(options =>
{
var connectionString = builder.Configuration.GetConnectionString("learnDatabase");
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
});

// add ConfigJwt to the service
builder.Services.Configure<ConfigJwt>(builder.Configuration.GetSection("Jwt"));

// add appsetting to the program
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.Development.json")
.Build();


// add the JWT authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = configuration["Jwt:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"])),
ValidateLifetime = true,
};
});


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// add authentication middleware to the program
app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 11: Test Login Controller

After all set, we can test our controller by pressing F5 to run the app and make request using swagger UI. If everything goes as planned, we should receive a 200 status code, along with the JWT and refresh token in the response body, formatted like this: :

Press enter or click to view image in full size

The final folder structure of this project should look like this :

You can see the full code of this project in my repository here : https://github.com/donny17-bit/learn

Congratulation!! 🎉🎉

We have finished adding the JWT authentication in our API, you can customize more by adding middleware, add password encryption, modify refresh token, etc.

Feel free to comment 😊

--

--