exvo-auth
This gem is Exvo's implementation of the oauth2 protocole for handling user authentication across Exvo apps.
Requirements
- Runs on Ruby 1.8.7 & 1.9.2 (preferred version)
- Rails 3.0+ (works with Rails 3.1) or Merb
OAuth2
- Get familiar with OmniAuth by Intridea. Read about OAuth2.
- Obtain
client_id
andclient_secret
for your app from Exvo. - Install
exvo-auth
gem and add it to your Gemfile.
Middleware configuration
The preferred way to configure the gem is via the ENV variables:
ENV['AUTH_CLIENT_ID'] = "foo"
ENV['AUTH_CLIENT_SECRET'] = "bar"
ENV['AUTH_DEBUG'] = "true" # [OPTIONAL] dumps all HTTP traffic to STDERR, useful during development; it *has to be a string, not a boolean*
ENV['AUTH_REQUIRE_SSL'] = "false" # [OPTIONAL] disable SSL, useful in development (note that all apps API urls must be http, not https); it *has to be a string, not a boolean*
ENV['AUTH_HOST'] = "test.exvo.com" # [OPTIONAL] override the default auth host
Then add this line to config/application.rb
:
config.middleware.use ExvoAuth::Middleware
But you can also set things directly in the config/application.rb
file (before the middleware declaration):
ExvoAuth::Config.client_id = "foo"
ExvoAuth::Config.client_secret = "bar"
ExvoAuth::Config.debug = true # boolean
ExvoAuth::Config.require_ssl = false # boolean
ExvoAuth::Config.host = "test.exvo.com"
Add routes
The following comes from Rails config/routes.rb
file:
match "/auth/failure" => "sessions#failure"
match "/auth/interactive/callback" => "sessions#create"
match "/auth/non_interactive/callback" => "sessions#create" # only if you use json-based login
match "/sign_out" => "sessions#destroy"
Failure url is called whenever there's a failure (d'oh).
You can have separate callbacks for interactive and non-interactive callback routes but you can also route both callbacks to the same controller method like shown above.
Include controller helpers into your application controller
include ExvoAuth::Controllers::Rails # (or Merb)
Implement a sessions controller
Sample implementation (Rails):
class SessionsController < ApplicationController
def create
sign_in_and_redirect!
end
def destroy
sign_out_and_redirect!
end
def failure
render :text => "Sorry!"
end
end
It's good to have your SessionsController#create action a little more extended, so that each time the user logs in into the app, his user data (like email, nickname) is updated from auth (his profile):
def create
auth = request.env["omniauth.auth"]
if user = User.find_by_uid(auth["uid"])
user.update_attributes!(auth["user_info"])
else
user = User.create(:uid => auth["uid"], :nickname => auth["user_info"]["nickname"], :email => auth["user_info"]["email"])
end
sign_in_and_redirect!
end
This is what you get (and what you can use/save for the local user) from auth (example data as of 2011-12):
request.env["omniauth.auth"].inspect
{ "provider" => "exvo",
"uid" => 1,
"credentials" => {
"token" => "a2d09701559b9f26a8284d6f94670477d882ad6d9f3d92ce9917262a6b54085fa3fb99e111340459"
},
"user_info" => {
"nickname" => "Pawel",
"email" => "[email protected]"
},
"extra" => {
"user_hash" => {
"id" => 1,
"nickname" => "Pawel",
"country_code" => nil,
"plan" => "admin",
"language" => "en",
"email" => "[email protected]",
"referring_user_id" => nil
}
}
}
Implement #find_or_create_user_by_uid(uid)
in your Application Controller
This method will be called by #current_user
. Previously we did this in sessions_controller
but since the sharing sessions changes this controller will not be used in most cases because the session comes from another app through a shared cookie. This method should find user by uid or create it.
Exemplary implementation (Rails):
def find_or_create_user_by_uid(uid)
User.find_or_create_by_uid(uid)
end
It's best to leave this method as it is (without updating any user data inside this method, better to do this in the SessionsController#create action). Updating user in this method might lead to some very hard to debug cyclic executions possibly leading to stack-level too deep errors and/or general slowness, so please proceed with extreme caution.
Sign up and sign in paths for use in links
sign in path: "/auth/interactive"
sign up path: "/auth/interactive?x_sign_up=true" # this is OAuth2 custom param
sign in path with a return address: "/auth/interactive?state=url" # using OAuth2 state param
You have a handy methods available in controllers (and views in Rails): sign_in_path
and sign_up_path
.
Require authentication in your controllers
In application_controller
(for all controllers) or in some controller just add:
before_filter :authenticate_user!
Fetching user information
All info about any particular user ca be obtained using auth api (/users/uid.json
path).
Read the source, there are few features not mentioned in this README
Inter-Application Communication
You need to have "App Authorization" created by Exvo first.
Contact us and provide following details:
consumer_id
- Id of an app that will be a consumer (this is you)provider_id
- Id of the provider appscope
- The tag associated with the api you want to use in the provider app
Consumer side
consumer = ExvoAuth::Autonomous::Consumer.new(
:app_id => "this is client_id of the app you want to connect to"
)
consumer.get(*args) # interface is exactly the same like in HTTParty. All http methods are available (post, put, delete, head, options).
Provider side
See #authenticate_app_in_scope!(scope)
method in ExvoAuth::Controllers::Rails
(or Merb). This method lets you create a before filter.
Scopes are used by providing app to check if a given consuming app should have access to a given resource inside a scope.
If scopes are empty, then provider app should not present any resources to consumer.
Example of the before filter for provider controller:
before_filter {|c| c.authenticate_app_in_scope!("payments") }
In provider controller, which is just a fancy name for API controller, you can use #current_app_id
method to get the app_id of the app connecting.
Dejavu - replay non-GET requests after authentication redirects
Limitations:
- doesn't work with file uploads
- all request params become query params when replayed