There is a lot of material on OAuth (OAuth2, OpenID Connect, and friends) but not much focused on the application of OAuth to the problem of Single Sign-On (SSO). This post seeks to do so.
What is Single Sign-On?
In the days of yore, Single Sign-On meant SAML (Security Assertion Markup Language) - the protocol used by Microsoft Exchange. In SAML terms, Exchange acts as an Identity Provider (IP) that does federated identity management for a group of applications. The applications have a pre-configured trust in the IP, collect user credentials, and pass them to the IP for verification. There is a security concern with having the application collect user credentials, which OAuth addresses with a browser redirect flow. Later versions of the SAML protocol have addressed this concern, but the world of Web Services has moved to OAuth, as you can see with the proliferation of sign-in options for Facebook, Google, Github, etc. Yet SAML remains entrenched in many internal IT shops.
What is OAuth?
OAuth is about authorization – delegating the authority to perform operations, while single sign-on is about authentication – making sure a user is who they say they are by verifying their credentials (e.g. user name and password). The Jedi mind trick is to understand the process of authentication as a delegation of authority to retrieve the user’s profile, e.g. name, email, icon, etc. In SAML terms your application (App) is first configured to trust an IP such as Google to verify the user’s credentials. When the user wants to sign into your App, your App asks for the OAuth scope to read profile information. Google then authenticates the user and presents them the option to grant your App the requested scopes. Once this is granted, the browser is redirected back to your app with the tokens needed to get this information. As your App trusts Google to return a verified profile you can now use that information, e.g. the email address, to associate authorization scopes within your App, e.g. this user has privilege to manage objects within a project, etc.
Your App needs to trust that the profile information it receives came from the designated IP. This is done by configuring your service as an “App” on the IP’s web site. You provide your App’s callback URL, an icon representing your App that is presented in the authorization flow, and other information. The IP then issues you a Client ID and Client Secret that are used by your App to make the authorization request. It is important to handle these credentials with care so that the scopes granted by your users are not abused by others impersonating your App. These “App” configurations are sometimes found under menu items like Developer APIs or Tools.
Sign-In vs. Sign-Up
What if the user isn’t in my system and how do we get new users into the system? The general idea is to use the same authorization flow to sign-up new users that you use to sign them in. However, to avoid creating a new account by accident it is important to maintain the context. Once the user expresses the intent to sign-up, that context can be preserved in the OAuth flow through the state variable.
I’ve worked on three OAuth integrations so far and there are differences. Working in Go, I’ve been able to adapt github.com/markbates/goth which does a good job providing a common interface for many providers. However, these services have changed over time. This project relies on golang.org/x/oauth2, which itself is a more general abstraction of different services that is, again, out-of-date. This means that to specify the full list of endpoints for a service you must modify both projects. Even the protocol flow can be different, for example, Google bundles the user profile with the access token without the need for another call. Another thing to recognize is that different information is stored in the profiles. For example, we want to collect separate first and last names but these fields are not always populated.
As the OAuth flow requires a publicly available endpoint you will need a tunnel for the OAuth service to reach behind a VPN or NAT to get to your test server. I used ngrok.com which requires an email address but is otherwise painless. As the public tunnel endpoint will terminate HTTPS, you will either need a certificate with that endpoint or a tunnel configuration to translate to HTTP. I can recommend github.com for testing the flow even if that is not your primary integration target because their Web site is easy to navigate and their OAuth protocol appears stable.
In summary, getting from the feature requirements to a concrete design for Single Sign On using OAuth was harder than it should be because the terminology and existing articles are not focused to the task. Hopefully, this post can help others get past these obstacles.