Home > other >  .NET 5 - Cookie authetication on challenge rewrite/re-execute instead of redirect
.NET 5 - Cookie authetication on challenge rewrite/re-execute instead of redirect

Time:04-05

We're currently migrating a .NET Framework 4.8 MVC app to .NET (Core) 5 and I'm encountering a challenge.

In the old Framework app, we implement AuthorizeAttribute where an override on HandleUnauthorizedRequest will clear the content of the response and 're'-execute another controller (with the help of IControllerFactory). This essentially creates a login landing page where the URL stays the same (same request). For each secured page with this attribute, we have a different page that we render (a 'sales pitch' as you will).

Now in .NET Core, authentication works differently than in .NET Framework. So I believe the approach described earlier will not work here. I think the correct approach is to configure cookie authentication to handle this. But we have two business requirements that make this a little challenging:

  1. No redirections; instead, show the login page on the first request, so basically, URL rewrite/re-execute instead of redirect
  2. Each page requiring authentication should render a different login page

My challenge lies in the first requirement. I need to find a way to render a different controller action on authentication. A URL re-execute will be fine, too, as we already do this in a custom UseStatusCodePages handler. The AddAuthentication().AddCookie(o => o.Events.OnRedirectToLogin = context => { ... } looks like the place where I could handle my logic, but nothing I tried works. A reason might be that this is too far into the middleware?

So the question is: how to do a rewrite or re-execute instead of a redirect on the authentication challenge?

CodePudding user response:

With the help of a colleague and looking at the source code of the UseStatusCodePages and UseRewriter features we have come up with a solution to the problem.

We ended up with implementing the OnRedirectToLogin event on the AddCookie. When this event gets triggered we find the URI of the current login page that we need to show. But instead of setting the ``context.RedirectUriwe instead add it to thecontext.HttpContext.Itemsdictionary. We then use custom middleware to look for that item and do the rewrite/re-execute there. Thereturn Task.CompletedTask;` makes sure nothing happens, no redirect. Without the middleware, we get a blank page.

services.AddAuthentication().AddCookie(o =>
{
    o.Events.OnRedirectToLogin = context =>
    {
        var loginPageUri = GetLoginLandingPageUri(context);

        context.HttpContext.Items[ReExecuteUriMiddleware.ReExecuteUriItemKey] = loginPageUri;

        return Task.CompletedTask;
    };
});

When the middleware sees that item in the Items dictionary we reset the endpoint information, so when we call the _next for the second time, the middleware pipeline will nicely re-execute the request, but with a different path.

public class ReExecuteUriMiddleware
{
    public const string ReExecuteUriItemKey = "ReExecuteUri";
    private readonly RequestDelegate _next;

    public ReExecuteUriMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        await _next.Invoke(httpContext);

        if (httpContext.Items[ReExecuteUriItemKey] is string reExecuteUri)
        {
            // An endpoint may have already been set. Since we're going to re-invoke the middleware pipeline we need to reset
            // the endpoint and route values to ensure things are re-calculated.
            httpContext.SetEndpoint(null);
            var routeValuesFeature = httpContext.Features.Get<IRouteValuesFeature>();
            routeValuesFeature?.RouteValues?.Clear();

            httpContext.Request.Path = reExecuteUri;

            await _next.Invoke(httpContext);
        }
    }
}

And ofcourse we added it in the Startup class right after the app.UseStatusCodePages().

app.UseMiddleware<ReExecuteUriMiddleware>();
  • Related