A primer on external login providers (social logins) with OWIN/Katana authentication middleware
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
Reblogged this on leastprivilege.com.
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.
See the github issue tracker.
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?
You would have to implement the custom grant type extension hooks in the OAuth2 AS MW.
Thanks. Dominick directed me here http://leastprivilege.com/2013/12/23/advanced-oauth2-assertion-flow-why/ which seems to describe exactly what I need. I will download the code here https://github.com/thinktecture/Thinktecture.AuthorizationServer and try it out hopefully it will have some guide as to how to implement these “hooks”.
Thanks again.
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.
This is by design — it’s what SSO is all about.
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
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.
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.
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.
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.
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.
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?
Nope, no idea — normally FB integration is super easy.
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
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.
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?
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.
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.
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.
I created a barebone project that reproduces this issue if anyone is interested – https://github.com/dmolesky/OwinCorsIssue
We are implementing Facebook Login on a project that shares different domain. How do we do this since OWIN starts earlier than Global.asax?
I’m not sure what you mean.
Hi Brock,
First of all thanks for your post, it helped me to understand what is going on with MVC5 template with individual accounts.
I’ve blogged my own implementation on http://bit.ly/1rfBOW6.
My Web API is built from scratch using OWIN, I’m not using the ready made template, I’ve added the middle-wares I need manually.
I’ve faced an issue described on the blog post when redirecting to the “redirect_uri” so I used another approach to implement the external logins.
It will be great when you have time to go over the blog post and give me your feedback about my implementation. You can check the demo application here: http://bit.ly/spaauth
Thanks in advance,
Taiseer
I am curious how one goes about CREATING an external provider? I.E. facebook has a provider, google has a provider, what is the correct terminology to search for when learning how to setup the side that is actually authenticating and creating claims for the user to be passed back into the MVC application?
I think you’re asking about creating an identity provider? You’d first have to decide what protocols to implement and go from there. At Thinktecture, we’ve built an open source identity provider which might work for you (rather than creating your own): https://github.com/thinktecture/Thinktecture.IdentityServer.v3/
Not sure if my previous comment went though, where can we get more information on how to CREATE an external login provider. IE The service that MVC talks to to actually authenticate and send claims information back? I have a situation in which a client wants us to “pass-through” authenticate their clients. This sounds like a perfect scenario for them to setup an external provider.
I know this is old, but it provides a good overview of this process and was useful. I have a question though, hopefully someone it still looking at this. :) Say my MVC site allows Google and FB login. I need some sort of ID to know who is who. How do I correlate my ID with the ID of the FB or Google login? In other words, assuming the user authenticates successfully with the external service, when the callback happens how do I know to which local user (to my MVC app) to assign those claims? It would seem that they would need to log in again on my side to link them up somehow…
You’d use google or FB’s ID to lookup your user and you’d use your own ID. How you do that is up to you — you can do it in the callbacks for the individual providers, or you can build your own “callback” page where you do that.
Hello, Is it possible to link an external login to an existing user? For instance, if I use the same email to register locally on the app and also login with Google, can I link the two accounts together instead of having to create a new account for Google with a different email? Thanks
Yes, you have to write that logic and keep track of the association yourself or in your identity management library.
Thanks a lot Brock. That’s helpful.
I have one more question, I’m trying to track down all method calls on authenticating users, external login, etc. Can you guide me through the source code on how authentication is first initiated? How many authentication managers are there, how one is selected, how the check for local cookies to see if user is authenticated etc. Just the start of the line I can then track the calls to have a better internal idea on how things and components are connected to each other. Many thanks.
Thank you!! you save my days