*Not affiliated with McDonalds Corp in any way.
https://github.com/jagthedrummer/so_auth_provider
https://github.com/jagthedrummer/so_auth
https://github.com/jagthedrummer/so_auth_client
Just a web client
GET /protected
Redirect #{provider}/authorize?client_id=123&state=yyz&response_type=code
GET #{provider}/authorize?client_id=123&state=yyz&response_type=code
Redirect #{consumer}/callback?code=abc&state=yyz&response_type=code
GET #{consumer}/callback?code=abc&state=yyz&response_type=code
POST /oauth/token?client_id=123&client_secret=456&code=abc
{token:'42ab', refresh_token:'9876', uid:'jag'}
GET /me.json?token=42ab
{id:111, name:"Jeremy Green", email:"jeremy@octolabs.com"}
Redirect /protected
GET /protected
200 OK /protected Whew!
GET /protected
Redirect #{provider}/...
$ rails plugin new so_auth \ --full \ -T \ --dummy-path=spec/dummy
Gem::Specification.new do |s| #... s.add_dependency 'omniauth', "~> 1.2.1" s.add_dependency 'omniauth-oauth2', "~> 1.1.2" #... end
# lib/omniauth/strategies/so.rb require 'omniauth-oauth2' class OmniAuth::Strategies::So < OmniAuth::Strategies::OAuth2 option :name, 'so' option :client_options, { :site => ENV['AUTH_PROVIDER_URL'], :authorize_path => '/oauth/authorize', :access_token_path => '/oauth/token' } end
# spec/dummy/config/initializers/omniauth.rb # If you need to pull the app_id and app_secret from a different spot # this is the place to do it APP_ID = ENV['AUTH_PROVIDER_APPLICATION_ID'] || "not_a_real_id" APP_SECRET = ENV['AUTH_PROVIDER_SECRET'] || "not_a_real_secret" Rails.application.config.middleware.use OmniAuth::Builder do provider :so, APP_ID, APP_SECRET end
spec/dummy $ rails g controller stuff private public create app/controllers/stuff_controller.rb route get "stuff/public" route get "stuff/private" invoke erb create app/views/stuff create app/views/stuff/private.html.erb create app/views/stuff/public.html.erb
# spec/dummy/app/controllers/stuff_controller.rb class StuffController < ApplicationController before_filter :login_required, :only => [:private] end
# spec/dummy/app/controllers/application_controller.rb class ApplicationController < SoAuth::ApplicationController #... end
class SoAuth::ApplicationController < ActionController::Base def login_required not_authorized end def not_authorized respond_to do |format| format.html{ redirect_to "/auth/so?origin=#{request.original_url}" } format.json{ head :unauthorized } end end end
GET #{provider}/auth...
Redirect#{consumer}/callback...
# Gemfile gem 'doorkeeper', '~> 1.1.0'
$ bundle install $ rails generate doorkeeper:install $ rails generate doorkeeper:migration $ rake db:migrate
# config/initializers/doorkeeper.rb Doorkeeper.configure do # This should either return the current user # or redirect to the sign in page. resource_owner_authenticator do current_user || warden.authenticate!(:scope => :user) end #... end
# config/initializers/doorkeeper.rb Doorkeeper.configure do # We want to skip the screen that asks # "can application X use your profile?" skip_authorization do |resource_owner, client| true end #... end
GET #{provider}/auth...
doorkeeperRedirect
GET #{consumer}/callback...
omniauthPOST /oauth/token...
doorkeeper{token:...}
omniauth/so_authGET /me.json...
???{name:...}
# config/routes.rb SoAuthProvider::Application.routes.draw do #... get "oauth/me" => "oauth/users#me" #... end
# oauth/users_controller.rb class Oauth::UsersController < ApplicationController doorkeeper_for :all # sets doorkeeper_token or sends 401 error respond_to :json # GET /me.json def me respond_with current_resource_owner end private # Find the user that owns the access token def current_resource_owner User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token end end
Provider App{name:...}
??? Redirect /protected
# lib/omniauth/strategies/so.rb class OmniAuth::Strategies::So < OmniAuth::Strategies::OAuth2 #... def raw_user_info @raw_user_info ||= access_token.get('/oauth/me').parsed end uid { raw_user_info['id'] } end
# lib/omniauth/strategies/so.rb class OmniAuth::Strategies::So < OmniAuth::Strategies::OAuth2 #... info do { :email => raw_user_info['email'], :admin => raw_user_info['admin'] } end extra do { :raw_user_info => raw_user_info } end end
# config/routes.rb Rails.application.routes.draw do # ... # omniauth callbacks get '/auth/:provider/callback', :to => 'so_auth/user_sessions#create' get '/auth/failure', :to => 'so_auth/user_sessions#failure' end
class SoAuth::UserSessionsController < SoAuth::ApplicationController # omniauth callback method def create omniauth = env['omniauth.auth'] user = User.find_or_create_by(:id => omniauth['uid']) if user.respond_to?(:email) user.email = omniauth['info']['email'] end user.save session[:user_id] = user.id redirect_to request.env['omniauth.origin'] || root_path end #... end
class SoAuth::UserSessionsController < SoAuth::ApplicationController #... # Omniauth failure callback def failure flash[:notice] = params[:message] redirect_to root_path end end
class SoAuth::ApplicationController < ActionController::Base #... def current_user return nil unless session[:user_id] @current_user ||= User.find_by_id(session[:user_id]) end def signed_in? current_user.present? end helper_method :signed_in? helper_method :current_user end
class SoAuth::ApplicationController < ActionController::Base def login_required not_authorized unless current_user end #... end
omniauth & so_auth Redirect /protected
class Oauth::SessionsController < Devise::SessionsController def set_user_cookie(user) cookies[:so_auth] = { :value => user.id, :domain => env_domain } end def remove_user_cookie cookies.delete(:so_auth, :domain => env_domain) end def env_domain domain = ENV['COOKIE_DOMAIN'] || "localhost" domain == "localhost" ? :all : domain end #... end
# config/routes.rb SoAuthProvider::Application.routes.draw do #... devise_for :users, :controllers => {:sessions => "oauth/sessions"} #... end
class Oauth::SessionsController < Devise::SessionsController def create super {|resource| set_user_cookie(resource) } end def destroy super {|resource| remove_user_cookie } end #... end
class SoAuth::ApplicationController < ActionController::Base before_filter :check_cookie def check_cookie reset_session unless cookie_valid? end def cookie_valid? cookies[:so_auth].present? && session[:user_id].present? && cookies[:so_auth].to_s == session[:user_id].to_s end #... end
# config/routes.rb Rails.application.routes.draw do # Custom logout post '/logout', :to => 'so_auth/user_sessions#destroy' end
class SoAuth::UserSessionsController < SoAuth::ApplicationController #... # logout - Clear our rack session BUT importantly redirect # to the provider to clean up the there too! def destroy reset_session redirect_to "#{ENV['AUTH_PROVIDER_URL']}/users/sign_out" end end
$ rails new so_auth_consumer -T --database=postgresql
# Gemfile # development gem 'so_auth', path: "../so_auth"
# for deployment gem 'vendorise', group: :development # rake "vendorise:gem[git@github.com:jagthedrummer/so_auth.git]" gem 'so_auth', path: "vendor/gems/so_auth"
$ rails generate so_auth:install create config/initializers/omniauth.rb
# If you need to pull the app_id and app_secret from a different spot # this is the place to do it APP_ID = ENV['AUTH_PROVIDER_APPLICATION_ID'] || "not_a_real_id" APP_SECRET = ENV['AUTH_PROVIDER_SECRET'] || "not_a_real_secret" Rails.application.config.middleware.use OmniAuth::Builder do provider :so, APP_ID, APP_SECRET end
AUTH_PROVIDER_URL=http://localhost:3000 AUTH_PROVIDER_APPLICATION_ID=1234 AUTH_PROVIDER_SECRET=5678
$ rails generate model user email:string $ rake db:migrate; rake db:test:prepare
# app/controllers/some_controller.rb class SomeController < ApplicationController before_filter :login_required, :only => [:private_stuff] end
@jagthedrummer
jeremy@octolabs.com