0

Nurturing a growing platform with an OmniAuth authentication flow

The Why

This post is about a pivotal moment in the evolution of Vox Media’s tech stack. To date, we’ve constructed a monolithic Rails app, called Chorus, which powers each of our 321 communities. As we begin to launch new applications outside of the Chorus mothership (e.g., Pick 6, a fantasy sports game, and Syllabus, a live blogging platform) we have a clear need for these apps to work together when it comes to things like utilizing sports data, tracking metrics, and the subject of this post: authentication.

The What

Vox Media has 321 interconnected communities of awesome engaged users, and we strive to make it easy for our users to participate in several at once. It’s obvious that signing up and logging into yet another website is no fun, so it’s critical that we make it super easy to hop back and forth between all our apps and communities with a single login session.

This has been no big deal up until now, because all of our communities and sites run under the same Rails app, connected to the same database. When Pick 6 came along, we made the decision to develop it as an independent Rails app, making it a good experiment in transitioning to a service-oriented architecture. With this decision, we needed a way to extend our existing single sign on (SSO) solution from one to many coordinated apps.

The How

When presented with the problem of securely sharing login credentials across apps, I immediately thought of the OAuth2 protocol, which I am familiar with from interacting with several popular APIs, including Facebook’s.

Instead of building a full OAuth2 server into Chorus, I (perhaps foolishly) chose to make the fewest codebase changes by devising a custom authentication flow. In hindsight, I may have chosen to go all out with OAuth2 by using rack-oauth.

Anyway, the authentication flow I came up with looks something like this:

Sequence of URLs and actions taken:

Step URL Action(s) Taken
1 http://pick6.sbnation.com - Click login link
2 http://pick6.sbnation.com/auth/chorus - Remember referrer for later
- 302 redirect
3 https://www.sbnation.com/login?
api_client_id=1
- Submit login form
4 POST
https://www.sbnation.com/login?
api_client_id=1&
username=blake&
password=secret
- Verify credentials
- Generate a session token
- Look up the callback URL
- Append the session token to the callback URL
- 302 redirect to the callback URL
5 https://pick6.sbnation.com/auth/chorus/
callback?api_session_token=abc123
- Derive user info from API call using session token and shared secret
- Save user info in session
- 302 redirect to remembered URL
6 http://pick6.sbnation.com - Arrive logged in and ready to go!

In order to implement this flow, changes were needed on both sides: in Chorus and in Pick 6.

Changes to Pick 6

My favorite way of implementing third-party login in Rails apps is with the awesome OmniAuth library. Having OmniAuth’s request and callback phase architecture in mind when coming up with the above flow made it super easy to write up a reusable authentication library to handle steps 2 and 5 as an OmniAuth strategy: omniauth-chorus.

Step 2 is the request phase, and step 5 is the callback phase. Step 2 is simply a redirect to a hard-coded URL. Step 5 is where the guts of the strategy live. This phase has the responsibility of converting an api_session_token into an auth hash via a request of the Chorus user API. I found omniauth-oauth2 to be a great example of how to write omniauth-chorus.

Changes to Chorus

In order to provide omniauth-chorus with the session token and API endpoint needed to complete its work, some changes to the Chorus app were necessary. Chorus has a robust internal API that is used to power our native mobile apps for iOS and Android. omniauth-chorus makes use of this same API in step 5 of the flow to convert a session token into user info.

To date, our mobile apps securely authenticated themselves to this internal API with a shared secret. This secret was hard coded into Chorus with a line of code like this:

API_KEY = abc123... # TODO: Don’t use a hard-coded API key

This project provided the needed impetus to create a flexible system of API clients and secrets so that the mobile apps didn’t share credentials with Pick 6. This was solved by creating an api_client resource that gives each client of the Chorus API a distinct identity, shared secret (API key), and callback URL to be used in the above flow.

Security

In step 5, the API key is only transmitted in a direct request from Pick 6 to Chorus (not through the user’s browser). This makes us reasonably certain of the secrecy of each API key.

If an outsider were able to observe the api_session_token parameter in step 5, they would be able to impersonate another user by visiting that URL. But because we enforce that steps 3, 4, and 5 all happen over SSL, we make it difficult for this man-in-the-middle attack to work.

Single Sign On

So far so good: We have the ability to log into Pick 6 by deferring to SB Nation using the above flow. But for a really seamless experience, we’d love if there was some way for my login status on Pick 6 and SB Nation to always be in sync. Logging in or out of SB Nation should change my login status on Pick 6 and vice-versa.

Chorus has a sweet system for sharing login status between all of the different domains of its communities. It works by placing a blocking

Home of the Vox Media product team. We help make SB Nation, The Verge, Polygon, Curbed, Eater, Racked, and Vox.com.

Follow us on Twitter

Flickr

view all

    Dribbble

    view all

      Vox Product Stats

      • 0Verticals Launched
      • 0Communities
      • 0Open source repos
      • 0Comments in the Last 24 Hours
      • 0Git Commits to Chorus
      • 0Product Team Members
      X
      Log In Sign Up

      forgot?
      Log In Sign Up

      Please choose a new voxmedia username and password

      As part of the new voxmedia launch, prior users will need to choose a permanent username, along with a new password.

      Your username will be used to login to voxmedia going forward.

      I already have a Vox Media account!

      Verify Vox Media account

      Please login to your Vox Media account. This account will be linked to your previously existing Eater account.

      Please choose a new voxmedia username and password

      As part of the new voxmedia launch, prior MT authors will need to choose a new username and password.

      Your username will be used to login to voxmedia going forward.

      Forgot password?

      We'll email you a reset link.

      If you signed up using a 3rd party account like Facebook or Twitter, please login with it instead.

      Forgot password?

      Try another email?

      Almost done,

      By becoming a registered user, you are also agreeing to our Terms and confirming that you have read our Privacy Policy.
      Spinner.vc97ec6e

      Authenticating

      Great!

      Choose an available username to complete sign up.

      In order to provide our users with a better overall experience, we ask for more information from Facebook when using it to login so that we can learn more about our audience and provide you with the best possible experience. We do not store specific user data and the sharing of it is not required to login with Facebook.