ASP.NET Authentication with Identity in a Web API with Bearer Tokens & Cookies in .NET 8

We’ll use the in-memory database for this example.

dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Swashbuckle.AspNetCore.Filters
class MyUser : IdentityUser {}
public class DataContext : IdentityDbContext<MyUser>
{
    public DataContext(DbContextOptions<DataContext> options) : base(options)
    {

    }
}

Program.cs

using Microsoft.EntityFrameworkCore;
using Swashbuckle.AspNetCore.Filters;
using WebApplication1;
using WebApplication1.Data;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("oauth2", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
    {
        In = Microsoft.OpenApi.Models.ParameterLocation.Header,
        Name = "Authorization",
        Type = Microsoft.OpenApi.Models.SecuritySchemeType.ApiKey
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});


builder.Services.AddDbContext<DataContext>(options => options.UseInMemoryDatabase("AppDb"));
builder.Services.AddAuthentication();
builder.Services.AddIdentityApiEndpoints<MyUser>()
    .AddEntityFrameworkStores<DataContext>();


var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.MapIdentityApi<MyUser>();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

In Swagger

References
https://www.youtube.com/watch?v=8J3nuUegtL4
https://devblogs.microsoft.com/dotnet/whats-new-with-identity-in-dotnet-8/

Create Refresh Token on ASP.NET Web API

[HttpPost("refresh-token")]
public async Task<ActionResult<string>> RefreshToken()
{
    var refreshToken = Request.Cookies["refreshToken"];

    if (!user.RefreshToken.Equals(refreshToken))
    {
        return Unauthorized("Invalid Refresh Token.");
    }
    else if(user.TokenExpires < DateTime.Now)
    {
        return Unauthorized("Token expired.");
    }

    string token = CreateToken(user);
    var newRefreshToken = GenerateRefreshToken();
    SetRefreshToken(newRefreshToken);

    return Ok(token);
}

private RefreshToken GenerateRefreshToken()
{
    var refreshToken = new RefreshToken
    {
        Token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)),
        Expires = DateTime.Now.AddDays(7),
        Created = DateTime.Now
    };

    return refreshToken;
}

private void SetRefreshToken(RefreshToken newRefreshToken)
{
    var cookieOptions = new CookieOptions
    {
        HttpOnly = true,
        Expires = newRefreshToken.Expires
    };
    Response.Cookies.Append("refreshToken", newRefreshToken.Token, cookieOptions);

    user.RefreshToken = newRefreshToken.Token;
    user.TokenCreated = newRefreshToken.Created;
    user.TokenExpires = newRefreshToken.Expires;
}

References
https://www.youtube.com/watch?v=HGIdAn2h8BA
https://www.youtube.com/watch?v=LowJMwa7LCU
https://passage.id/post/how-refresh-tokens-work-a-complete-guide-for-beginners?utm_source=pocket_reader
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/?utm_source=pocket_reader

Decode JWT Token in ASP.NET Web API

var stream = "[encoded jwt]";  
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = jsonToken as JwtSecurityToken;

Or, without the cast:

var token = "[encoded jwt]";  
var handler = new JwtSecurityTokenHandler();
var jwtSecurityToken = handler.ReadJwtToken(token);

I can get Claims using:

var jti = tokenS.Claims.First(claim => claim.Type == "jti").Value;

References
https://stackoverflow.com/questions/38340078/how-to-decode-jwt-token?utm_source=pocket_reader

Access JWT Token from Controller in ASP.NET Web API

[HttpGet]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<ActionResult<string>> Get()
{
    var token = await HttpContext.GetTokenAsync("access_token");
    return token;
}
var token = Request.Headers["Authorization"];
var token2 = await HttpContext.GetTokenAsync(JwtBearerDefaults.AuthenticationScheme, "access_token");

References
https://stackoverflow.com/questions/58887151/how-to-access-token-data-from-controller-method
https://stackoverflow.com/questions/52793488/jwt-cannot-be-retrieved-by-httpcontext-gettokenasync-in-net-core-2-1

Get User in ASP.NET Web API

[HttpGet("me"), Authorize]
public async Task<ActionResult<ResponseDto<GetMeResponseDto>>> GetMe()
{
    try
    {
        ResponseDto<GetMeResponseDto> result = new();
        var cts = new CancellationTokenSource();
        var context = await _dbFactory.CreateDbContextAsync(cts.Token);

        var userName = User?.Identity?.Name;

        if (!string.IsNullOrEmpty(userName))
        {
            var user = await context.Users.FirstOrDefaultAsync(x => x.Username == userName,
                cancellationToken: cts.Token);

            if (user != null)
            {
                var role = User!.FindFirstValue(ClaimTypes.Role) ?? string.Empty;

                result.Data = new GetMeResponseDto()
                {
                    Id = user.Id,
                    Username = userName,
                    Role = role,
                };
                result.HasError = false;
            }
        }

        return Ok(result);
    }
    catch (Exception e)
    {
        _logger.LogErrorEx(e, e.Message);
    }

    return BadRequest();
}

Or you can use HttpContext to access it.

References
https://www.youtube.com/watch?v=fhWIkbF18lM
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-7.0
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/use-http-context?view=aspnetcore-7.0
https://www.youtube.com/watch?v=7vqAHD9DlIA

JWT Token Creation, Authentication And Authorization In ASP.NET

Add Nuget Packages

Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.IdentityModel.Tokens
System.IdentityModel.Tokens.Jwt

Add setting in appsetting.json

"Jwt": {
    "Key": "ACDt1vR3lXToPQ1g3MyN", //Generate random String from https://www.random.org/strings
    "Issuer": "http://localhost:28747/", //Project Property-> Debug-> IIS-->App URL (you can local host url as well)
    "Audience": "http://localhost:28747/"
  },

Register JWT token for Authentication in Program.cs file

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters {
        ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
    };
});
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();

Create Models (UserLogin, UserModel and UserConstant)

namespace JWTLoginAuthenticationAuthorization.Models
{
    public class UserModel
    {
        public string Username { get; set; }
        public string Password { get; set; }
        public string Role { get; set; }
    }
}
namespace JWTLoginAuthenticationAuthorization.Models
{
    public class UserLogin
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
}
namespace JWTLoginAuthenticationAuthorization.Models
{
    // We are not taking data from data base so we get data from constant
    public class UserConstants
    {
        public static List<UserModel> Users = new()
            {
                    new UserModel(){ Username="naeem",Password="naeem_admin",Role="Admin"}
            };
    }
}

Create LoginAPI Controller (Authenticate user and generate token)

using JWTLoginAuthenticationAuthorization.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JWTLoginAuthenticationAuthorization.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class LoginController : ControllerBase
    {
        private readonly IConfiguration _config;
        public LoginController(IConfiguration config)
        {
            _config = config;
        }

        [AllowAnonymous]
        [HttpPost]
        public ActionResult Login([FromBody] UserLogin userLogin)
        {
            var user = Authenticate(userLogin);
            if (user != null)
            {
                var token = GenerateToken(user);
                return Ok(token);
            }

            return NotFound("user not found");
        }

        // To generate token
        private string GenerateToken(UserModel user)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier,user.Username),
                new Claim(ClaimTypes.Role,user.Role)
            };
            var token = new JwtSecurityToken(_config["Jwt:Issuer"],
                _config["Jwt:Audience"],
                claims,
                expires: DateTime.Now.AddMinutes(15),
                signingCredentials: credentials);


            return new JwtSecurityTokenHandler().WriteToken(token);

        }

        //To authenticate user
        private UserModel Authenticate(UserLogin userLogin)
        {
            var currentUser = UserConstants.Users.FirstOrDefault(x => x.Username.ToLower() ==
                userLogin.Username.ToLower() && x.Password == userLogin.Password);
            if (currentUser != null)
            {
                return currentUser;
            }
            return null;
        }
    }
}

Create User API Controller to authorize user role

namespace JWTLoginAuthenticationAuthorization.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        //For admin Only
        [HttpGet]
        [Route("Admins")]
        [Authorize(Roles = "Admin")]
        public IActionResult AdminEndPoint()
        {
            var currentUser = GetCurrentUser();
            return Ok($"Hi you are an {currentUser.Role}");
        }
        private UserModel GetCurrentUser()
        {
            var identity = HttpContext.User.Identity as ClaimsIdentity;
            if (identity != null)
            {
                var userClaims = identity.Claims;
                return new UserModel
                {
                    Username = userClaims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value,
                    Role = userClaims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value
                };
            }
            return null;
        }
    }
}

References
https://www.c-sharpcorner.com/article/jwt-token-creation-authentication-and-authorization-in-asp-net-core-6-0-with-po/
https://www.youtube.com/watch?v=UwruwHl3BlU
https://www.youtube.com/watch?v=6sMPvucWNRE

Passing multiple POST parameters to Web API Controller Methods

C# :

[HttpPost]
public string PostAlbum(JObject jsonData)
{
    dynamic json = jsonData;
    JObject jalbum = json.Album;
    JObject juser = json.User;
    string token = json.UserToken;

    var album = jalbum.ToObject<Album>();
    var user = juser.ToObject<User>();

    return String.Format("{0} {1} {2}", album.AlbumName, user.Name, token);
}

JavaScript :

var album = {
    AlbumName: "PowerAge",
    Entered: "1/1/1977"
}
var user = {
    Name: "Rick"
}
var userToken = "sekkritt";


$.ajax(
{
    url: "samples/PostAlbum",
    type: "POST",
    contentType: "application/json",
    data: JSON.stringify({ Album: album, User: user, UserToken: userToken }),
     success: function (result) {
        alert(result);
    }
});

How To Get ASP.NET Web API to Return JSON

first remove xml format :

config.Formatters.Remove(config.Formatters.XmlFormatter);

accepts text/html requests and returns application/json responses :

public class BrowserJsonFormatter : JsonMediaTypeFormatter
    {
        public BrowserJsonFormatter()
        {
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/x-www-form-urlencoded"));
            this.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
            this.SerializerSettings.Formatting = Formatting.Indented;
        }

        public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);
            headers.ContentType = new MediaTypeHeaderValue("application/json");
        }
    }

Register like so:

config.Formatters.Add(new BrowserJsonFormatter());

References
http://stackoverflow.com/questions/9847564/how-do-i-get-asp-net-web-api-to-return-json-instead-of-xml-using-chrome
https://github.com/aspnet/Mvc/issues/1765

Use OWIN to Self-Host ASP.NET Web API and SignalR

Startup.cs

[assembly: OwinStartup(typeof(ERPSelfHostServer.Startup))]
namespace ERPSelfHostServer
{
    public class Startup
    {
        // This code configures Web API. The Startup class is specified as a type
        // parameter in the WebApp.Start method.
        public void Configuration(IAppBuilder appBuilder)
        {
            // Configure Web API for self-host. 
            HttpConfiguration config = new HttpConfiguration();
            config.Formatters.Remove(config.Formatters.XmlFormatter);
            config.Formatters.Add(new BrowserJsonFormatter());

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );


            // Any connection or hub wire up and configuration should go here
            appBuilder.MapSignalR();

            appBuilder.UseWebApi(config);
            
        }
    }
}

Program.cs

static void Main(string[] args)
        {
            // bind to all network interfaces
            //string baseAddress = "http://*:13602/";
            string baseAddress = "http://172.20.63.161:13602";

            using (WebApp.Start<Startup>(baseAddress))
            {
                Thread.Sleep(Timeout.Infinite);
            }
        }

References
http://www.asp.net/web-api/overview/hosting-aspnet-web-api/use-owin-to-self-host-web-api
http://anthonychu.ca/post/web-api-owin-self-host-docker-windows-containers/
http://stackoverflow.com/questions/21634333/hosting-webapi-using-owin-in-a-windows-service
http://stackoverflow.com/questions/20068075/owin-startup-class-missing
http://stackoverflow.com/questions/16642651/self-hosted-owin-and-urlacl