This repo holds examples of how to add OAuth 2 to a Rails app built around the same basic structure as our simple BCrypt/Sign-up/Session auth system. In order to run the examples and understand the code, make sure to do the following:
- Sign up your instance of the example app with GitHub.
- Review OAuth 2 and GitHub's API.
- Review the base Rails app.
- "Checkout" the examples!
- a hand-rolled OAuth 2 Web flow using API calls,
- a "low-level" OAuth 2 library:
oauth2
, and - an OAuth flow with a provider-based API wrapper:
octokit
- a "kitchen sink", all-in-one, API wrapper:
github_api
In order to run this application you need to
- Register your app under your GitHub settings.
- Fill out the form with the following application info and then click "Register application":
- Add the Client ID and Client Secret values to your app's environment:
- You can use dotenv or simply export the values to your shell.
- The values need to be named:
- Client ID:
GITHUB_OAUTH_ID
- Client Secret:
GITHUB_OAUTH_SECRET
- Client ID:
- Following the (below) docs and the OAuth 2 flow diagram, we must add the necessary routes to implement the GitHub Web flow:
You can see this done very simply in Express or (less so, in) Sinatra, as well. On these frameworks the flow is easier to follow, as all the requests and responses are formed up in a row.
tag baseapp
... or git checkout baseapp
This app follows the basic format of WDI example Rails apps:
- Rails 4
- PostgreSQL
- No Minitest, no Turbolinks.
- Pry consoles instead of IRB.
It also uses a very simple User model with a unique email, a name, and a unique
OAuth user ID (oauth_uid
)* as attributes, and a scaffolded set of
routes to go along with that.
You must have defined the environmental variables GITHUB_OAUTH_ID
, and
GITHUB_OAUTH_SECRET
from above.
Very important! You must run the app with:
$ rails s -p 9888
This ensures the port number matches the one registered on GitHub.
The examples are all of how to "log in" with GitHub, store the resulting access token in the users' session, and associate that account with a local ActiveRecord model (User).
Note: because GitHub has a default scope
(set of permissions that the user
grants to the application) that allows us to access basic user data, all the
below examples omit declaring scopes for simplicity. This is unique to GitHub.
Specifically, the examples show how to do this with:
- a hand-rolled OAuth 2 Web flow using API calls,
- a "low-level" OAuth 2 library:
oauth2
, and - an OAuth flow with a provider-based API wrapper:
octokit
- a "kitchen sink", all-in-one, API wrapper:
github_api
commit e867014
... or git checkout handrolled
This commit adds a series of methods to the
SessionsController
, and renames the routesessions#create
to be the OAuth callback. It also generates a URL to the GitHub OAuth login page and renders it on thesessions/new.html.erb
view.
The simplest, but most tedious, way to implement the OAuth 2 protocol is to write out the necessary requests and responses using a relatively low-level HTTP abstraction.
This example uses the httparty
gem to send requests to the GitHub
API (both to get an access token and to make signed requests on the users'
behalf).
Note: This example omits state
, part of the OAuth 2 protocol, and only gets
a single access token instead of a refresh token. Both of these are done for
simplicity's sake. These are fixed in both of the below examples, but are
abstracted away.
commit ad47e9a
... or git checkout oauth2gem
(Uses version 1.0 of the gem.)
This commit adds a series of methods to the
SessionsController
, and renames the routesessions#create
to be the OAuth callback. It also generates a URL to the GitHub OAuth login page and renders it on thesessions/new.html.erb
view.
oauth2
is a very powerful, popular gem that implements the OAuth 2 protocol
flow. This allows you to abstract out some of the grunt work, while allowing
"granular" control of interactactions with the given identity provider and its
API.
commit 28f720d
... or git checkout octokit
(Uses version 3.8 of the gem.)
This commit adds to the
SessionsController
,routes.rb
, and thesessions/new.html.erb
view, as in the above example. It also adds extra "current user" caching methods to theApplicationController
, wrapping the access token in an instance ofOctokit::Client
.
octokit
is the official GitHub API wrapper. The name is a play on their
zoomorphic mascot, Octocat, a hybrid octopus–cat styled after the
world-famous Sanrio characters.
This is an example of how to integrate an OAuth flow (which retrieves an access token) with using an API-wrapper gem. The gem signs API requests using the access token, and offers a semantic interface to the developer.
commit 635b5f1
... or git checkout github_api
(Uses version 0.12 of the gem.)
This commit adds to the
SessionsController
,routes.rb
, and thesessions/new.html.erb
view, as in the above example. It also adds extra "current user" caching methods to theApplicationController
, as above. However, all interaction with GitHub runs thru thegithub_api
gem.
github_api
is an example of the most popular way to implement OAuth 2:
API-wrapper gems that combine services for OAuth and API requests. Sometimes
these gems are officially supported by the identity provider (eg:
PayPal, Instagram, DropBox), sometimes they are
maintained by third parties (Facebook, Twitter).
github_api
is maintained by Peter Murach.
These gems abstract the OAuth process at a very high level, but also confuse that process with using access tokens (received via OAuth) to make API requests on behalf of the user.** They promise simplicity but can often have the highest learning curve to use.
Notes:
* – The OAuth Unique ID attribute (oauth_uid
) is on the User model in
order that we connect the local user to the credentials passed from the
identity provider. In a system where you have multiple identity providers this
would need to be a bit more complex, perhaps having the attribute store a JSON
list of key- value pairs, like:
{
"GitHub": "34598258",
"Facebook": "",
"Google": "pj@ga.co"
}
** – This is a very big, very common confusion! An "access token" received via OAuth will allow your application to make API requests for your user. The "API key" we use elsewhere to access APIs from our applications are used to authenticate your app, not a user of your app. There is a good chance you may even use each, in different ways, at the same time!