Hooking into the OWIN Middleware Pipeline

Home / Hooking into the OWIN Middleware Pipeline

With my previous endeavors using OWIN Middleware for an SSO Authentication system, I used DotNetOpenAuth as the client to make the OAuth Authorization Code grant flow. However, after a bit of research, I’ve learned that hooking into the OWIN Middleware can completely eliminate the need to use DotNetOpenAuth.

Additionally, eliminating DotNetOpenAuth and its dependencies makes creating a Nuget reusable package for the applications that I intended to use with the SSO/OAuth2 mechanism much simpler.


OWIN’s middleware interface is very similar to the old IHttpHandler interface. There are (3) primary components, though:

  • AuthenticationMiddleware
  • AuthenticationHandler
  • AuthenticationOptions (our TOptions)

What do we really want to get out of hooking into the Middleware authentication pipeline? Primarily, I want it to do all of the things in a reusable (automagic) manner that I was doing procedurally with DotNetOpenAuth. Predominately, this includes interceptiong 401 (not-authenticated) requests and also intercepting the OAuth2 callbacks that allow requesting an access token.

The previous flow that I used with DotNetOpenAuth required creating an MvcController, providing Login/LoginCallback mechanisms that would make the approriate OAuth2 calls and also handle creating a Claims-based identity.

That flow looked like this:

public partial class AccountController : Controller
{
    private WebServerClient _webServerClient;

    public virtual ActionResult Login()
    {
        InitializeWebServerClient();

        // Callback URI
        var returnUri = new Uri(AppSettings.AuthLoginCallback);
        var userAuthorization = _webServerClient.PrepareRequestUserAuthorization(new[] { "identity", "roles" }, returnUri);
        userAuthorization.Send(HttpContext);
        Response.End();
        return null;
    }

    public virtual ActionResult LoginCallback()
    {
        InitializeWebServerClient();
        var authorizationState = _webServerClient.ProcessUserAuthorization(Request);
        if (authorizationState != null)
        {
            var accessToken = authorizationState.AccessToken;
            var refreshToken = authorizationState.RefreshToken;

            // Store the token if we want, and redirect
            return Redirect("/");
        }

        return View(MVC.Shared.Views.ViewNames.Error);
    }

    public virtual ActionResult Logoff()
    {
        var returnUrl = Url.Action(MVC.Account.ActionNames.LogoutCallback, MVC.Account.Name, null, Request.Url.Scheme, Request.Url.Host);
        var url = string.Format("{0}{1}?RedirectUrl={2}", AppSettings.AuthServer, AppSettings.LogoutPath, HttpUtility.UrlEncode(returnUrl));
        return Redirect(url);
    }

    private void InitializeWebServerClient()
    {
        var authorizationServerUri = new Uri(AppSettings.AuthServer);
        var authorizationServer = new AuthorizationServerDescription
        {
            AuthorizationEndpoint = new Uri(authorizationServerUri, AppSettings.AuthPath),
            TokenEndpoint = new Uri(authorizationServerUri, AppSettings.AuthTokenPath)
        };
        _webServerClient = new WebServerClient(authorizationServer, AppSettings.AuthClientId, AppSettings.AuthClientSecret);
    }
}

That’s not too bad. It uses the DotNetOpenAuth WebServerClient in conjunction with OWIN’s CookieAuthenticaiton to login the user and to reuse/share the cookie from the SSO site.

Obviously, I don’t want to have to copy/paste that code into every application. I’d also like to remove the DotNetOpenAuth dependency since OWIN is really intended to handle authentication all by itself.

This a bit more verbose when hooking into OWIN’s authentication pipeline, though. We have to make the requisite TCP/IP requests, for example, to the OAuth2 endpoint ourselves. Less abstraction has its advantages, though.

Let’s take a look at the initial implementation of an OWIN AuthenticationHandler:

public class SSOAuthenticationHandler : AuthenticationHandler<SSOAuthenticationOptions>
{
    private readonly ILogger _logger;
    private readonly HttpClient _httpClient;

    public SSOAuthenticationHandler(HttpClient httpClient, ILogger logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    /// <summary>
    /// Perform the core authentication - this method will initiate the authorization_code grant flow
    /// </summary>
    /// <returns></returns>
    protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
    {
        AuthenticationProperties props = null;

        try
        {
            string code = null;
            string state = null;

            IReadableStringCollection query = Request.Query;
            IList<string> values = query.GetValues("code");
            if (values != null && values.Count == 1)
            {
                code = values[0];
            }

            values = query.GetValues("state");
            if (values != null && values.Count == 1)
            {
                state = values[0];
            }

            props = Options.StateDataFormat.Unprotect(state);

            if (props == null)
            {
                return null;
            }

            if (!ValidateCorrelationId(props, _logger))
            {
                var authTicket = new AuthenticationTicket(null, props);
                return authTicket;
            }

            string requestPrefix = Request.Scheme + "://" + Request.Host;
            string redirectUri = requestPrefix + Request.PathBase + Options.CallbackPath;

            // Build up the body for the token request
            var body = new List<KeyValuePair<string, string>>();
            body.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
            body.Add(new KeyValuePair<string, string>("code", code));
            body.Add(new KeyValuePair<string, string>("redirect_uri", redirectUri));
            body.Add(new KeyValuePair<string, string>("client_id", Options.ClientId));
            body.Add(new KeyValuePair<string, string>("client_secret", Options.ClientSecret));

            // Request the token
            HttpResponseMessage tokenResponse = await _httpClient.PostAsync(string.Format("{0}/{1}", AppSettings.AuthServer, AppSettings.AuthTokenPath), new FormUrlEncodedContent(body));
            tokenResponse.EnsureSuccessStatusCode();
            string text = await tokenResponse.Content.ReadAsStringAsync();

            // Deserialize the token response
            dynamic response = JsonConvert.DeserializeObject<dynamic>(text);
            string accessToken = (string)response.access_token;
            string expires = (string)response.expires_in;
            string refreshToken = null;
            if (response.refresh_token != null)
                refreshToken = (string)response.refresh_token;

            // Get the User info using the bearer token
            var request = new HttpRequestMessage()
            {
                RequestUri = new Uri(string.Format("{0}/api/user", AppSettings.AuthServer)),
                Method = HttpMethod.Get
            };

            request.Headers.Add("Authorization", string.Format("Bearer {0}", accessToken));

            HttpResponseMessage userReponse = await _httpClient.SendAsync(request);
            userReponse.EnsureSuccessStatusCode();
            Task<Stream> streamTask = userReponse.Content.ReadAsStreamAsync();
            Stream stream = streamTask.Result;
            var sr = new StreamReader(stream);
            var json = sr.ReadToEnd();
            var converter = new ExpandoObjectConverter();
            dynamic user = JsonConvert.DeserializeObject<ExpandoObject>(json, converter);

            // Create the identity and add all of the roles
            var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, user.name) }, Options.SignInAsAuthenticationType);

            foreach (var role in user.roles)
            {
                identity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, role));
            }

            // Create a delegate to filter out non-name claims
            Func<dynamic, bool> matchClaimType = x => x.type != ClaimsIdentity.DefaultNameClaimType;

            // Add the other claims minus the name since we already added that
            foreach (var claim in System.Linq.Enumerable.Where<dynamic>(user.claims, matchClaimType))
            {
                identity.AddClaim(new Claim(claim.type, claim.value));
            }

            return new AuthenticationTicket(identity, props);
        }
        catch (Exception ex)
        {
            _logger.WriteError(ex.Message);
        }

        return new AuthenticationTicket(null, props);
    }

    /// <summary>
    /// Respond to an auth (401) challenge
    /// </summary>
    /// <returns></returns>
    protected override Task ApplyResponseChallengeAsync()
    {
        if (Response.StatusCode == 401)
        {                
            var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);

            // Only respond to challenges that match the SSOAuthenticationHandler
            if (challenge != null)
            {
                string baseUri = string.Format("{0}{1}{2}{3}", Request.Scheme, Uri.SchemeDelimiter, Request.Host, Request.PathBase);
                string currentUri = string.Format("{0}{1}{2}", baseUri, Request.Path, Request.QueryString);
                string redirectUri = string.Format("{0}{1}", baseUri, Options.CallbackPath);

                var props = challenge.Properties;

                if (string.IsNullOrEmpty(props.RedirectUri))
                {
                    props.RedirectUri = Request.Uri.ToString();
                }

                GenerateCorrelationId(props);

                // Scopes are sent as comma separated
                string scopes = string.Join(" ", Options.Scopes);
                    
                string state = Options.StateDataFormat.Protect(props);
                string authorizationEndpoint = string.Format("{0}{1}?response_type=code&client_id={2}&redirect_uri={3}&scope={4}&state={5}",
                    AppSettings.AuthServer,
                    AppSettings.AuthPath,
                    Uri.EscapeDataString(Options.ClientId),
                    Uri.EscapeDataString(redirectUri),
                    Uri.EscapeDataString(scopes),
                    Uri.EscapeDataString(state)
                );

                Response.Redirect(authorizationEndpoint);
            }
        }

        return Task.FromResult<object>(null);
    }

    /// <summary>
    /// Invoked for every request.  We'll perform the signin when redirected back from the authentication provider
    /// </summary>
    /// <returns></returns>
    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);
                Response.Redirect(ticket.Properties.RedirectUri);

                // Prevent further processing by the owin pipeline.
                return true;
            }
        }

        // Let the rest of the pipeline run.
        return false;
    }
}

As I said before, this implementation becomes a bit verbose. But bear with me.

The AuthenticateCoreAsync method is going to be called on any action within the application that requires the user to be Authenticated. Since this is part of the authorization code grant flow, primarily, you can see that the method will look for code/state query parameters and act on this accordingly.

If the correlation id is valid, then we simply return a ticket and exit. However, if the correlation id is not valid, then we are at a point in the flow where we have an authorization code. We make the request for the access token from the auth code at this point. Once the access token is returned, I make a simple call to a protected resource that will simply decrypt the token and allow me to grab the claims/etc. A new identity/ticket are created and returned.

The other main method in this class is ApplyResponseChallengeAsync. This method is called when OWIN detects that a 401 response is being processed. We perform a simple lookup using OWIN’s Helper methods to determine if our AuthenticaitonHandler should process this challenge request. If it should, then we perform the initial redirect of the user’s browser to our OAuth2/SSO login endpoint with the appropriate query parameters.

Further down in the code, you can see the InvokeAsync that will perform the actual signin (and cookie creation depending on your configuration) using OWIN’s AuthenticaitonManager.

Next up we have the AuthenticationMiddleware class implementation:

public class SSOAuthenticationMiddleware : AuthenticationMiddleware<SSOAuthenticationOptions>
{
    private readonly HttpClient _httpClient;
    private readonly ILogger _logger;

    public SSOAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, SSOAuthenticationOptions options)
        : base(next, options)
    {
        if (string.IsNullOrEmpty(Options.SignInAsAuthenticationType))
        {
            options.SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType();
        }

        if (string.IsNullOrWhiteSpace(Options.ClientId))
            throw new ArgumentException("ClientId");

        if (string.IsNullOrWhiteSpace(Options.ClientSecret))
            throw new ArgumentException("ClientSecret");

        _logger = app.CreateLogger<SSOAuthenticationMiddleware>();
        _httpClient = new HttpClient(ResolveHttpMessageHandler(Options))
        {
            Timeout = Options.BackchannelTimeout,
            MaxResponseContentBufferSize = 1024 * 1024 * 10
        };

        if (options.StateDataFormat == null)
        {
            var dataProtector = app.CreateDataProtector(typeof(SSOAuthenticationMiddleware).FullName,
                options.AuthenticationType);

            options.StateDataFormat = new PropertiesDataFormat(dataProtector);
        }
    }

    // Called for each request, to create a handler for each request.
    protected override AuthenticationHandler<SSOAuthenticationOptions> CreateHandler()
    {
        return new SSOAuthenticationHandler(_httpClient, _logger);
    }

    private HttpMessageHandler ResolveHttpMessageHandler(SSOAuthenticationOptions options)
    {
        HttpMessageHandler handler = options.BackchannelHttpHandler ?? new WebRequestHandler();

        // If they provided a validator, apply it or fail.
        if (options.BackchannelCertificateValidator != null)
        {
            // Set the cert validate callback
            var webRequestHandler = handler as WebRequestHandler;
            if (webRequestHandler == null)
            {
                throw new InvalidOperationException("ValidatorHandlerMismatch");
            }
            webRequestHandler.ServerCertificateValidationCallback = options.BackchannelCertificateValidator.Validate;
        }

        return handler;
    }

As you can see, this class is primarily a factory class that allows OWIN to create/access the AuthenticationHandler. So, I’m using it to create a few reusable pieces such as an HttpClient, Logger, etc.

Our AuthenticaitonOptions class is just that. It defines a few key options such as AuthenticaitonType which OWIN uses to know whether or not our AuthenticationHandler should process/intercept the response/request.

public class SSOAuthenticationOptions : AuthenticationOptions
{
    public SSOAuthenticationOptions(string clientId, string clientSecret)
        : base(SSOConstants.DefaultAuthenticationType)
    {
        Description.Caption = SSOConstants.DefaultAuthenticationType;
        CallbackPath = new PathString("/account/logincallback");
        AuthenticationMode = AuthenticationMode.Active;
        BackchannelTimeout = TimeSpan.FromSeconds(60);
        ClientId = clientId;
        ClientSecret = clientSecret;
        Scopes = new List<string>
        {
            "identity", "roles"
        };
    }

    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
    public PathString CallbackPath { get; set; }
    public string SignInAsAuthenticationType { get; set; }
    public List<string> Scopes { get; private set; }
    public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }

    public TimeSpan BackchannelTimeout { get; set; }
    public HttpMessageHandler BackchannelHttpHandler { get; set; }
    public ICertificateValidator BackchannelCertificateValidator { get; set; }
}

Last but not least, we can define an extention method for IAppBuilder that will allow use to easily tell OWIN to use our AuthenticationMiddleware (factory):

public static IAppBuilder UseSSOAuthentication(this IAppBuilder app, SSOAuthenticationOptions options)
{
    if (app == null)
        throw new ArgumentNullException("app");
    if (options == null)
        throw new ArgumentNullException("options");

    app.Use(typeof(SSOAuthenticationMiddleware), app, options);
    return app;
}

Finally, all that’s need to now hook our code into the OWIN pipeline is configure it in our Startup.Auth:

public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        var appCookieOptions = new CookieAuthenticationOptions()
        {
            AuthenticationType = SSOConstants.DefaultAuthenticationType,
            AuthenticationMode = AuthenticationMode.Active,
            LogoutPath = new PathString(AppSettings.LogoutPath),
            CookieSecure = CookieSecureOption.Always,
            CookieName = AppSettings.AuthCookieName,
            CookieDomain = AppSettings.AuthDomain,
            CookiePath = "/",
            SlidingExpiration = true,
        };

        // Configure the SSO Authentication - it is implemented as an external provider that hooks into the OWIN Middleware
        app.SetDefaultSignInAsAuthenticationType(SSOConstants.DefaultAuthenticationType);
        var ssoOptions = new SSOAuthenticationOptions(AppSettings.AuthClientId, AppSettings.AuthClientSecret);
        app.UseSSOAuthentication(ssoOptions);

    }
}

Now that we have all of this hooked up, we could effectively (tweaking within reason) let our application use a generic “external” OAuth2 endpoint and login form for authenticating our user. In my use case, this is already working quite well and I plan to wrap it into a Nuget package to keep things DRY.

PS – here are some nice resources I have found:

http://coding.abel.nu/2014/06/writing-an-owin-authentication-middleware/
https://github.com/RockstarLabs/OwinOAuthProviders/tree/master/Owin.Security.Providers

2 thoughts on “Hooking into the OWIN Middleware Pipeline

    1. Most of what you need is in the various posts. Are you looking for a pre-built Visual Studio project that includes the handler and a server? I can put one together, although hadn’t really anticipated doing so.

Leave a Reply