How to use Web API JWT Token

By FoxLearn 11/27/2024 1:52:59 PM   289
Generating a JWT (JSON Web Token) in ASP.NET Core using C# involves creating a token that contains claims and signing it with a secure key.

In this article, we'll walk you through the process of setting up JWT (JSON Web Token) authentication in an ASP.NET Core application. This tutorial covers creating a basic ASP.NET Core project, installing necessary packages, and building classes for account management and token generation.

Implementing JWT Authentication in ASP.NET Core

Open Visual Studio and create a new ASP.NET Core Web API project, then install package "Microsoft.AspNetCore.Authentication.JwtBearer" from Nuget Manage Packages in your Visual Studio.

To work with JWTs in ASP.NET Core, we need to install the Microsoft.AspNetCore.Authentication.JwtBearer package. This package allows us to handle authentication using JWTs.

Right-click on your project in the Solution Explorer and choose Manage NuGet Packages, then search for Microsoft.AspNetCore.Authentication.JwtBearer and click Install.

Next, we'll create two classes: Account and AccountInMemory.

The Account class represents a user, and the AccountInMemory class stores a mock list of accounts.

Create the Account class in a folder named Models:

namespace ASPNET.Models
{
    public class Account
    {
        public string Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}

Create a static class called AccountInMemory that holds a list of accounts:

namespace ASPNET.Models
{
    using System;
    using System.Collections.Generic;

    public static class AccountInMemory
    {
        public static IList<Account> ArrayAccount = new List<Account>();

        static AccountInMemory()
        {
            ArrayAccount.Add(new Account
            {
                Id = Guid.NewGuid().ToString("n"),
                FirstName = "Lucy",
                LastName = "Hynh",
                UserName ="admin",
                Password = "123abc"
            });
        }
    }
}

This in-memory list will simulate a database for user authentication.

Create a new folder with name is Provider, then create TokenProviderOptions, TokenProviderMiddleware classes to your Provider folder.

The TokenProviderOptions class holds the settings for the token generation process.

namespace ASPNET.Provider
{
    using Microsoft.IdentityModel.Tokens;
    using System;

    public class TokenProviderOptions
    {
        public string Path { get; set; } = "/token";

        public TimeSpan Expiration { get; set; } = TimeSpan.FromDays(+1);

        public SigningCredentials SigningCredentials { get; set; }
    }
}

The TokenProviderMiddleware class processes incoming requests and generates a JWT token if the request is valid.

namespace ASPNET.Provider
{
    using ASPNET.Models;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Options;
    using Newtonsoft.Json;
    using System;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using System.Linq;
    using System.Collections.Generic;

    public class TokenProviderMiddleware
    {
        private readonly RequestDelegate _next;

        private readonly TokenProviderOptions _options;

        public TokenProviderMiddleware(
            RequestDelegate next,
            IOptions<TokenProviderOptions> options)
        {
            _next = next;
            _options = options.Value;
        }

        public Task Invoke(HttpContext context)
        {
            if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
            {
                return _next(context);
            }

            if (!context.Request.Method.Equals("POST")
               || !context.Request.HasFormContentType)
            {
                context.Response.StatusCode = 400;
                return context.Response.WriteAsync("Bad request.");
            }

            return GenerateToken(context);
        }

        private async Task GenerateToken(HttpContext context)
        {
            var username = context.Request.Form["username"];
            var password = context.Request.Form["password"];

            var identity = await GetIdentity(username, password);

            if (identity == null)
            {
                context.Response.StatusCode = 400;
                await context.Response.WriteAsync("Invalid username or password.");
                return;
            }

            var now = DateTime.UtcNow;

            var jwt = new JwtSecurityToken(
                claims: identity.Claims,
                notBefore: now,
                expires: now.Add(_options.Expiration),
                signingCredentials: _options.SigningCredentials);

            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

            var response = new
            {
                access_token = encodedJwt,
                expires_in = (int)_options.Expiration.TotalSeconds,
            };

            context.Response.ContentType = "application/json";
            await context.Response.WriteAsync(JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented }));
        }

        private Task<ClaimsIdentity> GetIdentity(string username, string password)
        {
            var user = AccountInMemory.ArrayAccount.FirstOrDefault(x => x.UserName.Equals(username) && x.Password.Equals(password));

            if (user == null) return null;

            IList<Claim> claims = new List<Claim>();

            claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id, null, ClaimsIdentity.DefaultIssuer, "Provider"));

            claims.Add(new Claim(ClaimTypes.Name, $"{user.FirstName} {user.LastName}", null, ClaimsIdentity.DefaultIssuer, "Provider"));

            claims.Add(new Claim("Username", user.UserName));

            return Task.FromResult(new ClaimsIdentity(claims, "Bearer"));
        }
    }
}

Now that we've created the necessary classes, we need to configure them in the Startup.cs file.

Invoke method: used to check the endpoint if endpoint == "token" will handle the generate token task, or you can handle what is needed because each request it runs through this method.

GenerateToken method: Use to "Verify username and password then generate token".

GetIdentity method: used for creating identiy claim

Open the Startup.cs file, then add the following code.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    string secretKey = "enter_your_sercet_key";

    SymmetricSecurityKey SigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));

    app.UseMiddleware<TokenProviderMiddleware>(Options.Create(new TokenProviderOptions
    {
        SigningCredentials = new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256),
    }));

    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = SigningKey,
            ValidateIssuer = false,
            ValidateAudience = false,
        }
    });

    app.UseMvc();
}

We define a secret key used for signing the token, then we use middleware to handle the token generation and validation.

Next, We configure JWT Bearer authentication to validate incoming tokens.

First, you need to create a "Serial Key", this key we use to encrypt and decrypt token.

SymmetricSecurityKey is a class that helps us create a SecuretyKey

Finally, let's create a ValuesController class that is protected with JWT authentication.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

namespace ASPNET.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class ValuesController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }
}

The [Authorize] attribute ensures that only authenticated users can access the endpoints in this controller.

To test the API, you can use tools like Postman or the JWT Bearer Chrome extension to generate and send a request with the JWT token.

  1. First, make a POST request to /token with username and password in the form data.
  2. Use the returned JWT token to authenticate future requests by including the token in the Authorization header as Bearer <your-token>.

In this article, we have covered how to set up JWT authentication in an ASP.NET Core application. By creating account management classes, handling token generation with middleware, and configuring JWT authentication in Startup.cs, we've demonstrated the process to implement a secure authentication system for your API.