Search code examples
owinkatanaopenid-connect

How are OwinContext.Request.Path and PathBase populated?


I'm writing my own OWIN middleware for OpenID Connect authorization code flow, based on other examples in the Katana Project.

As part of this I have to construct a couple of URIs, eg a Redirect URI and a Return URL.

Other examples in Katana do this by concatenating parts from the current request, for example in CookieAuthenticationHandler

loginUri =
    Request.Scheme +
    Uri.SchemeDelimiter +
    Request.Host +
    Request.PathBase +
    Options.LoginPath +
    new QueryString(Options.ReturnUrlParameter, currentUri);

My question is what rules govern what ends up in the two path properties:

OwinContext.Request.Path
OwinContext.Request.PathBase

I've tried inspecting these properties as the request passes through different handlers in the pipeline below, for the request:

"https://localhost/Client/login" // Where Client is a virtual directory in IIS

The result:

  • In the mapped handler for /login, the PathBase = "/Client/Login".
  • But when the request gets to the ApplyResponseChallengeAsync method in my QuillCodeFlowHandler on the way back out for the same request, PathBase = "/Client" and Path = "/Login".

So without knowing the "rules" for how these values are populated, then later changed, it is hard to construct URIs using them. If anyone can explain, will be much appreciated.

An extract of my config is:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
    LoginPath = new PathString("/Login")
});

app.UseQuillCodeFlowAuthentication(new QuillCodeFlowOptions());

app.Map("/login", map =>
{
   map.Run(async ctx =>
   {
     if (ctx.Authentication.User == null ||
     !ctx.Authentication.User.Identity.IsAuthenticated)
     {                        
       var authenticationProperties = new AuthenticationProperties();
       [...]
       ctx.Authentication.Challenge(authenticationProperties,
                                    QuillCodeFlowDefaults.AuthenticationType);  

The OWIN specification gives some explanation and Microsoft.Owin.Host.HttpListener.GetPathAndQuery method seems to be where the path variables are set initially.


Solution

  • When using the construct

    app.Map("/login", map => [...]
    

    This uses

    Owin.MapExtensions.Map
    

    which constructs an instance of

    Microsoft.Owin.Mapping.MapMiddleware
    

    for the code which needs running.

    The behaviour I have seen is explained in the Invoke method of this middleware:

    public async Task Invoke(IDictionary<string, object> environment)
    {
        IOwinContext context = new OwinContext(environment);
    
        PathString path = context.Request.Path;
    
        PathString remainingPath;
        if (path.StartsWithSegments(_options.PathMatch, out remainingPath))
        {
            // Update the path
            PathString pathBase = context.Request.PathBase;
            context.Request.PathBase = pathBase + _options.PathMatch;
            context.Request.Path = remainingPath;
    
            await _options.Branch(environment);
    
            context.Request.PathBase = pathBase;
            context.Request.Path = path;
        }
        else
        {
            await _next(environment);
        }
    }
    

    Basically the code changes the Path and PathBase before it runs the delegate (await _options.Branch(environment) ), then sets these back to the original values after execution is complete.

    Hence the behaviour I had seen is explained.