aspnet / Security

[Archived] Middleware for security and authorization of web apps. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.27k stars 600 forks source link

HttpContext.SignInAsync sets null to Identity claims data #1913

Closed fabercs closed 5 years ago

fabercs commented 5 years ago

Describe the bug

In my project, I want to make use of the cookie based authentication without using the identity infrastructure. I have my own user,role and claim tables. So I was following this.

Our project is in a tfs repo. When any member in the team tries to run the app, we always get a exception caused from HttpContext.User.Identity is filled up with all null values. But when debug it or run it for several times, mostly values somehow filled up, or not. Details below;

To Reproduce

Steps to reproduce the behavior:

  1. Using this version of ASP.NET Core 2.1 I have a middleware where I get a header value and and depend on that value get user data, then set these user data to Identity object.

UserSessionMiddleware

public async Task Invoke(HttpContext context, IUserService userService, ISessionHelper sessionHelper,
                                    ILogger<UserSessionMiddleware> logger)
{
    if (sessionHelper.IsAppSessionSet)
        await _next.Invoke(context);
    else
    {
        var employeeId = context.Request.Headers["headerVal"].ToString();
        UserEntity user = userService.GetUserData(employeeId);
        List<ClaimEntity> roleClaims = userService.GetUserRoleClaims(user.UserRoles.Select(ur => ur.RoleId).ToArray()).ToList();

        if (user == null)
        {
            await context.Response.WriteAsync($"User with Id: {employeeId} not found in database!");
            return;
        }
        #region Create user claims

        ClaimsIdentity claimsIdentity = new ClaimsIdentity(CreateUserClaims(user, roleClaims),
            CookieAuthenticationDefaults.AuthenticationScheme);

        ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

        await context.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal,
            new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddDays(7) });

        #endregion
        sessionHelper.User = user;
        sessionHelper.IsAppSessionSet = true;

        var sessionId = context.Session.Id;
        logger.LogInformation($"SessionId: {sessionId}, {user.EmployeeId}/{user.FullName} has started session");
        await _next.Invoke(context);
    }
}

As the request reaches to the view component where to get user data and bind to the view elements, it throws null object exception. In fact when I inspect the context's User.Identity object after context.SignInAsync, I can see that a new object sits there however all values are null, as seen in the ss.

x

public class ProfileComponent : ViewComponent
{
    private ISessionHelper _sessionHelper;
    private IHttpContextAccessor _contextAccessor;

    public ProfileComponent(ISessionHelper sessionHelper, IHttpContextAccessor contextAccessor)
    {
        _sessionHelper = sessionHelper;
        _contextAccessor = contextAccessor;
    }
    public IViewComponentResult Invoke()
    {
        ClaimsIdentity identity = (ClaimsIdentity)_contextAccessor.HttpContext.User.Identity;
        dynamic user = new ExpandoObject();

        //this line throws exception
        user.EmployeeId = identity.FindFirst(ClaimTypes.NameIdentifier).Value;
        user.Name = identity.Name;
        user.Email = identity.FindFirst(ClaimTypes.Email).Value;
        user.Roles = identity.FindAll(ClaimTypes.Role).Select(i => i.Value).ToList();
        ViewBag.User = user;
        return View();
    }
}

Expected behavior

Expected behavior is to get claims data in view component properly and constantly. For now this code is working on my computer, and same not working on my firend's.

fabercs commented 5 years ago

I guess the User in HttpContext is not being set yet in ongoing request. It is filled up on subsequent requests, I saw that by changing the last line in UserSessionMiddleware await _next.Invoke(context); to context.Response.Redirect("Home")

If this is the case, is above approach a sensible way to make this work (making a 302 request) for my case?

Tratcher commented 5 years ago

Indeed, SignIn only affects future requests. You could set HttpContext.User yourself for the current request.

fabercs commented 5 years ago

got it, simply solved, thank you!!