Skip to content

Instantly share code, notes, and snippets.

@jpmckinney
Last active February 16, 2016 19:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jpmckinney/3d9122a4be194bade9dc to your computer and use it in GitHub Desktop.
Save jpmckinney/3d9122a4be194bade9dc to your computer and use it in GitHub Desktop.
See https://github.com/rishabhp/sso-rails-provider/issues/3. The repo also ran `rails g devise:install`, `rails g devise:views` and `rails g devise User`, which aren't shown in the diff to keep it small.
diff --git b/Gemfile a/Gemfile
index 257a263..4896a34 100644
--- b/Gemfile
+++ a/Gemfile
@@ -43,4 +43,12 @@ group :development do
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
+
+ gem 'pry'
end
+
+# gems for omniauth and devise
+gem 'devise'
+gem 'omniauth'
+gem 'omniauth-twitter'
+gem 'omniauth-facebook'
diff --git b/app/controllers/auth_controller.rb a/app/controllers/auth_controller.rb
new file mode 100644
index 0000000..cdd7176
--- /dev/null
+++ a/app/controllers/auth_controller.rb
@@ -0,0 +1,81 @@
+class AuthController < ApplicationController
+ # This is our new function that comes before Devise's one
+ before_filter :authenticate_user_from_token!, :except => [:access_token]
+
+ before_filter :authenticate_user!, :except => [:access_token]
+ skip_before_filter :verify_authenticity_token, :only => [:access_token]
+
+ def authorize
+ # Note: this method will be called when the user
+ # is logged into the provider
+ #
+ # So we're essentially granting him access to our
+ # system by generating certain tokens and then
+ # redirecting him back to the params[:redirect_uri]
+ # with a random code and the params[:state]
+
+
+ AccessGrant.prune!
+ create_hash = {
+ client: application,
+ state: params[:state]
+ }
+ access_grant = current_user.access_grants.create(create_hash)
+ redirect_to access_grant.redirect_uri_for(params[:redirect_uri])
+ end
+
+ # POST
+ def access_token
+ application = Client.authenticate(params[:client_id], params[:client_secret])
+
+ if application.nil?
+ render :json => {:error => "Could not find application"}
+ return
+ end
+
+ access_grant = AccessGrant.authenticate(params[:code], application.id)
+ if access_grant.nil?
+ render :json => {:error => "Could not authenticate access code"}
+ return
+ end
+
+ access_grant.start_expiry_period!
+ render :json => {:access_token => access_grant.access_token, :refresh_token => access_grant.refresh_token, :expires_in => Devise.timeout_in.to_i}
+ end
+
+ def user
+ hash = {
+ provider: 'sso',
+ id: current_user.id.to_s,
+ info: {
+ email: current_user.email,
+ },
+ extra: {
+ # first_name: current_user.first_name,
+ # last_name: current_user.last_name
+ first_name: '',
+ last_name: ''
+ }
+ }
+
+ render :json => hash.to_json
+ end
+
+ protected
+
+ def application
+ @application ||= Client.find_by_app_id(params[:client_id])
+ end
+
+ private
+
+ def authenticate_user_from_token!
+ if params[:oauth_token]
+ access_grant = AccessGrant.where(access_token: params[:oauth_token]).take
+ if access_grant.user
+ # Devise sign in
+ sign_in access_grant.user
+ end
+ end
+ end
+end
diff --git b/app/controllers/callbacks_controller.rb a/app/controllers/callbacks_controller.rb
new file mode 100644
index 0000000..0bf5b60
--- /dev/null
+++ a/app/controllers/callbacks_controller.rb
@@ -0,0 +1,8 @@
+class CallbacksController < ApplicationController
+
+ def facebook
+ @user = User.from_omniauth(request.env["omniauth.auth"])
+ sign_in_and_redirect @user
+ end
+
+end
diff --git b/app/models/access_grant.rb a/app/models/access_grant.rb
new file mode 100644
index 0000000..a36f161
--- /dev/null
+++ a/app/models/access_grant.rb
@@ -0,0 +1,34 @@
+class AccessGrant < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :client
+
+ before_create :generate_tokens
+
+ # Generate random tokens
+ def generate_tokens
+ self.code = SecureRandom.hex(16)
+ self.access_token = SecureRandom.hex(16)
+ self.refresh_token = SecureRandom.hex(16)
+ end
+
+ def self.prune!
+ delete_all(["created_at < ?", 3.days.ago])
+ end
+
+ def redirect_uri_for(redirect_uri)
+ if redirect_uri =~ /\?/
+ redirect_uri + "&code=#{code}&response_type=code&state=#{state}"
+ else
+ redirect_uri + "?code=#{code}&response_type=code&state=#{state}"
+ end
+ end
+
+ def self.authenticate(code, application_id)
+ AccessGrant.where("code = ? AND client_id = ?", code, application_id).first
+ end
+
+ # Note: This is currently configured through devise, and matches the AuthController access token life
+ def start_expiry_period!
+ self.update_attribute(:access_token_expires_at, Time.now + Devise.timeout_in)
+ end
+end
diff --git b/app/models/client.rb a/app/models/client.rb
new file mode 100644
index 0000000..94a95db
--- /dev/null
+++ a/app/models/client.rb
@@ -0,0 +1,7 @@
+class Client < ActiveRecord::Base
+
+ # Check whether a Client exists by app_id and app_secret
+ def self.authenticate(app_id, app_secret)
+ where(["app_id = ? AND app_secret = ?", app_id, app_secret]).first
+ end
+end
diff --git b/app/models/user.rb a/app/models/user.rb
index c822027..cf96d77 100644
--- b/app/models/user.rb
+++ a/app/models/user.rb
@@ -1,6 +1,18 @@
class User < ActiveRecord::Base
+ has_many :access_grants, dependent: :delete_all
+
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
- :recoverable, :rememberable, :trackable, :validatable
+ :recoverable, :rememberable, :trackable, :validatable,
+ :omniauthable, omniauth_providers: [:facebook]
+
+ def self.from_omniauth(auth)
+ where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
+ user.provider = auth.provider
+ user.uid = auth.uid
+ user.email = auth.info.email
+ user.password = Devise.friendly_token[0,20]
+ end
+ end
end
diff --git b/config/initializers/devise.rb a/config/initializers/devise.rb
index 4c07f75..f1a6149 100644
--- b/config/initializers/devise.rb
+++ a/config/initializers/devise.rb
@@ -259,4 +259,7 @@ Devise.setup do |config|
# When using OmniAuth, Devise cannot automatically set OmniAuth path,
# so you need to do it manually. For the users scope, it would be:
# config.omniauth_path_prefix = '/my_engine/users/auth'
+
+ config.omniauth :facebook, FB_APP_ID, FB_APP_SECRET,
+ scope: 'email', info_fields: 'name, email'
end
diff --git b/config/routes.rb a/config/routes.rb
index fc2791d..5741cc9 100644
--- b/config/routes.rb
+++ a/config/routes.rb
@@ -1,5 +1,16 @@
Rails.application.routes.draw do
- devise_for :users
+ # Easier to make GET request from the client
+ devise_for :users, sign_out_via: [:get, :delete],
+ controllers: { omniauth_callbacks: 'callbacks' }
+
+ root 'home#index'
+
+ # Provider stuff
+ match '/auth/sso/authorize' => 'auth#authorize', via: :all
+ match '/auth/sso/access_token' => 'auth#access_token', via: :all
+ match '/auth/sso/user' => 'auth#user', via: :all
+ match '/oauth/token' => 'auth#access_token', via: :all
+
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
diff --git b/db/migrate/20150826052258_add_columns_to_users.rb a/db/migrate/20150826052258_add_columns_to_users.rb
new file mode 100644
index 0000000..dec83f6
--- /dev/null
+++ a/db/migrate/20150826052258_add_columns_to_users.rb
@@ -0,0 +1,6 @@
+class AddColumnsToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :provider, :string
+ add_column :users, :uid, :string
+ end
+end
diff --git b/db/migrate/20150826060015_create_access_grants.rb a/db/migrate/20150826060015_create_access_grants.rb
new file mode 100644
index 0000000..384feec
--- /dev/null
+++ a/db/migrate/20150826060015_create_access_grants.rb
@@ -0,0 +1,15 @@
+class CreateAccessGrants < ActiveRecord::Migration
+ def change
+ create_table :access_grants do |t|
+ t.string :code
+ t.string :access_token
+ t.string :refresh_token
+ t.datetime :access_token_expires_at
+ t.integer :user_id
+ t.integer :client_id
+ t.string :state
+
+ t.timestamps
+ end
+ end
+end
diff --git b/db/migrate/20150826083958_create_client.rb a/db/migrate/20150826083958_create_client.rb
new file mode 100644
index 0000000..4e40ebb
--- /dev/null
+++ a/db/migrate/20150826083958_create_client.rb
@@ -0,0 +1,11 @@
+class CreateClient < ActiveRecord::Migration
+ def change
+ create_table :clients do |t|
+ t.string :name
+ t.string :app_id
+ t.string :app_secret
+
+ t.timestamps
+ end
+ end
+end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment