After creating a redistributal package for a custom OWIN AuthenticationHandler that handles logins to an internally hosted Oauth2/SSO provider, I found something a little annoying.
When OWIN detects a 401 response and the AuthenticationMode is “Active,” it doesn’t capture the URL hash from the request.
If you’re writing an application, like a SPA, which takes advantages of URL hashes, this creates an issue since users will not be able to link/bookmark to pages that utilize the hash at the client/browser.
This occurs because when OWIN has an AuthenticationType defined that is set with an AuthenticaitonMode of “Active,” OWIN takes over the process of logging in entirely.
The other option is to enable “Passive” AuthenticationMode which means that OWIN doesn’t actively (sic) attempt to intercept 401 responses. When in passive mode, your application must issue a Challenge explicitly to initiate the OWIN authorization flow.
While Active mode is simpler, I wound up having to use Passive mode on my external provider in order to redirect initially to an MVC controller. This controller returns a view which contains JavaScript that will detect any hash fragment in the URL and then redirect, yet again, to another MVC controller action with the hash as a query parameter. In the last redirect, the OWIN Challenge is issued.
The basic OWIN configuration in my application remains essentially the same as before:
var appCookieOptions = new CookieAuthenticationOptions() { AuthenticationType = SSOSettings.AuthenticationType, AuthenticationMode = AuthenticationMode.Active, LoginPath = new PathString(AppSettings.LoginPath), LogoutPath = new PathString(AppSettings.LogoutPath), CookieSecure = CookieSecureOption.Always, CookieName = AppSettings.AuthCookieName, CookieDomain = AppSettings.AuthDomain, CookiePath = "/", SlidingExpiration = true, }; app.UseCookieAuthentication(appCookieOptions); // Configure our Middleware handler app.SetDefaultSignInAsAuthenticationType(SSOSettings.AuthenticationType); var options= new SSOAuthenticationOptions(AppSettings.AuthClientId, AppSettings.AuthClientSecret); app.UseSSOAuthentication(options);
Note that the CookieAuthentication is Active while the Middleware Provider is configured as Passive. This makes the Cookie provider redirect to our specified LoginPath on Unauthorized/Unauthenticated requests. That LoginPath will return the View with JavaScript to strip the hash. For the sake of brevity, you can see a sampling of that code here.
When the “LoginRedirect” redirects back to our primary Login action, we must issue the challenge:
public virtual ActionResult Login(string redirectUrl) { AuthManager.Challenge(SSOSettings.AuthenticationType); return new HttpUnauthorizedResult(); }
The difference, though, is in the AuthenticationHandler itself. It now is configured through a web.config setting that allows the consuming application to specify the AuthenticationMode as active/passive. In our AuthenticationHandler, we want to modify the InvokeAsync slightly so that we’re detecting whether there is a RedirectUrl involved so that the redirect is handled properly:
public override async Task<bool> InvokeAsync() { if (Options.CallbackPath.HasValue && Options.CallbackPath == Request.Path) { var ticket = await AuthenticateAsync(); if (ticket != null) { Context.Authentication.SignIn(ticket.Properties, ticket.Identity); var hasAppReturnUrl = false; var redirectUrl = string.Empty; if (Options.AuthenticationMode == AuthenticationMode.Passive) { if (ticket.Properties.RedirectUri.ToLower().Contains("redirecturl")) { var index = ticket.Properties.RedirectUri.IndexOf("="); if (index != -1) { hasAppReturnUrl = true; redirectUrl = ticket.Properties.RedirectUri.Substring(index + 1); redirectUrl = HttpUtility.UrlDecode(redirectUrl).Replace("//", "/"); } } } if (hasAppReturnUrl) { Response.Redirect(redirectUrl); } else { Response.Redirect(ticket.Properties.RedirectUri); } // Prevent further processing by the owin pipeline. return true; } } // Let the rest of the pipeline run. return false; }
All this code is doing is, in the event that AuthMode is Passive, looking for a “RedirectUrl” query paramter in the Ticket’s RedirectUri property. If it finds it, then it will use it for the response redirect. Otherwise, you’ll get into an infinite redirect loop due to the way OWIN sets the initial RedirectUri property.
Having to modify the AuthenticationHandler and then also implementing the controller actions/views to intercept the URL hash has me wavering a little bit on whether there is any advantage of using OWIN for making the External OAuth2 call over just using DotNetOpenAuth.