It's been a while since I've wrote an article here on CodeProject. I guess that being busy with coding various website influenced me to write less. However, when I look back, I think that not reading much had even bigger impact. Writting has a lot to do with reading. When you read other people's work it inspires you to add your own thoughts.
In my case I've finally finished two books from my huge backlog:
I can't say that either book is strictly about programming. But that's actually a good thing. By each passing day, we who develop technology people interact with, are more and more in charge of focusing on why, rather than how. Programming languages and tools are becoming better and better - giving us more time to focus on making our apps and websites more user friendly.
In my case, two mentioned books got me really thinking about logins on sites I maintain. Sure, there are quite few cons related to social logins that come to mind when you think about them (privacy being the biggest). But, one thing that I can never argue against social logins is how easy they make account creation and login for end users.
On the other hand, social logins can be quite complicated for us developers. Sure, integrating Facebook Social Login is probably easier than implementing whole email registration yourself. Form validation, email sending, verification tokens in email - you get the drift. But, when you need to download and integrate Facebook SDK and then Twitter SDK, PayPal SDK... and make all that work while battling with tons of unfamiliar and unnecessary classes you have in newly added SDKs... it's not easy.
In article that follows, I'll lay out my proposed solution for the problem. But, as always in all my articles, before getting to main text, here is Table of Contents to help you navigate the article.
Table of Contents
If you prefer to skip talk about existing solutions I didn't like and go directly to introduction of my Oauth2Login library, feel free to skip to OAuth2Login library I present here chapter.
If you already have existing Apps setup and want just to see how to use them with library presented here, skim through Web.Oauth.config file and then go to Explaining the demo and code structure chapter.
Existing ASP.NET C# solutions for OAuth Social Logins
Before getting to my solution, it's worthwile to look at what's already out there. Maybe you find something that fits better to your use case. As long as this article helps you solve your problem, I'll be content.
Native OAuth2 social login libraries (SDKs)
Obviously all companies that offer their social logins externally provide their own libraries / SDKs. I can only imagine how many man-hours and millions of $$$ went into developing and maintaing those. Ironically, that's one big problem with native libraries. On server side, company develops and maintains OAuth2 interface in technology they're most competent with. But, now someone from management comes down and says - OK guys, now we need wrapper libaries for our API. I've Googled most popular programming languages and seems like we'll need: PHP, Ruby, C#, javascript, Python, Java SDKs. At least. Make it happen.
I'll skip talking about what happens when you get people to learn new programming language and then right away task them with job of creating library that'll be used by other developers. The bigger issue is that now you have numerous wrapper libraries to maintain and update. Taking that into consideration I would say how current situation is as good as it can be. Sure, if you go to Facebook API Bug tracker you'll probably LOL when you see how many KNOWN bugs is present in SDK Facebook is promoting for active use. But considering they are maintaining 5 SDKs (iOS, Android, Javascript, PHP and Unity SDK) on top of their Graph API - quantity of bugs is understandable.
To get started, let's list developer pages you'll want to visit for companies that expose social logins:
-
Facebook - https://developers.facebook.com - obviously the first company that comes to mind when you say social login. I love how Facebook pulled off whole SDK / Logins / Likes scenario. They allow other companies to more easily acquire users with Facebook Login and in return Facebook gets to track what people are doing on other websites that integrate their widgets, profile them better and then sell that data through ads to advertisers. Brilliant for them.
-
Twitter - https://dev.twitter.com/ - I've always saw Twitter as younger, more arrogant brother of Facebook (and yeah, I get the joke of claiming there is a company more arrogant than Facebook). You can see that well illustrated with their "OAuth2, 4 years in making" and "please give us email when user logins in" threads on support forum. Just follow two links I gave you and enjoy the reading.
-
PayPal - https://developer.paypal.com/ - I have quite lenghty experience with PayPal SDKs and integrations (as you can see from my other article - Introduction to PayPal for C# - ASP.NET developers). I can't say that they ever were shining example of how company should support developers that depend on their APIs. If you ever get forwarded to their technical support - good luck with finding solution to your problem. However, I like their recent moves regarding SDKs and I'm hoping things will get better.
-
Google - https://developers.google.com/ - from my experience Google's APIs are probably the most stable and least likely to give up on you. Which is good, because there is no a way in the world you'll reach live, knowledgable developer when you contact Google Developer support.
One thing I quite like, that started in past few years, is that mentioned companies are open sourcing their SDKs. Which, when you start thinking about it, should be default choice. Every SDK that is free-to-use should be open sourced. That way, in case of bug, instead of forcing me to type whiny emails to your support, you'll force me to fix it on my own. Here are the links to Facebook and PayPal SDKs. In case you know where Twitter and Google posted source of their SDKs, please leave a comment and I'll add your entry to the list:
Microsoft libraries for OAuth2 and social login
Ever since the whole ASP.NET / C# story started some 15 years ago, Microsoft has been trying to come up with libraries that'll allow us developers to handle "current hip task" in Visual Studio with ease. Now, I would love to find out how they pick and incetify those people who end up with the task of implementing library/addin in question.
I'm saying this, because most of the Microsoft developed libraries I've tried using (outside of core .NET platform) were pretty terrible. As if they were never tested on real projects before being released. So when you do try using them, you start uncovering tons of prerequisites, silly conventions and configuration quirks.
Luckily, I can say that OAuth2 is somewhat of exception. Sure, you'll be forced to switch to OWIN and Katana - but at least guides and examples provided are good. You can use this article as your starting point.
I was also pleasantly surprised when I discovered OAuthforaspnet.com website. I love the fact that Microsoft is swiching from closed source libraries for "hip things" to "let's support the guy who open sourced what we wanted". Now, if only they started allowing their own employees to also contribute to repositories like that, things would be even better. Hopefully, we'll see Entity Framework development model applied in cases like this.
OAuth2Login library I present here
Looking at available options it's easy to ask: why code your own library when there are options available? Four simple reasons:
- Backward compatibility - what Microsoft offers now is geared toward OWIN and Katana. Sure, you can make it work with websites you already have, but why force completely new technology on yourself when all you want is simple website login?
- Independency - previous iteration of Microsoft's OAuth providers was creating predefined tables in your database. Don't know about you, but I don't like when some library pollutes my database, just so it can run.
- Configurability - one thing that I was puzzled with is why configuration for current libraries is mostly stuck in code. Maybe it has to do with "easy of use". Whoever develops library doesn't want to get into explaining people where and how to set config values, so they leave configuration directly in code (uncomment these lines, and that's it). While this is definitely easier, it's a bad practice. Configuration needs to be in config files because that allows you to setup appropraite build process.
- Completely open source - I wanted library that is completely transparent so that whoever comes after me can change it to work for them. The only two dependencies this library has are .NET Framework and Newtonsoft.Json. Everything else is coded within the library as you can tell if you visit OAuth2Login Github repository page.
Finally, I want to thank ericzo for starting this library few years back. When you visit visit OAuth2Login Github repository you'll see that this is fork of what he has previously done. I've significatly rewrote most of the code, but still his contribution is immense; I doubt this version of library would be there if I needed to start from scratch. Beauty of open source at work.
How to setup OAuth2Login library
I've really strived to make library easy to use. Once you clone repository, running example should be two step process:
- Create Web.Oauth.config and populate it with values for your app
- Run the Solution and browser should open page that shows buttons for supported login providers you can use to test
Integrating in your own solution shouldn't be too much different. Obviously you first need to reference OAuth2Login library within you ASP.NET MVC project, then create appropriate config, and finally copy code / classes from Oauth2Login.Example to save you some time with implementation.
Since you are already reading this article, I trust you already know how to reference projects within Visual Studio. So, let's get to most "complicated" part - configuring library for use.
Web.Oauth.config file
Let's look at example Web.Oauth.config file that has mutliple OAuth providers setup:
<code><?xml version="1.0"?>
<oauth2.login.configuration>
<web failedRedirectUrl="~/AuthExternal/LoginFail" />
<oauth>
<add name="Google"
clientid="xxx_CLIENT_ID_xxx"
clientsecret="xxx_CLIENT_SECRET_xxx"
callbackUrl="http://localhost:28950/AuthExternal/Callback/Google"
scope="https://www.googleapis.com/auth/userinfo.email+https://www.googleapis.com/auth/userinfo.profile" />
<add name="Facebook"
clientid="xxx_CLIENT_ID_xxx"
clientsecret="xxx_CLIENT_SECRET_xxx"
callbackUrl="http://localhost:28950/AuthExternal/Callback/Facebook"
scope="public_profile,user_friends,email" />
<add name="PayPal"
clientid="xxx_CLIENT_ID_xxx"
clientsecret="xxx_CLIENT_SECRET_xxx"
callbackUrl="http://localhost:28950/AuthExternal/Callback/PayPal"
scope="openid email"
endpoint="sandbox" />
<add name="Twitter"
clientid="xxx_CLIENT_ID_xxx"
clientsecret="xxx_CLIENT_SECRET_xxx"
callbackUrl="http://127.0.0.1:28950/AuthExternal/Callback/Twitter" />
</oauth>
</oauth2.login.configuration>
</code>
I believe config file is mostly self explainatory, but let's explain few potentially problematic details:
- failedRedirectUrl is path to URL you want to redirect user in case something goes wrong. For example, user fails to login. Or he rejects granting access to your App.
- Every OAuth client needs clientId and clientSecret values. Your OAuth provider will give you these values once you create your App on their developer website.
- Another required piece of information is callbackUrl. This is URL your OAuth provider will redirect user after he logs in, along with approriate OAuth access tokens.
- Other values depend on OAuth provider. For example:
- When creating Facebook App for Login you'll want to specify which permissions you want user to grant you
- Or when creating PayPal app you have choice between sandbox and production environment so you can use endpoint="sandbox" when testing or remove that entry for production.
- Speaking of environments, one important digression. If anybody from any OAuth provider reads this - PLEASE, stop limiting us to 2 environments. Most developers who understand environment setup do AT LEAST 3 environments: development (their local machine), staging (remote machine used for testing) and production (where site runs). So, if you are thinking about providing different environments for single app, and your developers suck, get them to provide at least 3 fixed instead of 2 fixed environments. Then those without staging can use 2 environments, and us who have 3 environments don't need to create and maintain 2 separate apps.
Obtaining ClientId and ClientSecret
At it's core, obtaining ClientId and ClientSecret for each OAuth provider is simple. There are just few generic steps:
- You go to OAuth provider developer portal, register account and accept various terms and conditions
- Create your app by providing requested data. Be sure to have your Terms and Conditions and Privacy Policy ready as they'll ask for those
- Set Callback Url
- Receive ClientId and ClientSecret from OAuth provider
Differences in this process come from requirements of OAuth providers and the way their developer portals are organized. Also, there are varied levels of App reviews. The thing that I've mentioned in #2, how you need to have ToC and Privacy Policy ready - Facebook actually suspended one of my Apps because it didn't have appropriate Privacy Policy on the website. Other OAuth providers either haven't reviewed the Policy or it was good enough for them, but Facebook - nope - App was suspended.
So, let me give you links to where you can register your App along with few tips and tricks applicable to different OAuth providers.
Google OAuth
You'll need to visit Google Developer Console to get started. The idea is to create new project, enable Google+ API (that's in charge of logins) add Client Credentials for it, setup appropriate Authorized Javascript Origins and Authorized Redirect. To make a list out of it:
- Credentials -> Create credentials -> Oauth2 client ID -> Web Application
- Enter name for your client, "Website Login" for example
- Authorized Javascript origins - root of your website. I.e: http://www.mysite.com
- Authorized redirect url - Callback url from your website. I.e: http://www.mysite.com/Callback/Google
- Copy Client ID and Secret back into Web.Oauth.config
Facebook OAuth
Start point is Apps listing on Facebook Developer. When you create your App, simply go Settings -> Add Platform -> Website. Once you add website and setup site's domain, you'll get ClientId and ClientSecret. As I said in previous paragraphs, be sure to have Terms and Privacy Policy ready. They may let you slide with placeholder pages for a month, but be sure to have those pages up and running before going to full blown production.
PayPal OAuth
PayPal redid their Developer Portal and new iteration is pretty straighforward from design standpoint. However, be warned - portal is pretty buggy.
Few months ago there was silly bug with Dashboard link redirecting you to home page first two times you click it on it. So, in order to see your apps after logging in you needed to: click on Dashboard link, be redirected to main page, click on Dashboard link again, get redirected to main page, and then finally click on Dashboard link again. I am not kidding. I've spent an hour trying to figure out how to create new App because of Dashboard link problems.
Last bug on their portal that cost me quite some time was that updating app settings takes extremely long time. I uncovered this when I tried to change required permissions for users and callback url. I've changed those settings in the PayPal Developer Portal for my App and then spent 3 hours trying to figure out why those changes weren't propagated. Being desperate to get it working I created new App with my new settings, changed ClientId and ClientSecret in Web.Oauth.config - boom, worked right away. The biggest shock came when I took my time to submit this bug to PayPal Developer Support - they said - oh, we know about that, it's intended behaviour. You need to wait on our cache to refresh... give it 24 hours and it should work.
I'm listing all this not to whine, but to warn you. If you run into problems using PayPal Developer Portal - don't worry, you are not alone. At least, for once in your life familiar saying - it's not my code, it's external bug - will actually be true.
You need to go Developer Apps page. Two big gotchas:
-
This is probably only well known OAuth provider that doesn't support localhost in Callback URL. You need to use 127.0.0.1 instead. This also means that if you use IIS Express for debugging, you'll need to configure it to listen on all addresses. To do that, open your IIS Express config file (%MyDocuments%/IIS Express/config/applicationhost.config), find entry related to example website I provided you with (search for port 28950) and then add extra line below existing localhost entry:
<code> <binding protocol="http" bindingInformation="*:28950:localhost" />
<binding protocol="http" bindingInformation="*:28950:*" />
</code>
-
Getting users's email is not readily supported (they have been working on this). Fun reading on that subject - https://twittercommunity.com/t/how-to-get-email-from-twitter-user-using-oauthtokens/558/22
Explaining the demo and code structure
By now hopefully you've managed to create Web.Oauth.config in demo project within downloaded source, pressed F5, tried different login providers and are now you are wondering: OK, can you provide detailed explaination on how exactly code works and how I can take all this into my own website?
Let's break it down:
- Everything in demo website mostly revolves around AuthExternalController. It's likely that you'll want to copy that controller into your website along with Views/AuthExternal directory.
- Clicking on Home/Index view buttons opens new popup window that goes to URL like: '/AuthExternal/Login/Facebook?mode='. With that example url Login method of AuthExternalController will have values:
- id=Facebook - this means that BaseOauth2Service.GetService(id) method will return you instance of FacebookService for handling authentication
- mode=Default - if you down the road want to support scenario of user adding multiple login providers to his account, you'll want to set this mode to "AttachLogin". For now empty value or "Default" will finish the job
- Once you have instance of BaseOauth2Service, BeginAuthentication() method will generate URL to which user will be redirected. Once user accepts sharing his login with your app, OAuth provider will invoke Callback method
- In Callback method, we again instatiate appropriate BaseOauth2Service (depending on id) and try to extract OAuth Access Token (and Secret if needed) along with other user data (email, phone number, etc). Once we have those two pieces, we can create account for our user and tell our popup window where to go by setting AuthCallbackResult.RedirectUrl
- If you open Callback.cshtml you'll see that redirect script first tries to pass login notification through Javascript to window opener. You can use this fact to provide your own custom loginMethods.handleRedirect method.
As for OAuth2Login library, let's go in order through namespaces:
- Oauth2Login.Client - these are mostly legacy classes that I'll try removing during next refactoring. Basically, they hold data from config once instatiated and data obtained during communication with OAuth2 provider
- Oauth2Login.Configuration - classes for reading Web.Oauth.config file
- Oauth2Login.Core - base classes and core classes used throughout library and outside library.
- Oauth2Login.Service - main namespace that contains classes for each OAuth2 providers. BaseOauth2Service class provides base functionality and then each class provides appropriate calls depending on provider.
- Oauth2Login.ServiceData - extra data classes used for interacting with APIs go here. Nothing big for now.
I hope that after reading these paragraphs and browsing source for few minutes you can say: whoa, this doesn't seem like lots of code. Because that was another goal - keep codebase as small as possible. That way if there are any API changes it'll be easier to implement them back into library.
Few notices on library
If you plan on using the library please be warned that (as soon as I have time) I plan on refactoring library to make it even leaner. So, don't be surprised by breaking changes.
Also, if you can, please keep library under git repo and consider submitting pull requests. For example, now that basic structure is in place I think testing and adding more OAuth2 providers should be easy-peasy. If anybody does this, just notify me and submit pull request - I'll be glad to merge your contribution and give you well deserved credit & shoutout.
That's it?!
Yeah, pretty much. OAuth2 is neat protocol and it's a shame that it's simplicity, beauty and usefullness is hidden behind so many terrible libraries/SDKs that contain tons of unnecessary code.
Looking back, I am really thankful that I got inspired by mentioned books (Don't Make Me Think: A Common Sense Approach to Web Usability && Letting Go of the Words: Writing Web Content that Works) and started looking into what was really going in code of all those bloated SDKs required for OAuth2 Login integration.
Looking forward, I can only hope that my work will be useful to other devs out there and that this small OAuth2Login library will keep evolving.
Future Plans
I've found this pretty useful article that talks about popularity of certain social login providers across the Internet. Looking at the data it seems that Yahoo and LinkedIn are worth implementing, which is what I'll do next. If you manage to beat me to it - consider submitting pull request.