Basic JWT Application with .Net 6 and Entity Framework Core

Basic JWT Application with .Net 6 and Entity Framework Core

I published a couple of days ago my blog post about JWT Token. Since what you get from a subject can be understood by applying it, I've decided to write this post.

To fully understand this post, a tad Entity Framework and .Net knowledge is required since I skip over some details supposed to be known.

I choose Rider generally in my C# coding stuff because I'm a user of M1 and its UI/UX is similar to Visual Studio on Windows.

Here is its full GitHub Link which is runnable immediately when downloaded.

What will we do?

We'll create a basic Web API taking advantage of Swagger framework(it is a 'framework', yes again, Swagger is a 'framework', even IBM calls it as such) getting data from MySql db installed on our local. At first, the data can be obtained without any authorization mechanism, then we're going to add JWT based token authorization mechanism to this API.

NuGet Packages

We are going to apply JWT on an API, so, we need the following NuGet packages as well as Web API application.

  • Microsoft.AspNetCore.Authentication.JwtBearer
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Tools (it is necessary too since we must choose startup project to create migration)
  • Pomelo.EntityFrameworkCore.MySql
  • Microsoft.AspNetCore.Authentication.JwtBearer

Let's create the API,

1.png

Add the following folders into the API, thereby helping us to be organized.

  • Data
  • Migrations
  • Models
  • Repository
  • IRepository under Repository

Then, it is seen as following,

hmm.png

We will have 'Team' table simply consisting of creation date, name and id.

Team.cs under Model

using System.ComponentModel.DataAnnotations;

namespace MyWebApi.Models;

public class Team
{
    public DateTime? CreatedTime { get; set; }
    [Key]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; } = null!;
}

ITeamRepository.cs under IRepository folder,

using MyWebApi.Models;

namespace MyWebApi.Repository.IRepository;

public interface ITeamRepository
{
    ICollection<Team> GetTeamMembers();
}

TeamRepository.cs under Repository folder

using MyWebApi.Data;
using MyWebApi.Models;
using MyWebApi.Repository.IRepository;

namespace MyWebApi.Repository;
public class TeamRepository : ITeamRepository
{
    private readonly ApplicationDbContext _db;

    public TeamRepository(ApplicationDbContext db)
    {
        _db = db;
    }

    public ICollection<Team> GetTeamMembers()
    {
        return _db.Teams.ToList();
    }
}

ApplicationDbContext.cs under Data folder

using Microsoft.EntityFrameworkCore;
using MyWebApi.Models;

namespace MyWebApi.Data;

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(
                 DbContextOptions<ApplicationDbContext> options) 
    : base(options)
    {

    }

    public DbSet<Team> Teams { get; set; }
}

I assume that you have had or have created a local db. For example, in my local it is called as mysampledb. I'm saying this because we need to register its service in Program.cs as following.

var connectionString = "server=localhost;" +
                       "user=root;" +
                       "password=yourdbpassword;" +
                       "database=mysampledb";
var serverVersion = ServerVersion.AutoDetect(connectionString);

builder.Services.AddDbContext<ApplicationDbContext>(
    dbContextOptions => dbContextOptions
        .UseMySql(connectionString, serverVersion)
        .LogTo(Console.WriteLine, LogLevel.Information)
        .EnableSensitiveDataLogging()
        .EnableDetailedErrors()
);

Then register the 'Team' entity as well.

builder.Services.AddScoped<ITeamRepository, TeamRepository>();

Now we're ready to add migration.

3.png

Yes, that's it! They came.

7.png

Let's add some data into it. For the sake of simplicity, I'm adding them directly.

5.png

Nice, let's run it and go further. 🥳

swag.gif

Even we can test it by Postman.

postman.gif

Until here, as you can see, we wouldn't need any authorization and not used JWT. As I mentioned in the first section of the series, we need a 'secret key' for negotiation, which is known by both the issuer and the audience.

I prefer to store it in appsettings.json. Before showing you how to obtain it as string, we need to authorize users by JWT. Due to this, we need to update our migration. For the sake of simplicity, let's add a user with password.

‼‼ Passwords never be stored in a table clearly. ‼‼

Append to User.cs under Mode folder.

using System.ComponentModel.DataAnnotations;

namespace MyWebApi.Models;

public class User
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string UserName { get; set; } = null!;
    [Required]
    public string Password { get; set; } = null!;
    [NotMapped]
    public string Token { get; set; }
}

Append to ApplicationDbContext.cs under Data folder

public DbSet<User> Users { get; set; }

Then add and update migration to see the following result.

users.gif

Add a user into it, let's say, username and password are "testuser".

Time to specify the secret key

In the appsetting.json file we add

"AppSettings": {
    "Secret": "sonerABCsoner123sonerDEFsoner123"
  }

As you see it is 32 characters, each of which is 1 byte, that is 256-byte. That's because we prefer to use HS256 signing algorithm.

Add AppSettings.cs ,

namespace MyWebApi.Data;

public class AppSettings
{
    public string Secret { get; set; }
}

Then, register JWT related stuff and obtain secret key from the appsettings.json

var appSettingsSection = builder.Configuration.GetSection("AppSettings");
builder.Services.Configure<AppSettings>(appSettingsSection);
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);


builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(e =>
    {
        e.RequireHttpsMetadata = false;
        e.SaveToken = true;
        e.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key)
        };
    });

Last, but by no means least, after var app = builder.Build(); line in Program.cs we need to append app.UseAuthentication();. At the final stage, let's check whether it works as we anticipate.

Let's see what we've done

jwt2-min.gif

Here we are. We're done 🥳 You can get all the code and MySql dump file from my GitHub.