Mastering JWT Authentication in .NET/ASP.NET Core 6.0: Secure Your Applications with Token-based Identity Verification

Murat Tanrısever
10 min readAug 31, 2023

In this article, we will do the authentication project with JWT (Json Web Token) using .NET 6.0 and .net Core.In short, what is Jwt, let’s talk about it a little.

JWT is the standard that enables secure data exchange between the parties in communication Decently. It is used in operations such as service security, user verification, information exchange. The main reason for choosing JSON Web Tokens is that the data transported in JSON can be verified.

JWT consists of three parts:
1-)Header: The header contains the encryption algorithm of the token used in the signature section of the JWT.
2-)Payload: Contains a user’s claims.
3-)Signature: Performs the signing process by taking the encoded secret information from the header and payload tags of the token.Let’s call it a kind of verification process.
Now let’s move on to our project.

  1. CREATING A TABLE

First of all, let’s create a database.We will create two tables named userInfo and Employee in it. I am writing the database scripts for you here.

USE [MTAuthenticationDB]

CREATE TABLE [dbo].[UserInfo](
[UserId] [int] IDENTITY(1,1) NOT NULL,
[DisplayName] [varchar](60) NOT NULL,
[UserName] [varchar](30) NOT NULL,
[Email] [varchar](50) NOT NULL,
[Password] [varchar](20) NOT NULL,
[CreatedDate] [datetime] NOT NULL,
CONSTRAINT [PK_UserInfo] PRIMARY KEY CLUSTERED
(
[UserId] ASC
)
)
INSERT [dbo].[UserInfo] VALUES (N'Murat Tanrısever', N'Admin', N'admin@murattanrisever.com', N'admin123!', CAST(N'2023-08-20 04:17:58.207' AS DateTime))


CREATE TABLE [dbo].[Employee](
[EmployeeID] [int] NOT NULL,
[NationalIDNumber] [nvarchar](15) NOT NULL,
[EmployeeName] [nvarchar](100) NULL,
[LoginID] [nvarchar](256) NOT NULL,
[JobTitle] [nvarchar](50) NOT NULL,
[BirthDate] [date] NOT NULL,
[MaritalStatus] [nchar](1) NOT NULL,
[Gender] [nchar](1) NOT NULL,
[HireDate] [date] NOT NULL,
[VacationHours] [smallint] NOT NULL,
[SickLeaveHours] [smallint] NOT NULL,
[rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_Employee] PRIMARY KEY CLUSTERED
(
[EmployeeID] ASC
)
)
INSERT [dbo].[Employee] VALUES (1, N'20100544', N'Murat TANRISEVER', N'Admin', N'Computer Programmer', CAST(N'1995-01-21' AS Date),
N'S', N'M', CAST(N'2009-01-14' AS Date), 99, 69, N'CBB97C64-481A-4FF0-980E-FD545C6F3B1C', CAST(N'2009-06-30 00:00:00.000' AS DateTime))
INSERT [dbo].[Employee] VALUES (2, N'52100531', N'Batuhan ŞENER', N'Manager', N'Artificial Intelligence Engineer', CAST(N'1996-03-15' AS Date),
N'S', N'F', CAST(N'2008-01-31' AS Date), 1, 20, N'DF22A4FA-D69D-4954-9DC2-2E9482B4EA27', CAST(N'2015-06-30 00:00:00.000' AS DateTime))
INSERT [dbo].[Employee] VALUES (3, N'20100554', N'Mehmet KAYHAN', N'Moderator', N'Agricultural Engineer', CAST(N'1994-10-24' AS Date),
N'M', N'M', CAST(N'2007-11-11' AS Date), 2, 21, N'C01805E1-4869-4413-BD1E-519B2C7C292B', CAST(N'2009-06-30 00:00:00.000' AS DateTime))

2. CREATING A PROJECT

Now open Visual Studio 2022 and click Create a new Project. On the opened page ASP.NET Select the Core Web API template and click next. Then, after typing the name of the project and determining its location, say next and switch to the framework selection. Here, select the .NET 6.0 (Long-term support) option and click the create button. Now 3. We can move to the stage.

3. INSTALLING NUGET PACKAGES

Click on the Tools menu and then respectively:

NuGet Package Manager -> Click Package Manager Console.

Install the following packages on the console screen that opens:

=> Install-Package Microsoft.EntityFrameworkCore

=> Install-Package Microsoft.EntityFrameworkCore.SqlServer

=> Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

4. CREATION OF MODELS

Right-click on the root directory of your project and create a new folder named Models. Then right-click on the Models folder and userInfo.cs and Employee.cs

userInfo.cs and write these codes there:

namespace MTAuthentication.WebApi.Models
{
public class UserInfo
{
public int UserId { get; set; }
public string? DisplayName { get; set; }
public string? UserName { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public DateTime? CreatedDate { get; set; }
}
}

Employee.cs and write these codes there:

namespace MTAuthentication.WebApi.Models
{
public class Employee
{
public int EmployeeID { get; set; }
public string? NationalIDNumber { get; set; }
public string? EmployeeName { get; set; }
public string? LoginID { get; set; }
public string? JobTitle { get; set; }
public DateTime BirthDate { get; set; }
public string? MaritalStatus { get; set; }
public string? Gender { get; set; }
public DateTime HireDate { get; set; }
public short VacationHours { get; set; }
public short SickLeaveHours { get; set; }
public Guid? RowGuid { get; set; }
public DateTime ModifiedDate { get; set; }
}
}

5. ADDING A DATA ACCESS LAYER

DatabaseContext, where we define the database connection in the next stage.We will create class. To do this, right-click on the Models folder and click on the new class option.

DatabaseContext.cs and write these codes there:

using Microsoft.EntityFrameworkCore;

namespace MTAuthentication.WebApi.Models
{
public partial class DatabaseContext : DbContext
{
public DatabaseContext()
{
}

public DatabaseContext(DbContextOptions<DatabaseContext> options)
: base(options)
{
}

public virtual DbSet<Employee>? Employees { get; set; }
public virtual DbSet<UserInfo>? UserInfos { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserInfo>(entity =>
{
entity.HasNoKey();
entity.ToTable("UserInfo");
entity.Property(e => e.UserId).HasColumnName("UserId");
entity.Property(e => e.DisplayName).HasMaxLength(60).IsUnicode(false);
entity.Property(e => e.UserName).HasMaxLength(30).IsUnicode(false);
entity.Property(e => e.Email).HasMaxLength(50).IsUnicode(false);
entity.Property(e => e.Password).HasMaxLength(20).IsUnicode(false);
entity.Property(e => e.CreatedDate).IsUnicode(false);
});

modelBuilder.Entity<Employee>(entity =>
{
entity.ToTable("Employee");
entity.Property(e => e.EmployeeID).HasColumnName("EmployeeID");
entity.Property(e => e.NationalIDNumber).HasMaxLength(15).IsUnicode(false);
entity.Property(e => e.EmployeeName).HasMaxLength(100).IsUnicode(false);
entity.Property(e => e.LoginID).HasMaxLength(256).IsUnicode(false);
entity.Property(e => e.JobTitle).HasMaxLength(50).IsUnicode(false);
entity.Property(e => e.BirthDate).IsUnicode(false);
entity.Property(e => e.MaritalStatus).HasMaxLength(1).IsUnicode(false);
entity.Property(e => e.Gender).HasMaxLength(1).IsUnicode(false);
entity.Property(e => e.HireDate).IsUnicode(false);
entity.Property(e => e.VacationHours).IsUnicode(false);
entity.Property(e => e.SickLeaveHours).IsUnicode(false);
entity.Property(e => e.RowGuid).HasMaxLength(50).IsUnicode(false);
entity.Property(e => e.ModifiedDate).IsUnicode(false);
});

OnModelCreatingPartial(modelBuilder);
}

partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
}

Now let’s create Interface and Repository folders to be able to execute operations in our database.MTAuthentication .Add two new folders, Interface and Repository, by right-clicking on the WebAPI project.

Right-click the Interface folder and click IEmployees.cs an interface named cs and type the following codes into it:

using MTAuthentication.WebApi.Models;

namespace MTAuthentication.WebApi.Interface
{
public interface IEmployees
{
public List<Employee> GetEmployeeDetails();
public Employee GetEmployeeDetails(int id);
public void AddEmployee(Employee employee);
public void UpdateEmployee(Employee employee);
public Employee DeleteEmployee(int id);
public bool CheckEmployee(int id);
}
}

Now the Repository folder will inherit the IEmployees interface EmployeeRepository.cs the and write this code there:

using MTAuthentication.WebApi.Interface;
using MTAuthentication.WebApi.Models;
using Microsoft.EntityFrameworkCore;

namespace MTAuthentication.WebApi.Repository
{
public class EmployeeRepository : IEmployees
{
readonly DatabaseContext _dbContext = new();

public EmployeeRepository(DatabaseContext dbContext)
{
_dbContext = dbContext;
}

public List<Employee> GetEmployeeDetails()
{
try
{
return _dbContext.Employees.ToList();
}
catch
{
throw;
}
}

public Employee GetEmployeeDetails(int id)
{
try
{
Employee? employee = _dbContext.Employees.Find(id);
if (employee != null)
{
return employee;
}
else
{
throw new ArgumentNullException();
}
}
catch
{
throw;
}
}

public void AddEmployee(Employee employee)
{
try
{
_dbContext.Employees.Add(employee);
_dbContext.SaveChanges();
}
catch
{
throw;
}
}

public void UpdateEmployee(Employee employee)
{
try
{
_dbContext.Entry(employee).State = EntityState.Modified;
_dbContext.SaveChanges();
}
catch
{
throw;
}
}

public Employee DeleteEmployee(int id)
{
try
{
Employee? employee = _dbContext.Employees.Find(id);

if (employee != null)
{
_dbContext.Employees.Remove(employee);
_dbContext.SaveChanges();
return employee;
}
else
{
throw new ArgumentNullException();
}
}
catch
{
throw;
}
}

public bool CheckEmployee(int id)
{
return _dbContext.Employees.Any(e => e.EmployeeID == id);
}
}
}

Now MTAuthentication.The program of the WebAPI project.we will add the DatabaseContext, UserManager and IUser references to the cs file.

Edit the Program.cs file :

using MTAuthentication.WebApi.Interface;
using MTAuthentication.WebApi.Models;
using MTAuthentication.WebApi.Repository;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

//Donot forgot to add ConnectionStrings as "dbConnection" to the appsetting.json file
builder.Services.AddDbContext<DatabaseContext>
(options => options.UseSqlServer(builder.Configuration.GetConnectionString("dbConnection")));
builder.Services.AddTransient<IEmployees, EmployeeRepository>();
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

6. ADDING THE API CONTROLLER TO THE PROJECT

Right-click the Controllers folder and click Add, then click Controller. Select API Controller from the dialog box on the left. EmployeeController.cs an API Controller named cs.

Now EmployeeController.cs the file and type the following codes:

using MTAuthentication.WebApi.Interface;
using MTAuthentication.WebApi.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace MTAuthentication.WebApi.Controllers
{
[Route("api/employee")]
[ApiController]
public class EmployeeController : ControllerBase
{
private readonly IEmployees _IEmployee;

public EmployeeController(IEmployees IEmployee)
{
_IEmployee = IEmployee;
}

// GET: api/employee>
[HttpGet]
public async Task<ActionResult<IEnumerable<Employee>>> Get()
{
return await Task.FromResult(_IEmployee.GetEmployeeDetails());
}

// GET api/employee/5
[HttpGet("{id}")]
public async Task<ActionResult<Employee>> Get(int id)
{
var employees = await Task.FromResult(_IEmployee.GetEmployeeDetails(id));
if (employees == null)
{
return NotFound();
}
return employees;
}

// POST api/employee
[HttpPost]
public async Task<ActionResult<Employee>> Post(Employee employee)
{
_IEmployee.AddEmployee(employee);
return await Task.FromResult(employee);
}

// PUT api/employee/5
[HttpPut("{id}")]
public async Task<ActionResult<Employee>> Put(int id, Employee employee)
{
if (id != employee.EmployeeID)
{
return BadRequest();
}
try
{
_IEmployee.UpdateEmployee(employee);
}
catch (DbUpdateConcurrencyException)
{
if (!EmployeeExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return await Task.FromResult(employee);
}

// DELETE api/employee/5
[HttpDelete("{id}")]
public async Task<ActionResult<Employee>> Delete(int id)
{
var employee = _IEmployee.DeleteEmployee(id);
return await Task.FromResult(employee);
}

private bool EmployeeExists(int id)
{
return _IEmployee.CheckEmployee(id);
}
}
}

7. TESTING PHASE

Download Postman and open it on the browser.
appsettings.we will use our localhost address in the Issuer,Audience sections that we created in our json file. I will give an example via localhost:7022. Write down which link it goes to when you run the project.

Run the project, open your postman and follow the steps below.

To become a login https://localhost:7022/api/token write the link. Select the method as POST and enter the correct email, password information that we defined in the userInfo table and click the send button. Copy the token that appears at the bottom and proceed to the next step.

https://localhost:7022/api/employee write the link.
Select the method as GET and click Send. Now the entire employee list will be in front of you.

https:localhost:7022/api/employee type the link and select the CREATE method.Click on the Authorization tab, select Bearer Token and paste the token you copied while logging in here.To come to the Body tab and add a record, take a sample record from the GET tab and replace the information we have enclosed in quotes with new ones, make sure that there is no EmployeeID, because we have done this automatically incrementally in the database.

https:localhost:7022/api/employee / type the link and after the slash, if you want to change the information of the employee who has which EmployeeID, type it I as an example https:localhost:7022/api/employee/50 , I will want to update Ahmet Yilmaz’s information as Mehmet Yilmaz.Set the method to PUT.
We are performing our token transactions in Authorization and copying the information of Ahmet Yilmaz, whose EmployeeID is 50, from the GET tab. Then we enter our PUT link and select raw on the body tab, set the data type to JSON and paste the data we have imported into the textbox.
After making the necessary arrangements, we click on the send button and now our employee with an EmployeeID of 50 becomes Mehmet Yilmaz.

https://localhost:7022/api/employee/50 We only skip the editing process from the operations we do in Edit. We type the data we want to delete into the body on the screen and write the EmployeeID to the extension on the link at the top. After performing the authorization process, we DELETE the login method, which is the only important part, and we press send. Mehmet Yilmaz, who now has an EmployeeID of 50, has been deleted from our database.

8. ADDING THE TOKEN

Right-click the Controllers folder and click Add, then click Add New Item.From the menu on the left side, select ASP.NET and select the API Controller — Empty option. The name of the controller is TokenController.cs.

Token Controllers.cs file and type the following codes there:

using MTAuthentication.WebApi.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace MTAuthentication.WebApi.Controllers
{
[Route("api/token")]
[ApiController]
public class TokenController : ControllerBase
{
public IConfiguration _configuration;
private readonly DatabaseContext _context;

public TokenController(IConfiguration config, DatabaseContext context)
{
_configuration = config;
_context = context;
}

[HttpPost]
public async Task<IActionResult> Post(UserInfo _userData)
{
if (_userData != null && _userData.Email != null && _userData.Password != null)
{
var user = await GetUser(_userData.Email, _userData.Password);

if (user != null)
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim("UserId", user.UserId.ToString()),
new Claim("DisplayName", user.DisplayName),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_configuration["Jwt:Issuer"],
_configuration["Jwt:Audience"],
claims,
expires: DateTime.UtcNow.AddMinutes(10),
signingCredentials: signIn);

return Ok(new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return BadRequest("Invalid credentials");
}
}
else
{
return BadRequest();
}
}

private async Task<UserInfo> GetUser(string email, string password)
{
return await _context.UserInfos.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
}
}

TokenController as a login control checks to see if the user’s credentials are the same as those in the database.Returns the token if the username and password are correct, returns the error code if it is incorrect.

The next stage is absettings.cs the json file and add the following code:

"Jwt": {
"Key": "MuratTanrıseverDnzli32Bytes123456",
"Issuer": "https://localhost:7022/",
"Audience": "https://localhost:7022/",
"Subject": "JWTServiceAccessToken"
},

Now the Program.cs file and add the following codes:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidIssuer = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});

app.UseAuthentication();

In the code above, we have configured how the authorization operations should work and performed the verification operations of the Issuer and Audience.

Program.cs I’m leaving the exact form of cs here:

using MTAuthentication.WebApi.Interface;
using MTAuthentication.WebApi.Models;
using MTAuthentication.WebApi.Repository;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

//Donot forgot to add ConnectionStrings as "dbConnection" to the appsetting.json file
builder.Services.AddDbContext<DatabaseContext>
(options => options.UseSqlServer(builder.Configuration.GetConnectionString("dbConnection")));
builder.Services.AddTransient<IEmployees, EmployeeRepository>();
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidIssuer = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

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

app.UseHttpsRedirection();

app.UseAuthentication();

app.UseAuthorization();

app.MapControllers();

app.Run();

Now let’s add the Authorization feature to the EmployeeController control so that no one can access it without user login.

Now we will receive a warning named user unauthorized when our token expires.

Dear reader, in this article I explained how to create a REST API using .NET 6.0, ASP:NET Core, perform basic CRUD operations, create a JWT (JSON Web Token) and ensure the security of APIs.
See you in the next post..

Bana ulaşmak için bu adresi kullanabilirsiniz : www.linkedin.com/in/murattanrisever/

--

--

No responses yet