Giter Site home page Giter Site logo

omniauth_readme-v-000's Introduction

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.

omniauth_readme-v-000's People

Contributors

annjohn avatar blake41 avatar victhevenot avatar queerviolet avatar franknowinski avatar pletcher avatar lisamarie616 avatar mendelb avatar smulligan85 avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.