Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add OAuth2 documentation.

  • Loading branch information...
commit eb3118e89a00758e1cae70536665648b27e5f3b9 1 parent 5d6900d
@josevalim josevalim authored
View
170 README.rdoc
@@ -7,10 +7,11 @@ Devise is a flexible authentication solution for Rails based on Warden. It:
* Allows you to have multiple roles (or models/scopes) signed in at the same time;
* Is based on a modularity concept: use just what you really need.
-Right now it's composed of 11 modules:
+Right now it's composed of 12 modules:
* Database Authenticatable: encrypts and stores a password in the database to validate the authenticity of an user while signing in. The authentication can be done both through POST requests or HTTP Basic Authentication.
* Token Authenticatable: signs in an user based on an authentication token (also known as "single access token"). The token can be given both through query string or HTTP Basic Authentication.
+* Oauthable: adds OAuth2 support;
* Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
* Recoverable: resets the user password and sends reset instructions.
* Registerable: handles signing up users through a registration process, also allowing them to edit and destroy their account.
@@ -179,9 +180,13 @@ Since Devise is an engine, all its views are packaged inside the gem. These view
rails generate devise:views
-However, if you have more than one role in your application (such as "User" and "Admin"), you will notice that Devise uses the same views for all roles. Fortunately, Devise offers an easy way to customize views. All you need to do is set "config.scoped_views = true" inside "config/initializers/devise.rb".
+If you are using HAML, you will need hpricot installed to convert Devise views to HAML.
-After doing so, you will be able to have views based on the role like "users/sessions/new" and "admins/sessions/new". If no view is found within the scope, Devise will use the default view at "devise/sessions/new".
+If you have more than one role in your application (such as "User" and "Admin"), you will notice that Devise uses the same views for all roles. Fortunately, Devise offers an easy way to customize views. All you need to do is set "config.scoped_views = true" inside "config/initializers/devise.rb".
+
+After doing so, you will be able to have views based on the role like "users/sessions/new" and "admins/sessions/new". If no view is found within the scope, Devise will use the default view at "devise/sessions/new". You can also use the generator to generate scoped views:
+
+ rails generate devise:views users
== Configuring controllers
@@ -194,7 +199,7 @@ If the customization at the views level is not enough, you can customize each co
2) Tell the router to use this controller:
- devise_for :admins, :controllers => { :sessions => "admin/sessions" }
+ devise_for :admins, :controllers => { :sessions => "admins/sessions" }
3) And since we changed the controller, it won't use the "devise/sessions" views, so remember to copy "devise/sessions" to "admin/sessions".
@@ -219,15 +224,16 @@ You can also create distinct messages based on the resource you've configured us
admin:
signed_in: 'Hello admin!'
-The Devise mailer uses the same pattern to create subject messages:
+The Devise mailer uses a similar pattern to create subject messages:
en:
devise:
mailer:
- confirmation_instructions: 'Hello everybody!'
- user:
- confirmation_instructions: 'Hello User! Please confirm your email'
- reset_password_instructions: 'Reset instructions'
+ confirmation_instructions:
+ subject: 'Hello everybody!'
+ user_subject: 'Hello User! Please confirm your email'
+ reset_password_instructions:
+ subject: 'Reset instructions'
Take a look at our locale file to check all available messages.
@@ -249,13 +255,123 @@ You can include the Devise Test Helpers in all of your tests by adding the follo
Do not use such helpers for integration tests such as Cucumber or Webrat. Instead, fill in the form or explicitly set the user in session. For more tips, check the wiki (http://wiki.github.com/plataformatec/devise).
+== OAuth2
+
+Since version 1.2, Devise comes with OAuth support out of the box. To create OAuth support from Github for example, first you need to register your in Github and add it as provider in your devise initializer:
+
+ config.oauth :github, 'APP_ID', 'APP_SECRET',
+ :site => 'https://github.com/',
+ :authorize_path => '/login/oauth/authorize',
+ :access_token_path => '/login/oauth/access_token',
+ :scope => %w(user public_repo)
+
+Then, you need to mark your model as oauthable:
+
+ class User < ActiveRecord::Base
+ devise :database_authenticatable, :oauthable
+ end
+
+And add a link to your views/sign up form:
+
+ <%= link_to "Sign in as User with Github", user_oauth_authorize_url(:github) %>
+
+This link will send the user straight to Github. After the user authorizes your applications, Github will redirect the user back to our application at "/users/oauth/github/callback". This URL will be handled by Devise and, for Github and User model, it looks like this:
+
+ def github
+ access_token = github_config.access_token_by_code(params[:code])
+ @user = User.find_for_github_oauth(access_token, signed_in_resource)
+
+ if @user && @user.persisted?
+ sign_in @user
+ set_oauth_flash_message :notice, :success
+ redirect_to after_oauth_success_path_for(@user) #=> redirects to user_root_path or root_path
+ elsif @user
+ session[:user_github_oauth_token] = access_token.token
+ render_for_auth #=> renders sign up view by default
+ else
+ set_oauth_flash_message :alert, :skipped
+ redirect_to after_oauth_skipped_path_for(:user) #=> redirects to user_new_session_path
+ end
+ end
+
+In other words, Devise expects you to implement +find_for_github_oauth+ method in your model and will act accordingly depending on what the method returns. This method will receive two arguments: the first is an +access_token+ object from OAuth2 library (http://github.com/intridea/oauth2) and the second is the signed in resource which we will ignore for this while.
+
+A basic implementation would be:
+
+ def self.find_for_github_oauth(access_token, signed_in_resource=nil)
+ # Get the user email info from Github for sign up
+ data = ActiveSupport::JSON.decode(access_token.get('/api/v2/json/user/show'))["user"]
+
+ if user = User.find_by_email(data["email"])
+ user
+ else
+ # Create an user with a stub password.
+ User.create!(:name => user["name"], :email => user["email"], :password => Devise.friendly_token)
+ end
+ end
+
+Our method above has two branches and both of them returns a persisted user. So, if we go back to our github action above, we will see that after returning a persisted record, it will sign in the returned user in session, redirect to the configured +after_oauth_success_path_for+ with a flash message. This flash message is retrieved from I18n and looks like this:
+
+ en:
+ devise:
+ oauth_callbacks:
+ # Higher priority message:
+ user:
+ github:
+ success: 'Hello dear user! Welcome to our app!'
+
+ # With medium priority
+ github:
+ success: 'Hello coder! Welcome to our app!'
+
+ # With lower priority
+ success: 'Successfully authorized from %{kind} account.'
+
+However, this workflow assumes that all information retrieved from Github is enough for us to create an user and this may not be true for all providers. That said, Devise allows +find_for_github_oauth+ to have different outcomes. For instance, if it returns a record which was not persisted (usually a new record with errors), it will render the sign up views from the registrations controller and show all error messages. Finally, if you decide to return nil from +find_for_github_oauth+, Devise will consider that you decided you skip the authentication and will redirect to +after_oauth_skipped_path_for+ (defaults to the sign in page) with the skipped flash message.
+
+All these methods +after_oauth_skipped_path_for+, +render_for_oauth+ and so on can be customized and overwritten in your application by inheriting from Devise::OauthCallbacksController as we have seen above in the "Configuring controllers" section.
+
+For last but not least, Devise also supports linking accounts. The setup discussed above only uses Github for sign up and assumes that after the user signs up, there will not have any interaction with Github at all. However, this is not true for some applications.
+
+If you need to interact with Github after sign up, the first step is to create a +github_token+ in the database and store it in the +find_for_github_oauth+ method above for further requests to their API. Next, you may also want to allow an already signed in user to link his account to a Github account without a need to sign up again. This is where the +signed_in_resource+ we discussed earlier takes place. If +find_for_github_oauth+ receives a signed in resource as parameter, you can link the github account to it like below:
+
+ def self.find_for_github_oauth(access_token, signed_in_resource=nil)
+ data = ActiveSupport::JSON.decode(access_token.get('/api/v2/json/user/show'))["user"]
+
+ # Link the account if an e-mail already exists in the database
+ # or a signed_in_resource, which is already in session was given.
+ if user = signed_in_resource || User.find_by_email(data["email"])
+ user.update_attribute(:github_token, access_token.token)
+ user
+ else
+ User.create!(:name => user["name"], :email => user["email"],
+ :password => Devise.friendly_token){ |u| u.github_token = access_token.token }
+ end
+ end
+
+After the token is stored, you can create another access token object to do get/post/put/delete requests like this:
+
+ def oauth_github_token
+ @oauth_github_token ||= self.class.oauth_access_token(:github, github_token)
+ end
+
+For github, the access token never expires. For facebook, you need to ask for offline access to get a token that won't expire. However, some providers like 37 Signals may expire the token and you need to store both access_token and refresh token in your database. This mechanism is not yet supported by Devise by default and you should check OAuth2 documentation for more information.
+
+Finally, notice in cases a resource is returned by +find_for_github_oauth+ but is not persisted, we store the access token in the session before rendering the registrations form. This allows you to recover your token later by overwriting +new_with_session+ class method in your model:
+
+ def self.new_with_session(params, session)
+ super.tap { |u| u.github_token = session[:user_github_oauth_token] }
+ end
+
+This method is called automatically by Devise::RegistrationsController before building/creating a new resource.
+
== Migrating from other solutions
Devise implements encryption strategies for Clearance, Authlogic and Restful-Authentication. To make use of these strategies, set the desired encryptor in the encryptor initializer config option. You might also need to rename your encrypted password and salt columns to match Devise's fields (encrypted_password and password_salt).
== Other ORMs
-Devise supports ActiveRecord (by default) and Mongoid. We offer experimental Datamapper support (with the limitation that the Devise test suite does not run completely with Datamapper). To choose other ORM, you just need to configure it in the initializer file.
+Devise supports ActiveRecord (default) and Mongoid. To choose other ORM, you just need to require it in the initializer file.
== Extensions
@@ -263,32 +379,29 @@ Devise also has extensions created by the community:
* http://github.com/scambra/devise_invitable adds support to Devise for sending invitations by email.
-* http://github.com/grimen/devise_facebook_connectable adds support for Facebook Connect authentication, and optionally fetching user info from Facebook in the same step.
-
* http://github.com/joshk/devise_imapable adds support for imap based authentication, excellent for internal apps when an LDAP server isn't available.
* http://github.com/cschiewek/devise_ldap_authenticatable adds support for LDAP authentication via simple bind.
Please consult their respective documentation for more information and requirements.
-== TODO
+== Bugs and Feedback
-Please refer to TODO file.
+If you discover any bugs, we would like to know about it. Be sure to include as much relevant information as possible, as Devise and Rails versions. If possible, please try to go through the following steps:
-== Security
+1) Look at the source code a bit to find out whether your assumptions are correct. We try to keep it as clean and documented as possible;
-Needless to say, security is extremely important to Devise. If you find yourself in a possible security issue with Devise, please go through the following steps, trying to reproduce the bug:
+2) Provide a simple way to reproduce the bug: a small test case to Devise test suite or a simple app on Github;
-1) Look at the source code a bit to find out whether your assumptions are correct;
-2) If possible, provide a way to reproduce the bug: a small app on Github or a step-by-step to reproduce;
-3) E-mail us or send a Github private message instead of using the normal issues;
+Our Issues Tracker is available at:
-Being able to reproduce the bug is the first step to fix it. Thanks for your understanding.
+http://github.com/plataformatec/devise/issues
-== Maintainers
+If you found a security bug, we ask you to *NOT* use the Issues Tracker and instead send us a private message through Github or send an e-mail to the developers.
-* José Valim (http://github.com/josevalim)
-* Carlos Antônio da Silva (http://github.com/carlosantoniodasilva)
+Finally, if you have questions or would like to give some feedback, please use the Mailing List instead of Issues Tracker:
+
+http://groups.google.com/group/plataformatec-devise
== Contributors
@@ -296,15 +409,12 @@ We have a long list of valued contributors. Check them all at:
http://github.com/plataformatec/devise/contributors
-== Bugs and Feedback
-
-If you discover any bugs, please create an issue on GitHub.
+If you want to add new features, let us know in the Issues Tracker! If you want to scratch our itches, feel free to check the TODO file in the repository. :)
-http://github.com/plataformatec/devise/issues
-
-For support, send an e-mail to the mailing list.
+== Maintainers
-http://groups.google.com/group/plataformatec-devise
+* José Valim (http://github.com/josevalim)
+* Carlos Antônio da Silva (http://github.com/carlosantoniodasilva)
== License
View
1  TODO
@@ -1,3 +1,4 @@
* Move integration tests to Capybara
* Better ORM integration
* Extract activatable models tests from confirmable
+* Add support to automatically refresh the access token for OAuth
View
15 lib/devise/models/authenticatable.rb
@@ -4,18 +4,17 @@ module Devise
module Models
# Authenticable module. Holds common settings for authentication.
#
- # == Configuration:
+ # == Options
#
- # You can overwrite configuration values by setting in globally in Devise,
- # using devise method or overwriting the respective instance method.
+ # Authenticatable adds the following options to devise_for:
#
- # authentication_keys: parameters used for authentication. By default [:email].
+ # * +authentication_keys+: parameters used for authentication. By default [:email].
#
- # http_authenticatable: if this model allows http authentication. By default true.
- # It also accepts an array specifying the strategies that should allow http.
+ # * +http_authenticatable+: if this model allows http authentication. By default true.
+ # It also accepts an array specifying the strategies that should allow http.
#
- # params_authenticatable: if this model allows authentication through request params. By default true.
- # It also accepts an array specifying the strategies that should allow params authentication.
+ # * +params_authenticatable+: if this model allows authentication through request params. By default true.
+ # It also accepts an array specifying the strategies that should allow params authentication.
#
# == Active?
#
View
20 lib/devise/models/confirmable.rb
@@ -9,22 +9,22 @@ module Models
# it means it won't be able to sign in again without confirming the account
# again through the email that was sent.
#
- # Configuration:
+ # == Options
#
- # confirm_within: the time you want the user will have to confirm it's account
- # without blocking his access. When confirm_within is zero, the
- # user won't be able to sign in without confirming. You can
- # use this to let your user access some features of your
- # application without confirming the account, but blocking it
- # after a certain period (ie 7 days). By default confirm_within is
- # zero, it means users always have to confirm to sign in.
+ # Confirmable adds the following options to devise_for:
#
- # Examples:
+ # * +confirm_within+: the time you want to allow the user to access his account
+ # before confirming it. After this period, the user access is denied. You can
+ # use this to let your user access some features of your application without
+ # confirming the account, but blocking it after a certain period (ie 7 days).
+ # By default confirm_within is zero, it means users always have to confirm to sign in.
+ #
+ # == Examples
#
# User.find(1).confirm! # returns true unless it's already confirmed
# User.find(1).confirmed? # true/false
# User.find(1).send_confirmation_instructions # manually send instructions
- # User.find(1).resend_confirmation! # generates a new token and resent it
+ #
module Confirmable
extend ActiveSupport::Concern
View
19 lib/devise/models/database_authenticatable.rb
@@ -5,21 +5,20 @@ module Models
# Authenticable Module, responsible for encrypting password and validating
# authenticity of a user while signing in.
#
- # Configuration:
+ # == Options
#
- # You can overwrite configuration values by setting in globally in Devise,
- # using devise method or overwriting the respective instance method.
+ # DatabaseAuthenticable adds the following options to devise_for:
#
- # pepper: encryption key used for creating encrypted password. Each time
- # password changes, it's gonna be encrypted again, and this key
- # is added to the password and salt to create a secure hash.
- # Always use `rake secret' to generate a new key.
+ # * +pepper+: encryption key used for creating encrypted password. Each time
+ # password changes, it's gonna be encrypted again, and this key is added
+ # to the password and salt to create a secure hash. Always use `rake secret'
+ # to generate a new key.
#
- # stretches: defines how many times the password will be encrypted.
+ # * +stretches+: defines how many times the password will be encrypted.
#
- # encryptor: the encryptor going to be used. By default :sha1.
+ # * +encryptor+: the encryptor going to be used. By default :sha1.
#
- # Examples:
+ # == Examples
#
# User.find(1).valid_password?('password123') # returns true/false
#
View
13 lib/devise/models/lockable.rb
@@ -7,13 +7,14 @@ module Models
# will unlock the user automatically after some configured time (ie 2.hours).
# It's also possible to setup lockable to use both email and time strategies.
#
- # Configuration:
+ # == Options
#
- # maximum_attempts: how many attempts should be accepted before blocking the user.
- # lock_strategy: lock the user account by :failed_attempts or :none.
- # unlock_strategy: unlock the user account by :time, :email, :both or :none.
- # unlock_in: the time you want to lock the user after to lock happens. Only
- # available when unlock_strategy is :time or :both.
+ # Lockable adds the following options to devise_for:
+ #
+ # * +maximum_attempts+: how many attempts should be accepted before blocking the user.
+ # * +lock_strategy+: lock the user account by :failed_attempts or :none.
+ # * +unlock_strategy+: unlock the user account by :time, :email, :both or :none.
+ # * +unlock_in+: the time you want to lock the user after to lock happens. Only available when unlock_strategy is :time or :both.
#
module Lockable
extend ActiveSupport::Concern
View
13 lib/devise/models/oauthable.rb
@@ -1,5 +1,18 @@
module Devise
module Models
+ # Adds OAuth support to your model. The whole workflow is deeply discussed in the
+ # README. This module adds just a class +oauth_access_token+ helper to your model
+ # which assists you on creating an access token. All the other OAuth hooks in
+ # Devise must be implemented by yourself in your application.
+ #
+ # == Options
+ #
+ # Oauthable adds the following options to devise_for:
+ #
+ # * +oauth_providers+: Which providers are avaialble to this model. It expects an array:
+ #
+ # devise_for :database_authenticatable, :oauthable, :oauth_providers => [:twitter]
+ #
module Oauthable
extend ActiveSupport::Concern
View
6 lib/devise/models/recoverable.rb
@@ -1,8 +1,9 @@
module Devise
module Models
- # Recoverable takes care of reseting the user password and send reset instructions
- # Examples:
+ # Recoverable takes care of reseting the user password and send reset instructions.
+ #
+ # == Examples
#
# # resets the user password and save the record, true if valid passwords are given, otherwise false
# User.find(1).reset_password!('password123', 'password123')
@@ -13,6 +14,7 @@ module Models
#
# # creates a new token and send it with instructions about how to reset the password
# User.find(1).send_reset_password_instructions
+ #
module Recoverable
extend ActiveSupport::Concern
View
27 lib/devise/models/rememberable.rb
@@ -11,24 +11,23 @@ module Models
# You probably wouldn't use rememberable methods directly, they are used
# mostly internally for handling the remember token.
#
- # Configuration:
+ # == Options
#
- # remember_for: the time you want the user will be remembered without
- # asking for credentials. After this time the user will be
- # blocked and will have to enter his credentials again.
- # This configuration is also used to calculate the expires
- # time for the cookie created to remember the user.
- # 2.weeks by default.
+ # Rememberable adds the following options in devise_for:
#
- # remember_across_browsers: if true, a valid remember token can be
- # re-used between multiple browsers.
- # True by default.
+ # * +remember_for+: the time you want the user will be remembered without
+ # asking for credentials. After this time the user will be blocked and
+ # will have to enter his credentials again. This configuration is als
+ # used to calculate the expires time for the cookie created to remember
+ # the user. By default remember_for is 2.weeks.
#
- # extend_remember_period: if true, extends the user's remember period
- # when remembered via cookie.
- # False by default.
+ # * +remember_across_browsers+: if a valid remember token can be re-used
+ # between multiple browsers. By default remember_across_browsers is true.
#
- # Examples:
+ # * +extend_remember_period+: if true, extends the user's remember period
+ # when remembered via cookie. False by default.
+ #
+ # == Examples
#
# User.find(1).remember_me! # regenerating the token
# User.find(1).forget_me! # clearing the token
View
11 lib/devise/models/timeoutable.rb
@@ -7,9 +7,16 @@ module Models
# will be asked for credentials again, it means, he/she will be redirected
# to the sign in page.
#
- # Configuration:
+ # == Options
+ #
+ # Timeoutable adds the following options to devise_for:
+ #
+ # * +timeout_in+: the interval to timeout the user session without activity.
+ #
+ # == Examples
+ #
+ # user.timedout?(30.minutes.ago)
#
- # timeout_in: the time you want to timeout the user session without activity.
module Timeoutable
extend ActiveSupport::Concern
View
7 lib/devise/models/token_authenticatable.rb
@@ -8,12 +8,11 @@ module Models
# This module only provides a few helpers to help you manage the token. Creating and resetting
# the token is your responsibility.
#
- # == Configuration:
+ # == Options
#
- # You can overwrite configuration values by setting in globally in Devise (+Devise.setup+),
- # using devise method, or overwriting the respective instance method.
+ # TokenAuthenticable adds the following options to devise_for:
#
- # +token_authentication_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
+ # * +token_authentication_key+: Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
#
module TokenAuthenticatable
extend ActiveSupport::Concern
View
11 lib/devise/models/validatable.rb
@@ -1,10 +1,17 @@
module Devise
module Models
-
# Validatable creates all needed validations for a user email and password.
# It's optional, given you may want to create the validations by yourself.
# Automatically validate if the email is present, unique and it's format is
- # valid. Also tests presence of password, confirmation and length
+ # valid. Also tests presence of password, confirmation and length.
+ #
+ # == Options
+ #
+ # Validatable adds the following options to devise_for:
+ #
+ # * +email_regexp+: the regular expression used to validate e-mails;
+ # * +password_length+: a range expressing password length. Defaults to 6..20.
+ #
module Validatable
# All validations used by this module.
VALIDATIONS = [ :validates_presence_of, :validates_uniqueness_of, :validates_format_of,
View
30 lib/generators/devise/templates/devise.rb
@@ -35,16 +35,16 @@
# config.http_authentication_realm = "Application"
# ==> Configuration for :database_authenticatable
- # For bcrypt, this is the cost for hashing the password and defaults to 10. If
- # using other encryptors, it sets how many times you want the password re-encrypted.
- config.stretches = 10
-
# Define which will be the encryption algorithm. Devise also supports encryptors
# from others authentication tools as :clearance_sha1, :authlogic_sha512 (then
# you should set stretches above to 20 for default behavior) and :restful_authentication_sha1
# (then you should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper)
config.encryptor = :bcrypt
+ # For bcrypt, this is the cost for hashing the password and defaults to 10. If
+ # using other encryptors, it sets how many times you want the password re-encrypted.
+ config.stretches = 10
+
# Setup a pepper to generate the encrypted password.
config.pepper = <%= ActiveSupport::SecureRandom.hex(64).inspect %>
@@ -126,17 +126,21 @@
# should add them to the navigational formats lists. Default is [:html]
# config.navigational_formats = [:html, :iphone]
+ # ==> OAuth2
+ # Add a new OAuth2 provider. Check the README for more information on setting
+ # up on your models and hooks.
+ # config.oauth :github, 'APP_ID', 'APP_SECRET',
+ # :site => 'https://github.com/',
+ # :authorize_path => '/login/oauth/authorize',
+ # :access_token_path => '/login/oauth/access_token',
+ # :scope => %w(user public_repo)
+
# ==> Warden configuration
- # If you want to use other strategies, that are not (yet) supported by Devise,
- # you can configure them inside the config.warden block. The example below
- # allows you to setup OAuth, using http://github.com/roman/warden_oauth
+ # If you want to use other strategies, that are not supported by Devise, or
+ # change the failure app, you can configure them inside the config.warden block.
#
# config.warden do |manager|
- # manager.oauth(:twitter) do |twitter|
- # twitter.consumer_secret = <YOUR CONSUMER SECRET>
- # twitter.consumer_key = <YOUR CONSUMER KEY>
- # twitter.options :site => 'http://twitter.com'
- # end
- # manager.default_strategies(:scope => :user).unshift :twitter_oauth
+ # manager.failure_app = AnotherApp
+ # manager.default_strategies(:scope => :user).unshift :some_external_strategy
# end
end
Please sign in to comment.
Something went wrong with that request. Please try again.