Benjamin Abt
Benjamin Abt

Reputation: 1838

How to extend and validate session in ASP.NET Core Identity?

We want to offer the users to manage their login sessions. This worked so far pretty easy with ASP.NET Core and WITHOUT the Identity Extensions.

https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

But how can we invoke this validation with ASP.NET Core Identity?

Problem we have:

It feels the ASP.NET Core Identity is still not that extensible and flexible :(

Upvotes: 2

Views: 5420

Answers (1)

Benjamin Abt
Benjamin Abt

Reputation: 1838

Unfortunately, this area of ASP.NET Identity is not very well documented, which I personally see as a risk for such a sensitive area.

After I've been more involved with the source code, the solution seems to be to use the SignIn process of the SignIn Manager.

The basic problem is that it's not that easy to get your custom claims into the ClaimsIdentity of the cookie. There is no method for that. The values for this must under no circumstances be stored in the claims of the user in the database, as otherwise every login receives these claims - would be bad.

So I created my own method, which first searches for the user in the database and then uses the existing methods of the SignInManager.

After having a ClaimsIdentity created by the SignIn Manager, you can enrich the Identity with your own claims. For this I save the login session with a Guid in the database and carry the id as a claim in the cookie.

    public async Task<SignInResult> SignInUserAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
    {
        DateTimeOffset createdLoginOn = DateTimeOffset.UtcNow;
        DateTimeOffset validTo = createdLoginOn.AddSeconds(_userAuthOptions.ExpireTimeSeconds);

        // search for user
        var user = await _userManager.FindByNameAsync(userName);
        if (user is null) { return SignInResult.Failed; }


        // CheckPasswordSignInAsync checks if user is allowed to sign in and if user is locked
        // also it checks and counts the failed login attempts
        var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
        if (attempt.Succeeded)
        {
            // TODO: Check 2FA here

            // create a unique login entry in the backend
            string browserAgent = _httpContextAccessor.HttpContext.Request.Headers["User-Agent"];

            Guid loginId = await _eventDispatcher.Send(new AddUserLoginCommand(user.Id, user.UserName, createdLoginOn, validTo, browserAgent));

            // Write the login id in the login claim, so we identify the login context
            Claim[] customClaims = { new Claim(CustomUserClaims.UserLoginSessionId, loginId.ToString()) };

            // Signin User
            await SignInWithClaimsAsync(user, isPersistent, customClaims);

            return SignInResult.Success;
        }

        return attempt;
    }

With each request I can validate the ClaimsIdentity and search for the login id.

public class CookieSessionValidationHandler : CookieAuthenticationEvents
{
    public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
    {
        ClaimsPrincipal userPrincipal = context.Principal;

        if (!userPrincipal.TryGetUserSessionInfo(out int userId, out Guid sessionId))
        {
            // session format seems to be invalid
            context.RejectPrincipal();
        }
        else
        {
            IEventDispatcher eventDispatcher = context.HttpContext.RequestServices.GetRequiredService<IEventDispatcher>();

            bool succeeded = await eventDispatcher.Send(new UserLoginUpdateLoginSessionCommand(userId, sessionId));
            if (!succeeded)
            {
                // session expired or was killed
                context.RejectPrincipal();
            }
        }
    }
}

See also https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1#react-to-back-end-changes

Upvotes: 1

Related Questions