Table of Contents
Allowing users to sign in using their existing credentials is often a great way to drive additional traffic to our applications, as demonstrated by a number of case studies by Google, Facebook and Twitter. Such technique is commonly called External Provider Authentication or Third-Party Authentication and we already talked about it a number of times, such as in this AUTH-focused post.
As you might already know, a lot of modern web development frameworks comes with a set of handy packages that will take care of the whole Third-Party Authentication process with the above mentioned providers, saving ourselves from dealing with the relevant amount of complexity of the OAuth2 authentication flow: the .NET Framework offers a perfect example of that with the ASP.NET Core Identity package, featuring a set of plug-and-play , easy-to-implement features to authenticate users with Facebook, Twitter, Google and Microsoft providers in a (almost) seamless way: for more info on that, read the Microsoft ASP.NET Core official docs.
In this post we'll briefly explain what is going on behind the hood, i.e. what the OAuth2-based authorization flow is and how it actually works.
The 2.0 release of OAuth, also known as OAuth2, is arguably the best known third-party authorization protocol nowadays: it supersedes the former release (OAuth1 or simply OAuth) originally developed by Blaine Cook and Chris Messina in 2006. OAuth 2 has quickly become the industry-standard protocol for authorization and is currently used by a gigantic amount of community-based websites and social networks, including Google, Facebook and Twitter. It basically works like this:
- Whenever an existing user requests a set of permissions to our application via OAuth, we open a transparent connection interface between them and a third-party authorization provider that is trusted by our application (for example, Facebook).
- The provider acknowledges the user and, if they have the proper rights, responds entrusting them with a temporary, specific access key.
- The user presents the access key to our application and will be granted access.
We can clearly see how easy it is to exploit this authorization logic for authentication purposes as well; after all, if Facebook says I can do something, shouldn't it also imply that I am who I claim to be? Isn't that enough?
The short answer is no. It might be the case for big players such as Facebook, because their OAuth 2 implementation implies that the subscriber receiving the authorization must have authenticated himself to their service first; however, this assurance is not written anywhere: considering how many websites are using it for authentication purposes we can assume that neither Facebook, nor Twitter nor Google won't likely change their actual behaviour, yet there are no guarrantees about it. Theoretically speaking, these websites could split their authorization system from their authentication protocol at any time, thus leading our application's authentication logic to an unrecoverable state of inconsistency. More generally, we can say that presuming something from something else is almost always a bad practice unless that assumption lies upon very solid, well-documented and (most importantly) highly guaranteed grounds. Nonetheless, since OAuth2 is arguably the most used standard for access delegation and SSO strategies around the web nowadays, we can say that such assumption has earned an high level of trust.
Let's do a quick recap of how OAuth2 authorization flow actually works for a standard web application:
- The user asks the web application to login with the external provider X.
- The web application prompts the user with a popup window containing a page directly hosted by the external provider X, from which he can:
- Login to X to authenticate himself there, unless he's not logged-in there;
- If/when logged-in, authorize the web application to use X as third-party authentication provider, thus giving it access to the minimum amount of required user info (name, e-mail, and so on) to allow that.
- If the user refuses to either login to X or to give X the authorization, the popup will close and the authentication process will fail; if he accepts, X will send back an OAuth2 access token.
- The web application will immediately consume that OAuth2 access token to fetch the above mentioned user info and use them to either create a new account or login with an existing one, depending if these info corresponds to an existing user or not.
This is what happens under the hood, regardless of X being Facebook, Google, Twitter or anything else. That said, such workflow can be implemented in a number of alternative ways, which can be grouped into two significative approaches (or, to better say, grant types):
- Using an explicit flow, with the help of a set of server-side tools, packages or libraries made available by the chosen development framework (such as ASP.NET Core), third-party packages or the external provider itself.
The explicit flow grant type is also called Authorization Code flow, because it returns a unique authorization code that must be used to retrieve the OAuth2 access token, avoiding the latter to be directly exposed to the user and to applications that might have access to the user's User Agent (such as browsers extensions, installed software, packet sniffers and so on).
To learn more about the OAuth2 authorization framework, we strongly suggest reading the following URLs:
Implicit flow vs Explicit flow
The main difference between the two grant types is all about how the aforementioned OAuth2 access token is requested, obtained and handled: in short words, how the steps 2 and 3 are actually performed.
- The popup window (step 2) will directly point to the external provider login/authorization page;
- After the login and auth, the OAuth2 access token (step 3) will be directly fetched by the client-side part of our web application and then sent to a dedicated server-side API controller, which will use it to retrieve the user data and perform the account creation/login (step 4).
Conversely, when using an explicit flow grant type such as those provided by AspNet.Security.OAuth.Providers, Windows SDK for Facebook or OpenIddict, these same steps take place in the following way:
- The popup window (step 2) will point to a server-side intermediate page of our app, which will then redirect the user to the external provider login/authorization page;
- After the login & auth, the external provider will redirect the user to a specific callback URL together with an Authorization Code which will be used by the server-side part of our application to retrieve the actual OAuth2 access token (step 3) and then immediately use it to retrieve the user data and perform the account creation/login (step 4).
Either of these approaches is viable enough: however, they both have their pros and cons in terms of security and versatility, depending on the given scenario.
Implicit flow pros and cons
On top of that, the overall results will most likely look great: the required popup window will open (and close) in the best possible way, without size mismatches or other UI/UX issues, and without any hack (that we're aware of).
However, such approach also comes with a few downsides: our users will be able to receive their access tokens, together with whatever could spy, hack, sniff or impersonate them; additionally, it will also force us to write a certain amount of dedicated client-side code for each supported provider, which might be far from ideal if we want to support a whole lot of them.
Explicit flow pros and cons
The explicit flow approach is the most commonly used in server-side web applications for a number of good reasons: the auth source code is not publicly exposed, the Client Secret confidentiality can be mantained and the whole process is definitely more secure due to the presence of the Authorization Code, which is nothing less than an additional security layer. On top of that, if we're using a third-party-aware framework such as the .NET Framework, we could always count on the built-in server-side tools - such as the aforementioned Microsoft.AspNetCore.Identity service.
The only real downside about that is the fact that it still is a flow based upon browser redirection, which means that the application must be capable of interacting with the user-agent (aka the web browser): open the login/auth popup (with a proper size), receive API authorization codes that are routed through the browser, close that popup, and so on. Although this is hardly an issue in standard MVC web applications, it's definitely way more complicated when dealing with Angular and Single-Page Applications; although it can be definitely "forced" into that, the developer will eventually have to pull off a small, yet consistent number of nasty workarounds: it won't be an out-of-the-box experience, that's for sure.
As we can easily see, both approaches have their pros and cons. As always, it's mostly a matter of what we actually need (or want to) achieve as product owners, project managers or full-stack software developers.