Detect Online Users using Circuit Handler in ASP.NET Blazor Server

public class UserCircuitHandler : CircuitHandler
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserCircuitHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public override async Task OnConnectionUpAsync(Circuit circuit,
        CancellationToken cancellationToken)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        var userAgent = httpContext.Request.Headers["User-Agent"];
        string uaString = userAgent.ToString();
        var uaParser = Parser.GetDefault();
        ClientInfo c = uaParser.Parse(uaString);
        Console.WriteLine(httpContext.User.Identity.Name);
    }

    public override async Task OnConnectionDownAsync(Circuit circuit,
        CancellationToken cancellationToken)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        var userAgent = httpContext.Request.Headers["User-Agent"];
        string uaString = userAgent.ToString();
        var uaParser = Parser.GetDefault();
        ClientInfo c = uaParser.Parse(uaString);
        Console.WriteLine(httpContext.User.Identity.Name);
    }
}

References
https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/signalr?view=aspnetcore-7.0#blazor-server-circuit-handler
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/server/additional-scenarios?view=aspnetcore-7.0#circuit-handler-to-capture-users-for-custom-services

Apply Authorization to All Pages in Blazor

Program.cs

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
});

builder.Services.ConfigureApplicationCookie(options =>
{
    options.ExpireTimeSpan=TimeSpan.FromDays(14);
    options.SlidingExpiration = true;
    options.Cookie.Name = "BI";
    options.Cookie.HttpOnly = true;
    options.LoginPath = "/Login";
    options.LogoutPath="/Logout";
});

To apply authorization to all pages in Blazor, you have to add:

@attribute [Microsoft.AspNetCore.Authorization.Authorize]

…to your _Imports.razor file.

@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]

…on pages that don’t require authorization.

References
https://stackoverflow.com/questions/60840986/blazor-redirect-to-login-if-user-is-not-authenticated
https://stackoverflow.com/questions/71434131/blazor-allow-anonymous-for-razor-page
https://stackoverflow.com/questions/50633896/asp-net-core-identity-change-login-url
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/secure-data?view=aspnetcore-7.0

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