How are OwinContext.Request.Path and PathBase populated?

Appetere picture Appetere · Sep 3, 2014 · Viewed 9.1k times · Source

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.

Answer

Appetere picture Appetere · Sep 4, 2014

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.