Skip to content

A primer on external login providers (social logins) with OWIN/Katana authentication middleware

January 9, 2014

Like MVC 4, in MVC 5 and Visual Studio 2013 we have the ability to use external login providers (aka social logins) in our ASP.NET applications. The big change related to this from the prior version is that we no longer are using DotNetOpenAuth and instead are now using OWIN authentication middleware to handle the the various protocols to these external providers. Unfortunately the templates in Visual Studio 2013 related to these external providers are quite complex and can be overwhelming (I know, because it took many *days* of debugging and using reflector to really understand how it all worked). Anyway, that’s the point of this post – an attempt to explain in the least amount of code how this external authentication middleware works in Katana. I already made a couple of other posts related to this (cookie middleware and active vs. passive middleware), so those are assumed knowledge.

Katana ships with a few pieces of middleware to allow an ASP.NET application authenticate with external identity providers (like Google, Facebook, Live, Twitter, etc.). The middleware encapsulates the various protocols used to achieve this authentication and that’s a convenient abstraction for an application developer, since there are lots of different protocols that might be used. But it helps to learn the mechanics of the middleware to understand how you’ll be configuring and using it in your code.

When your application wants to authenticate a user with an external provider you indicate this to the AuthenticationManager on the OwinContext. Your code calls Challenge passing the name of the authentication middleware you want to invoke (so “Google”, “Facebook”, etc.). Also, as part of the mechanics, the middleware won’t kick in unless the current HTTP response is a 401 status code. When the external provider middleware you’ve triggered sees the 401 response from your application, it initiates the protocol to the external provider. This usually involves various HTTP redirects to the external provider so the user can login. Here’s what that code might look like:

public ActionResult ExternalLogin(string provider)
{
    var ctx = Request.GetOwinContext();
    ctx.Authentication.Challenge(
        new AuthenticationProperties{
            RedirectUri = Url.Action("Callback", new { provider })
        },
        provider);
    return new HttpUnauthorizedResult();
}

The above code gets the OwinContext from the request. We call Challenge passing an AuthenticationProperties which allows us to indicate a return URL (more on that later), and then the name of the provider we want to use. And, as mentioned above, we need to return a 401 status code.

Presumably your application would give the user a hyperlink with the provider as a query string parameter into this method so the user can indicate which provider they want to use. To dynamically discover what providers your application has configured, look into the GetAuthenticationTypes API on the AuthenticationManager, perhaps something like this:

var ctx = Request.GetOwinContext();
var providers =
    from p in ctx.Authentication.GetAuthenticationTypes(d => !String.IsNullOrWhiteSpace(d.Caption))
    select new
    {
        name = p.Caption,
        url = Url.Action("ExternalLogin", new { provider = p.AuthenticationType })
    };

So after your application triggers the authentication middleware and the protocol is all done, then the middleware does two important things: 1) it issues an authentication cookie (typically called the “ExternalCookie”) representing the outcome of the authentication from the external provider (and this cookie contains all the claims from the external provider for the user), and 2) it redirects the browser back into your application to a URL you have provided (from the AuthenticationProperties above). In this redirect is your chance to examine the claims inside of the external cookie to know who the user is, and then it’s up to you what to do next. Your application could store all of this information in a database, and/or it could just log the user in with the normal cookie middleware based upon the claims from the external provider. It just depends on your authentication requirements. Here’s an example of what that callback might look like:

public ActionResult Callback(string provider)
{
    var ctx = Request.GetOwinContext();
    var result = ctx.Authentication.AuthenticateAsync("ExternalCookie").Result;
    ctx.Authentication.SignOut("ExternalCookie");

    var claims = result.Identity.Claims.ToList();
    claims.Add(new Claim(ClaimTypes.AuthenticationMethod, provider));

    var ci = new ClaimsIdentity(claims, "Cookie");
    ctx.Authentication.SignIn(ci);

    return Redirect("~/");
}

The above code calls back into the AuthenticationManager to obtain the identity of the user from the external cookie. Once we have that, we immediately revoke the cookie by calling SignOut. Next we get the claims for the external authentication (this is where you might store them in a database or load additional claims from a database to add to the logged in user’s claims). In my example here, I chose to augment the claims with an additional claim so I know which provider was used. We then use those claims to log the user into the app using the normal cookie middleware.

The only other thing to look at is how to configure the middleware. This is configured in your Katana startup code, as such:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // this is the normal cookie middleware
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "Cookie",
            AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
        });

        // these two lines of code are needed if you are using any of the external authentication middleware
        app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "ExternalCookie",
            AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
        });

        // these lines of code configure the various providers we want to use
        app.UseFacebookAuthentication(new FacebookAuthenticationOptions {
            AppId = "id", AppSecret = "secret"
        });
        app.UseGoogleAuthentication();
    }
}

The first section (lines 5-10) is the normal cookie middleware used for authenticating users (see my previous post on this).

The second section (lines 13-18) of code relates to the mechanics of the external provider middleware. Recall the external provider middleware needs to issue a cookie to represent the outcome of the external authentication — these two lines set that up. Line 13 assigns into the app.Properties the name of the cookie middleware to use. All the external authentication middleware look for this hard-coded name. Line 14 then configures that cookie middleware. In the templates in Visual Studio 2013 these two lines are hidden behind the UseExternalSignInCookie call in Startup.Auth.cs. An alternative would be to set on each external provider the SignInAsAuthenticationType property, which is the name of the cookie middleware to use.

Finally, the third section (lines 21-24) in the above code configures which external providers we want to use.

HTH

26 Comments leave one →
  1. January 9, 2014 11:38 am

    Reblogged this on leastprivilege.com.

  2. January 15, 2014 10:50 am

    Hi, I’m new to this stuff (I’ve been out of the web world for a few years)(my, how its changed) and still trying to get my head around it all. I cloned your git project ‘thinktecture / Thinktecture.IdentityModel’ and tried to run one of the samples, but in samples\SystemWeb\ClaimsAuthorizeSample\app_start\WebApiConfig, line 16 is not compiling — it looks like you’re trying to add a ClaimsAuthorizeAttribute object to an HttpFilterCollection, and it can’t do it. Am I doing something wrong? I’d love to get this running, it seems exactly what I need. Many thanks for all your work.

  3. January 17, 2014 2:46 am

    Hi, I have a question specifically regarding Facebook Authentication. Facebook supports several authentication workflows depending on where you place your token and secret. For web based clients the process outlined in most of the examples currently on the web make sense where a user authenticates on fb and is issued a code (rather than an access token), he then hands this over to the server-side where the magic happens and he gets a bearer token to use for authentication.
    However for mobile clients that require deep integration with facebook on the device and connect using facebook APIs, the client is directly issued an access token from facebook sdk. How then does one go about exchanging that facebook access token for a bearer token for my own WebApi service while taking as much advantage of the already existing external provider infrastructure?

  4. February 5, 2014 4:37 am

    Do you have any idea how to go about logging out from facebook when signing out from the MVC site? Seems like this is a bit of a hole in the implementation – the user remains logged in to facebook after calling AuthenticationManager.Signout() and the next attempt to authenticate from the same browser will therefore automatically login.

    • February 5, 2014 9:08 pm

      This is by design — it’s what SSO is all about.

      • XYZ  XYZ  permalink
        May 17, 2014 7:44 pm

        Very insecure. What if you’re on a public computer. The next user that logs in, Is not challenged and is automatically logged in as the previous user.

        So, I’m dealing with the same issue as RS

        • May 19, 2014 1:08 am

          Well, the other project is that OAuth isn’t for authentication and thus the social provides all have custom ways to logout. A better/newer protocol like OIDC can address this issue since it supports single signout.

  5. Dave permalink
    April 19, 2014 10:15 pm

    Hi I’m attempting to implement the external facebook auth with Angular JS as my front end. Since the angular is hosted on a different web-server I have to enable CORS in my Web APi project. When I return the ChallengeResult() the redirect to the facebook login page is returning a No-Acces-Control-Allow-Origin. I notice the redirect request has a Origin header with a null value. I’m thinking thats the issue. Do you know if that is the issue? and if it is, do you know how to go about setting that? IT comes from the location header in the ChallengeREsult reponse.

    The SPA template doesn’t have this issue because its not making the original requests from another domain.

    • April 20, 2014 7:17 pm

      Sorry, I’m a little confused on the details in your question. I can’t tell which login page you mean, and I’m not sure how your angular code is trying to authenticate with your Web API app (and thus how it’s invoking it). If you have an angular app in a separate app trying to authenticate to a Web API app, then it seems you should be using the OAuth2 implicit flow to the Web API authorize endpoint and then that should simply be redirecting you to facebook if you’re not yet authenticated.

      • Dave permalink
        April 21, 2014 7:44 am

        Thanks for responding. Yes my angular app is hosted on a different web-server. I am using the OAuth2 implicit flow to the Web API authorize endpoint. The problem comes in when it tries to redirect me to the facebook login page. The redirect request is giving me a No Access-Control-Allow-Origin error. When I view the headers on the redirect request I see there is a Origin header with a null value. I think this is the issue, but do not know why its there or how to remove/set it since its coming from the redirect.

        I see the redirect is simply the Owin Challenge setting Location header in the response, but don’t know how to do anything after that.

        Do you have any ideas? Thanks.

        • April 21, 2014 9:22 am

          Well, there’s no CORS involved with the Facebook middleware — it’s all redirect based, and no Ajax from the browser, so it sounds like a red herring.

          If you’re getting an error code back from facebook then I suspect you have something misconfigured about your OAuth settings in FB or in your app.

          • Dave permalink
            April 21, 2014 9:38 am

            So its not an issue that my redirect request has a Origin header on it with a null value? Any ideas on what I could have configured wrong?

          • April 21, 2014 9:42 am

            Nope, no idea — normally FB integration is super easy.

  6. Dave permalink
    April 21, 2014 9:46 am

    If I use the standard template, the only difference is that their client is hosted on the same server where as my angular front end is hosted on a different server. Also, the facebook redirect request on the SPA template doesn’t have the Origin header, where mine does. Thats what leads me to believe thats messing things up

    • April 21, 2014 9:57 am

      I guess I am still confused — not sure where it’s failing. It sounded like it’s failing from FB back to your API app to authenticate from facebook, but now it sounds as if you’re failing once you’re back in your angular app. Still there’s no CORS between the angular app and the API app because you’re doing OAuth2 implicit flow. Once you have the token, to use it from the Angular app CORS will need to be enabled in your Web API endpoints, yes.

  7. Dave permalink
    April 21, 2014 9:48 am

    Could it be because the my client is hosted on localhost:8000 and my Web api is hosted on another localhost port? So the redirect is coming from a client that is on a different host than where the Owin auth is configured?

  8. Dave permalink
    April 21, 2014 10:01 am

    I have a detailed question posted on stack overflow with images of the requests/errors that are occuring (http://stackoverflow.com/questions/23171448/web-api-2-facebook-authentication-with-cors-enabled). If you have any time and are able to look and offer advice, it would be a huge help to me. I’ve been struggling with this issue for a while and can’t seem to find the solution. Thanks in advance.

  9. Dave permalink
    April 21, 2014 11:29 am

    So I wasn’t sure what you meant by OAuth2 implicit flow. I looked into it and that is actually not what I am trying to do. From my understanding that would be if I was trying to directly call the facebook api’s from my javascript. I am actually trying to use the Web Api 2 build in external auth code which uses Owin challenge to redirect to the external login page.

    Should my redirect url I pass be the url of my client or my web api? The redirect actually responds with the No Access-Control-Allow-Origin so it doesnt redirect to the page. However I can click on the link in the chrome dev tools and it does take me to the page. When I attempt to login It does hit my AccountController.GetExternalLogin method in my web api project, but the error is access_Denied.

    So my first issue is getting past the Access-Control-allow-Origin, which would allow the redirect to take the user to the login screen, next I would need to figure out why access is denied.

  10. Dave permalink
    April 21, 2014 1:43 pm

    Actually I’m sure its the NULL origin header that gets put in the redirect request. I can make the call to my API from fiddler, remove the Origin header, which then causes that origin header to not be put in the redirect, and it works.

  11. Dave permalink
    April 23, 2014 2:41 pm

    I created a barebone project that reproduces this issue if anyone is interested – https://github.com/dmolesky/OwinCorsIssue

  12. Lee permalink
    June 27, 2014 2:37 am

    We are implementing Facebook Login on a project that shares different domain. How do we do this since OWIN starts earlier than Global.asax?

Trackbacks

  1. Oren Novotny » External Auth in ASP.Net MVC/SPA/Web API apps

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: