In this tutorial, we will go through the steps to enable OAuth on a Ruby on Rails application using Sorcery for authentication. While we use Github as the OAuth provider in this tutorial, the steps for connecting to other OAuth providers should be very similar.
If you want to follow this tutorial but don’t have an existing Rails app, you can clone this app to start.
This repository contains the final app that is produced by following this entire tutorial.
Fill out the form, using http://localhost:3000 as the callback url (when you deploy the application you will need to change this to reflect the url of your application)
You have now been issued api keys for your application, you will need these in a little bit:
Step 2. Add oauth to your app
This guide follows much of the same steps as https://github.com/NoamB/sorcery/wiki/External, but shows how to allow logging in with Github without allowing user creation through Github.
# config/initializers/sorcery.rb# Here you can configure each submodule's features.Rails.application.config.sorcery.configuredo|config|...# add :github to external_providersconfig.external_providers=[:github]...# configure github using secrets (Rails 4.1, use environment variables in previous versions)config.github.key="#{Rails.application.secrets.sorcery_github_key}"config.github.secret="#{Rails.application.secrets.sorcery_github_secret}"config.github.callback_url="#{Rails.application.secrets.sorcery_github_callback_url}"config.github.user_info_mapping={:email=>"name"}...config.user_configdo|user|...user.authentications_class=Authentication...end...end
Add the configuration to your secrets.yml file:
1234567891011121314151617181920
# config/secrets.ymldevelopment:...sorcery_github_callback_url:http://0.0.0.0:3000/oauth/callback?provider=githubsorcery_github_key:<%= ENV["SORCERY_GITHUB_KEY"] %> sorcery_github_secret: <%=ENV["SORCERY_GITHUB_SECRET"]%>test: ... sorcery_github_key: <%= ENV["SORCERY_GITHUB_KEY"] %>sorcery_github_secret:<%= ENV["SORCERY_GITHUB_SECRET"] %># Do not keep production secrets in the repository,# instead read values from the environment.production: ... sorcery_github_callback_url: <%=ENV["SORCERY_GITHUB_CALLBACK_URL"]%> sorcery_github_key: <%= ENV["SORCERY_GITHUB_KEY"] %>sorcery_github_secret:<%=ENV["SORCERY_GITHUB_SECRET"]%>
Add the configuration to your environment variable manager (figaro if you followed step 1)
# app/controllers/oauths_controller.rbclassOauthsController<ApplicationControllerskip_before_filter:require_loginbefore_filter:require_login,only::destroy# sends the user on a trip to the provider,# and after authorizing there back to the callback url.defoauthlogin_at(auth_params[:provider])end# this is where all of the magic happensdefcallback# this will be set to 'github' when user is logging in via Githubprovider=auth_params[:provider]if@user=login_from(provider)# user has already linked their account with githubflash[:notice]="Logged in using #{provider.titleize}!"redirect_toroot_pathelse# User has not linked their account with Github yet. If they are logged in,# authorize the account to be linked. If they are not logged in, require them# to sign in. NOTE: If you wanted to allow the user to register using oauth,# this section will need to be changed to be more like the wiki page that was# linked earlier.iflogged_in?link_account(provider)redirect_toroot_pathelseflash[:alert]='You are required to link your GitHub account before you can use this feature. You can do this by clicking "Link your Github account" after you sign in.'redirect_tologin_pathendendend# This is used to allow users to unlink their account from the oauth provider.## In order to use this action you will need to include this route in your routes file:# delete "oauth/:provider" => "oauths#destroy", :as => :delete_oauth## You will need to provide a 'provider' parameter to the action, create a link like this:# link_to 'unlink', delete_oauth_path('github'), method: :deletedefdestroyprovider=params[:provider]authentication=current_user.authentications.find_by_provider(provider)ifauthentication.present?authentication.destroyflash[:notice]="You have successfully unlinked your #{provider.titleize} account."elseflash[:alert]="You do not currently have a linked #{provider.titleize} account."endredirect_toroot_pathendprivatedeflink_account(provider)if@user=add_provider_to_user(provider)# If you want to store the user's Github login, which is required in order to interact with their Github account, uncomment the next line.# You will also need to add a 'github_login' string column to the users table.## @user.update_attribute(:github_login, @user_hash[:user_info]['login'])flash[:notice]="You have successfully linked your GitHub account."elseflash[:alert]="There was a problem linking your GitHub account."endenddefauth_paramsparams.permit(:code,:provider)endend
Now let’s add the necessary routes to your routes file:
12345678910
# config/routes.rb...post"oauth/callback"=>"oauths#callback"get"oauth/callback"=>"oauths#callback"# for use with Githubget"oauth/:provider"=>"oauths#oauth",:as=>:auth_at_providerdelete"oauth/:provider"=>"oauths#destroy",:as=>:delete_oauthroot'todos#index'
Step 3. Testing
You should always create adequate tests in your apps. Here is an example of the tests that I wrote to test the oauth functionality that we have added (using rspec):
# spec/controllers/oauths_controller_spec.rbrequire'spec_helper'describeOauthsControllerdodescribe"#callback"doit'logs in a linked user'doOauthsController.any_instance.should_receive(:login_from).with('github').and_return(Authentication.new)alice=Fabricate(:user)session[:user_id]=alice.idget:callback,provider:'github',code:'123'expect(flash[:success]).tobe_presentendit'links the users github account if they are logged in'doOauthsController.any_instance.should_receive(:login_from).and_return(false)OauthsController.any_instance.should_receive(:add_provider_to_user).and_return(Authentication.new(user_id:'123',uid:'123',provider:'github'))controller.instance_variable_set(:@user_hash,{user_info:{'login'=>'alice.smith'}})alice=Fabricate(:user)session[:user_id]=alice.idget:callback,provider:'github',code:'123'expect(flash[:success]).tobe_presentendit'saves the users github login when it links the account'doOauthsController.any_instance.should_receive(:login_from).and_return(false)OauthsController.any_instance.should_receive(:add_provider_to_user).and_return(Authentication.create(user_id:'123',uid:'123',provider:'github'))controller.instance_variable_set(:@user_hash,{user_info:{'login'=>'alice.smith'}})alice=Fabricate(:user)session[:user_id]=alice.idget:callback,provider:'github',code:'123'expect(assigns(:user).login).toeq('alice.smith')endit'displays an error if there is a problem linking github account'doOauthsController.any_instance.should_receive(:login_from).and_return(false)OauthsController.any_instance.should_receive(:add_provider_to_user).and_return(false)alice=Fabricate(:user)session[:user_id]=alice.idget:callback,provider:'github',code:'123'expect(flash[:danger]).tobe_presentendit'displays an error if user is not logged in and their github account is not linked'doOauthsController.any_instance.should_receive(:login_from).and_return(false)get:callback,provider:'github',code:'123'expect(flash[:danger]).tobe_presentendenddescribe"DELETE destroy"docontext'user is logged in'dolet(:alice){Fabricate(:user)}before{session[:user_id]=alice.id}it'deletes linked account for logged in user'doFabricate(:authentication,user:alice)delete:destroy,provider:'github'expect(alice.authentications.count).toeq(0)endit'displays a success message after unlinking the users oauth account'doFabricate(:authentication,user:alice)delete:destroy,provider:'github'expect(flash[:success]).tobe_presentendit'displays an error if there is no linked account for the logged in user'dodelete:destroy,provider:'github'expect(flash[:danger]).tobe_presentendendcontext'user is not logged in'doit'does not delete any linked accounts'doalice=Fabricate(:user)Fabricate(:authentication,user:alice)delete:destroy,provider:'github'expect(alice.authentications.count).toeq(1)endendendend
Step 4. Testing in development
You are now ready to give the app a whirl!
In the terminal, start the Rails server: rails s
In your browser, visit http://0.0.0.0:3000. NOTE: Do not use http://localhost:3000 because that does not match the Github callback url.
Click “Login”.
Click “Link your Github Account”
Login to Github and authorize the app.
Click “Logout”
Click “Login with Github”.
You are now logged in using Github!
Step 5. Deploying
When you deploy, do not forget to set the proper environment variables in your deployed environment. Also, you have to change the callback url on your Github application’s settings page. If you do not do one or both of these things, your users will be sent to a 404 page on Github when they try to link their account and also when they try to login using Github.