Dennis

Sign-in using GitHub

Dennis | over 2 years ago | Rails Omniauth

I want a painless way for visitors of my website to post comments, without going through the hassle of registering for an account. OmniAuth is a gem that allows us to do just that. In this tutorial I'll show how to enable users to sign-in/up using github and OAuth.

The setup

We're going to configure omniauth to use github for a users credentials. We do however create a user for the authenticated user, which will contain the information we obtain from GitHub. For one, this makes sure that our app doesn't crash and burn when GitHub is offline.

We're going to assume that you already have an authentication scheme working. With a sign_in(user) method that performs all the work related to sessions, etc. because I just want to show the implementation of omniauth itself.

OmniAuth has several strategies. You can install these separately in your Gemfile. Here we'll use the omniauth-github gem. Look at the OmniAuth wiki for more strategies.

# Gemfile
gem 'omniauth-github'

# Bash
bundle install

With omniauth-github installed, we have to setup the authentication. OmniAuth is provided as a rack middleware application and we include it in our app using an initializer. Create a new file in the config/initializers path and give it a descriptive name (e.g. omniauth.rb). The initializer does the setup of the authentication strategy for github. It needs a github-key and github-secret. You can obtain these at GitHub. For development purposes you can set the application url to http://localhost:3000 and the callback url to http://localhost:3000/auth/github/callback.

# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
  provider :github, APP_CONFIG[:github_id], APP_CONFIG[:github_secret]
end

As you see above, I store my variables in a global has called APP_CONFIG. You can also just put the key and secret right in the initializer. However, whatever you do, do not make the secret public, they don't call it a secret for nothing.

Believe it or not, but with this minimal configuration, the first part of the authentication is already working. Fire up your rails server and go to /auth/github. The application will forward you to GitHub, where it will ask you if you want to allow it to have access.

The next step from github will be to make a call back to your application. If everything went right, it should call to /auth/github/callback, if a user rejects your application, it will call /auth/failure. Let's catch those calls and do something with it.

When we reach /auth/failure, we could for example want to redirect the user to the root path and display an error message saying that the authentication failed.

A successful callback will provide you with the users information in request.env["omniauth.auth"], You could use pry or the rails logger to inspect this object.

# config/routes.rb
match "/auth/:provider/callback" => "sessions#create_from_github"
match "/auth/failure" => "sessions#failure_from_github"

# app/controllers/sessions_controller.rb
def create_from_github
   # Rails.logger.debug request.env["omniauth.auth"]
end

def failure_from_github
  flash[:error] = "Error logging in with GitHub. #{params[:message]}"
  redirect_to root_path
end

What we really want to do with the information, however, is two things.

  1. Check if the user is new. If so, create a new User model for this user.
  2. Find the User model that belongs to this user and log him in.

We can do this using the uid that is provided by GitHub in the returned omniauth.hash. Lets add this column to our user model.

rails generate migration add_github_uid_to_users github_uid:string

Now, in our SessionsController, we can check if a user with this ID already exists by searching the user database for that GitHub UID. If it doesn't find one, we can create a new user using the information provided by GitHub.

# SessionsController
def create_from_github
  omniauth = request.env["omniauth.auth"]
  @user = User.find_by_github_uid(omniauth["uid"]) || User.create_from_omniauth(omniauth)
  # Hook into your own authentication system!
  sign_in @user 
  # This would normally be configured to return to the previous path 
  redirect root_path, :notice => "Welcome, #{@user.name}"  
end

Creating the user is done using the createfromomniauth method:

#user.rb
def self.create_from_omniauth(omniauth)
  User.new.tap do |user|
    user.github_uid = omniauth["uid"]
    user.name = omniauth["info"]["nickname"]
    user.email = omniauth["info"]["email"]
    user.save!
  end
end

Now, when a user tries to login using omniauth, your call will create a user if necessary, log him user in and redirect him to the root path. Great, isn't it? But, how does a user login using omniauth? Simple, just redirect him to /auth/github and omniauth will take care of everything else.

Notes:

I found that OAuth is picky about domain names. So, www.web-l.nl and web-l.nl are two different worlds. So, if you setup your application to use example.com, make sure that all requests at www.example.com get redirected to the preferred domain name before making the OAuth call.

In my case, I use Apache and mod_rewrite. In the virtualhost block I rewrite the url to use the root domain name.

RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ http://example.com$1 [R=301,L]
Back to index

Comments:

417d568e3bfd2f99243f1e6532a87279
Mmellado, over 1 year ago

I'm trying to do exactly this and it looks very straight forward. Thanks! I have a quick question tho.

Let's use this scenario with the approach you describe above: I'm a new user and I hit sign in with github. Then I logout from github (hence logging out from your application as well). Then I come back and I want to login again. However, this time for some reason I don't want to login using my github account. In theory, last time an account was created for me. However, how would I know what password should I use to login without using github, and when is this password being created/stored?

Dennis
Dennis, over 1 year ago

Good question! In my specific example a user gets assigned a long random password when it is created via OAuth. I have the option to e-mail the password to the user at *that* time, because I just generated it. However, I don't think it is good policy to mail plaintext passwords.

I think the best way forward for a user that does not want to login using GitHub the next time, is to use the 'recover password' feature and the public e-mail of his Github account. If no public e-mail was shared, then I don't see any other options then to login using GitHub and then reset your password.

A possible alternative is to ask a user to setup a password after authenticating by oath for the first time. Disadvantage is that is an extra step between where the user is, and what he is trying to do (e.g. comment)

9649e6b72d7e55806f97669409ac91ca
Datapimp, about 1 year ago

Just curious, but isn't the UID parameter publicly available information in the Github API for a given user?

So if somebody wanted to authenticate I imagine it is pretty easy to forge this request to a server which uses this code?

7b5a451ee25044b9c869e3e98b79425d
Amcaplan, 7 months ago

Trying this, I found that setting the application URL to http://localhost:3000 worked, but http://127.0.0.1:3000 raised an error. So yes, GitHub is pretty picky about the URLs.

In case anyone ran into the same problem.

You can sign in using Github if you want to comment