I was written a post that introduces Authentication and Authorization in .NET. And this post just does some supplements to that post.

Cookies

I was used Authentication with cookies. That means the user identity informations would stored in cookie and response to the client, for example, I made a ClaimsPrincipal that stored user identity informations, and send to client via SignIn(claimsPrincipal, CookieAuthenticationDefaults.AuthenticationScheme) in controller:

var claims = new List<Claim>
{
new(ClaimTypes.Sid, <Id>,ClaimValueTypes.Sid, ISSUER, ISSUER),
new(ClaimTypes.Email, <Email>, ClaimValueTypes.Email, ISSUER, ISSUER)
};
var claimsIdentity = new ClaimsIdentity(
    claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

return SignIn(claimsPrincipal, CookieAuthenticationDefaults.AuthenticationScheme);

SignIn will create a SignInResult for Response, return it from controller is very important. You could see the set-cookie header in response:

file

I preferring use cookies

There are several ways to do Authentication as like: cookie, JWT, etc. I would like to use cookie if I using browser-base client. for example: Web, Tauri, Electron, etc.

Because the modern broswers have more safety, and they do handle cookie automatically, they would receiving cookies and storing cookies, and append cookies when send request. Clients do not need to do extra process for cookies.

JWT would be more complicated then cookies.

The cookies that includes user identity informations could be contructed to Claims into User, so you can access they via User in Controller:

var id = Guid.Parse(User.FindFirstValue(ClaimTypes.Sid)!);
var email = User.FindFirstValue(ClaimTypes.Email)!;

Authorization

.NET also has several ways to do authorize, see authorization

If we just want to check the Claims, can use Claims-based authorization:

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy("RequiredId", policy => policy.RequireClaim(ClaimValueTypes.Sid));
});

But if we need some more complicate validation as like access database or other services, we would need the custom Authorization.

We need a new Attribute class that inherited AuthorizeAttribute and implemented IAuthorizationRequirement, IAuthorizationRequirementData, here I named it LoggedInAuthorizeAttribute:

public class LoggedInAuthorizeAttribute : AuthorizeAttribute, IAuthorizationRequirement, IAuthorizationRequirementData
{
    public IEnumerable<IAuthorizationRequirement> GetRequirements()
    {
        yield return this;
    }
}

This Attribute we would use to mark controllers that need authorization.

[HttpPost(), LoggedInAuthorize]
public async Task<IActionResult> Index()
{
    // ...
}

And we need to create a new class that implement AuthorizationHandler<LoggedInAuthorizeAttribute>

and write some complicate logic in

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LoggedInAuthorizeAttribute requirement)
{
}

And if we need other services as like access database, log, just injecting it!

private readonly DbContext _db;

public LoggedInAgeAuthorizationHandler(DbContext db)
{
    _db = db;
}

Then we can use it.

And if we passed authorization, should call context.Succeed(requirement);

or context.Fail(); when not passed.

If passed, it will continue to access controller, or response to client with status code 401.

Not forget call builder.Services.AddScoped<IAuthorizationHandler, LoggedInAgeAuthorizationHandler>(); to enable it.