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:
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.
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.