Skip to content

CodeByLine/omniauth_readme-v-000

 
 

Repository files navigation

Omniauth

Objectives

  1. Describe the problem of authentication and how Omniauth solves it.
  2. Explain an Omniauth strategy.
  3. Describe the problem OAuth solves, and how it solves it.
  4. Use Omniauth to provide OAuth authentication in a Rails server.

Overview

Passwords are terrible.

For one thing, you have to remember them. Or you have to use a password manager, which comes with its own problems. Unsurprisingly, some percentage of users will just leave and never come back the moment you ask them to create an account.

And then on the server, you have to manage all these passwords. You have to store them securely. Rails secures your passwords when they are stored in your database, but it does not secure your servers, which see the password in plain text. If I can get into your servers, I can edit your Rails code and have it send all your users' passwords to me as they submit them. You'll also have to handle password changes, email verification, and password recovery. Inevitably, your users accounts will get broken into. This may or may not be your fault, but when they write to you, it will be your problem.

What if it could be someone else's problem?

Like Google, for example. They are dealing with all these problems somehow (having a huge amount of money helps). For example, when you log into Google, they are looking at vastly more than your username and password. Google considers where you are in the world (they can guess based on your IP address, the operating system you're running (their servers can tell because they listen very carefully to your computer's accent when it talks to them), and numerous other factors. If the login looks suspicious — for instance, you usually log in on a Mac in New York, but today you're logging in on a Windows XP machine in Thailand — they may reject it or ask you to solve a captcha.

Wouldn't it be nice if your users could use their Google — or Twitter, or Facebook, etc. — login for your site?

Of course, you know this is possible. I'm sure you've seen sites that let you log in with Facebook. Today, we're going to talk about how you can enable such a feature for your site.

Omniauth

Omniauth is a gem for Rails that lets you use multiple authentication providers on your site. You can let people log in with Twitter, Facebook, Google, or with a username and password.

Here's how it works from the user's standpoint:

  1. I try to access a page which requires me to be logged in. I am redirected to the login screen.
  2. It offers me the options of creating an account or logging in with Google or Twitter.
  3. I click "Login with Google". This momentarily sends me to $your_site/auth/google, which quickly redirects to the Google signin page.
  4. If I'm not signed in to Google, I sign in. More likely, I am already signed in to Google, so Google asks me if they should let $your_site know who I am. I say yes.
  5. I am (hopefully quickly) redirected to $your_site/auth/google/callback and, from there, to the page I initially tried to access.

Let's see how this works in practice:

Omniauth with Facebook

The Omniauth gem allows us to use the OAuth protocol with a number of different providers. All we need to do is add the Omniauth gem and the provider-specific Omniauth gem (e.g., omniauth-google) to our Gemfile. In this case, add omniauth and omniauth-facebook to your Gemfile, and then bundle. If we were so inclined, we could add additional Omniauth gems to our heart's desire, offering login via multiple providers in our app.

Next, we'll need to tell Omniauth about our app's OAuth credentials.

Create config/initializers/omniauth.rb. It will contain this:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
end

The ENV constant refers to a global hash for your entire computer environment. You can store any key value pairs in this environment, so it's a very useful place to store credentials that we don't want to be managed by Git or stored on GitHub (especially if your GitHub repo is public). The most common error we see from students here is that when ENV["PROVIDER_KEY"] is evaluated in the initializer it returns nil. Then later when you try to authenticate with the provider you'll get some kind of 4xx error because the provider doesn't recognize your app.

To recieve these credentials, each provider's process is different. You'll essentially need to register your app with the provider, and they'll give you a set of keys specific to your app.

For Facebook: Log in to the Facebook developer's panel. Create an app, copy the key (it's called "App ID" on Facebook's page) and the secret, and set them as environment variables in the terminal:

export FACEBOOK_KEY=<your_key>
export FACEBOOK_SECRET=<your_key>

We've included a quick video as this often trips people up (including experienced folks). Make sure you do the last two steps of setting your URL and valid domains. If you don't, Facebook will think you're making a request from an invalid site and will never let the user log in.

Running these commands will make these key-value pairs appear in the ENV hash in Ruby in that terminal. A more lasting way to do this is using the Figaro or Dotenv gems.

Jump into the console to check that you have set the keys properly. If ENV["FACEBOOK_KEY"] and ENV["FACEBOOK_SECRET"] return your keys you're all set!

We now need to create a link that will take the user to Facebook to login. Create a link anywhere you'd like that sends the user to "/auth/facebook". We'll need a route, a controller, and a view. I'll only show the view.

<!-- app/views/static/home.html.erb -->

<%= link_to("login with facebook!", "/auth/facebook") %>

Hot-Tip: Log out of Facebook before you do this portion so you can see the full flow.

Let's visit this page in the browser and click on the link. Clicking on the link clearly sends a GET request to your server to "/auth/facebook", but in the browser we end up at "https://www.facebook.com/login.php?skip_api_login=1&api_key=1688265381390456&signed_next=1&next=https%3A%2F%2Fwww.facebook.com%2Fv2.5%2Fdialog%2Foauth%3Fredirect_uri%3Dhttp%253A%252F%252Flocalhost%253A3000%252Fauth%252Ffacebook%252Fcallback%26state%3Dc7e7feeea98f875e7a77d76f7385ea2960db3dc23a397c4b%26scope%3Demail%26response_type%3Dcode%26client_id%3D1688265381390456%26ret%3Dlogin&cancel_url=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Ffacebook%2Fcallback%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dc7e7feeea98f875e7a77d76f7385ea2960db3dc23a397c4b%23_%3D_&display=page"

This URL has a whole bunch of parameters all URL encoded (which is why they look so strange). At this point we are at Facebook's site because somewhere in our app Omniauth sent the browser a redirect to that url (which the gem intelligently autogenerated for us).

Once we're at Facebook and the user logs in, Facebook will send the browser ANOTHER redirect with the URL Omniauth told it about in the previous URL. Omniauth always wants Facebook to redirect us back to our server to the route "/auth/whatever_provider/callback". Along with that request the provider will also send back a whole bunch of information for us.

The URL in your browser should look something like this mess: "http://localhost:3000/auth/facebook/callback?code=AQA_CrhVYnuufhQid-3vS1NvI5rZfk4uPJwFZIymA90JeUR7NDFFy0bHQjbtneLkymqqZlmFbjcg2A0y5zRmaCy0D7k9H46F3j9pm9slzBIN9fM4Q54zAdiVZo2k6XtiMPZ_AG2xEZ8MyiTtbbQOBdaK57PY7lr7iLuFeaVUCUnZC69ddzcq_tLILEkjagSyWXi8WGGshbnIwy9C6d98hnoxl6AJjIi4TC3FScEAxKQ9vH1tXntQ9YvTLNWlWsWUcbefEq1RlywNi3IqGsLnDgyyRcHph0u4-TpnaqZPxHSNdcWCgnYfHK_bSO-R_a3H4Oo&state=60fb843af784e411ea7b5f809e34dd29d5e4eda891d0c4c1#_=_"

You should now see a routing error, No route matches [GET] "/auth/facebook/callback".

Let's add something to handle that redirect:

# config/routes.rb

get '/auth/facebook/callback' => 'sessions#create'

# Note that the controller and action you use don't matter, but it's most logical to use the SessionsController because we're going to log the user in by creating a session.

Now we create a SessionsController. Our goal here is to either create a new user or find the user in our database and log them in. Facebook sends us a bunch of information back, and Omniauth parses it for us and puts it in the request environment: request.env['omniauth.auth']. We can use the information in there to log the user in (if they have an existing account) or create a new user.

Here's a sample of the auth hash Facebook sends us:

{
  :provider => 'facebook',
  :uid => '1234567',
  :info => {
    :email => '[email protected]',
    :name => 'Joe Bloggs',
    :first_name => 'Joe',
    :last_name => 'Bloggs',
    :image => 'http://graph.facebook.com/1234567/picture?type=square',
    :urls => { :Facebook => 'http://www.facebook.com/jbloggs' },
    :location => 'Palo Alto, California',
    :verified => true
  },
  :credentials => {
    :token => 'ABCDEF...', # OAuth 2.0 access_token, which you may wish to store
    :expires_at => 1321747205, # when the access token expires
    :expires => true # this will always be true
  },
  :extra => {
    :raw_info => {
      :id => '1234567',
      :name => 'Joe Bloggs',
      :first_name => 'Joe',
      :last_name => 'Bloggs',
      :link => 'http://www.facebook.com/jbloggs',
      :username => 'jbloggs',
      :location => { :id => '123456789', :name => 'Palo Alto, California' },
      :gender => 'male',
      :email => '[email protected]',
      :timezone => -8,
      :locale => 'en_US',
      :verified => true,
      :updated_time => '2011-11-11T06:21:03+0000'
    }
  }
}

Let's log the user in! (We've omitted the model-related code.)

# app/controllers/sessions_controller.rb

class SessionsController < ApplicationController

  def create
    user = User.find_or_create_by(:uid => auth['uid']) do |u|
      u.name = auth['info']['name']
      u.email = auth['info']['email']
    end
    session[:user_id] = user.id
  end

  def auth
    request.env['omniauth.auth']
  end
end

That completes the whole OAuth login flow!

Conclusion

Implementing the OAuth protocol yourself is extremely complicated. Using the Omniauth gem along with any Omniauth-provider gem(s) allows users to log in to your site easily, streamlining the process. However, it still trips a lot of people up! Make sure you understand each piece of the flow, what you expect to happen, and any deviance from the expected result. The end result should be getting access to the user's data from the provider in your SessionsController, where you can decide what to do with it. Typically, you'll create a new User in your database using their provider data (if they are not already an existing user) and/or log them in.

Video Review

Resources

View Omniauth on Learn.co and start learning to code for free.

View Omniauth on Learn.co and start learning to code for free.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 80.7%
  • HTML 15.2%
  • CSS 2.1%
  • JavaScript 2.0%