3

Situation: I have a (intranet only) website which utilizes logging in with username/password. It is also possible to use the windows user to login.

What I do: When a user goes to a page on this site and is not yet logged in with username/password, the user will instead be logged in with windows authentication. This results in his HttpContext.User object being of type WindowsPrincipal. I then automatically login the user into his corresponding user via SignInManager<User>.SignInAsync(). Now the user has all the roles as specified in my database, instead of the roles given by the AD.

Problem with current approach: Originally I had this rewrite only at the default start page. Thus I could reload this page explicitly after doing the sign in part. If the user would instead go to a different subpage (e.g. via bookmark), this rewriting of the authentication type would be skipped => he stays a WindowsPrincipal.

My attempt at a solution: Do the log in part via middleware. I have a service (which implements IMiddleware, compare https://stackoverflow.com/a/52214120/2968106) which does this in its InvokeAsync() Method.

Problem with my solution: Logging in as a user with my middleware happens too late, so that the page in question returns "Access Denied", since the user is missing the required role at that moment. Loading the page again fixes this. I have called the middleware in the Configure method of Startup.cs after app.UseAuthentication() and app.UseSession(). Moving it before these two doesn't change anything.

Question: a) Is this approach feasible; b) At which point should I call this, or is there a method to re-request the given page with all parameters intact from inside the middleware, after the login has finished?

In regard to b): Is there some kind of function to restart the pipeline from the beginning, which I could call in my middleware after the SignInAsync()?

Edit:

1) My Middleware

public class WindowsPrincipalToClaimPrincipal :IMiddleware
{
    private readonly SignInManager<User> _signInManager;
    private readonly UserService _userService;
    public WindowsPrincipalToClaimPrincipal(SignInManager<User> signInManager, UserService userService)
    {
        _signInManager = signInManager;
        _userService = userService;
    }

    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        if (context.User is WindowsPrincipal)
        {
            _signInManager.SignInAsync(_userService.GetCurrentUser(), false).Wait();
        }
        return next(context);
    }
}

2) Relevant parts of the Startup.cs

//At the end of ConfigureServices()
services.AddScoped<WindowsPrincipalToClaimPrincipal>();

//Inside Configure(), before app.UseMvc(...);
app.UseAuthentication();
app.UseSession();
app.UseMiddleware<WindowsPrincipalToClaimPrincipal>(); //moving this line to the beginning of Configure() doesn't change anything.
MilConDoin
  • 734
  • 6
  • 24

1 Answers1

3

It seems the answer was easier then expected. I'll have to try more in detail if this works in all situations as expected, but in the short run, I found the following potential solution:

In the middleware add the following line after the SignInAsync():

context.User = _signInManager.CreateUserPrincipalAsync(_userService.GetCurrentUser()).Result;

I was so used to the user being readonly (like when you use the PageModel.User), that I didn't even think of the possibility to write to that property.

Edit: Another important point: Don't call the middleware too early. It should be called after app.useAuthentication(), or you'll lose the ability to login with a normal account.

MilConDoin
  • 734
  • 6
  • 24