This gem provides the following features:
- Seamless integration with:
- ng-token-auth for AngularJS
- Angular2-Token for Angular2
- jToker for jQuery
- Oauth2 authentication using OmniAuth.
- Email authentication using Devise, including:
- User registration
- Password reset
- Account updates
- Account deletion
- Support for multiple user models.
- It is secure.
Here is a demo of this app running with the ng-token-auth module and AngularJS.
Here is a demo of this app running with the Angular2-Token service and Angular2.
Here is a demo of this app using the jToker plugin and React.
The fully configured api used in these demos can be found here.
Please read the issue reporting guidelines before posting issues.
- Dependencies
- Configuration TL;DR
- Usage TL;DR
- Configuration Continued
- Usage Continued
- Issue Reporting Guidelines
- FAQ
- Conceptual Diagrams
- Security
- Callouts
- Contribution Guidelines
This project leverages the following gems:
Add the following to your Gemfile
:
gem 'devise_token_auth'
Then install the gem using bundle:
bundle install
You will need to create a user model, define routes, include concerns, and you may want to alter some of the default settings for this gem. Run the following command for an easy one-step installation:
rails g devise_token_auth:install [USER_CLASS] [MOUNT_PATH]
Example:
rails g devise_token_auth:install User auth
This generator accepts the following optional arguments:
Argument | Default | Description |
---|---|---|
USER_CLASS | User |
The name of the class to use for user authentication. |
MOUNT_PATH | auth |
The path at which to mount the authentication routes. Read more. |
The following events will take place when using the install generator:
-
An initializer will be created at
config/initializers/devise_token_auth.rb
. Read more. -
A model will be created in the
app/models
directory. If the model already exists, a concern will be included at the top of the file. Read more. -
Routes will be appended to file at
config/routes.rb
. Read more. -
A concern will be included by your application controller at
app/controllers/application_controller.rb
. Read more. -
A migration file will be created in the
db/migrate
directory. Inspect the migrations file, add additional columns if necessary, and then run the migration:rake db:migrate
You may also need to configure the following items:
- OmniAuth providers when using 3rd party oauth2 authentication. Read more.
- Cross Origin Request Settings when using cross-domain clients. Read more.
- Email when using email registration. Read more.
- Multiple model support may require additional steps. Read more.
Jump here for more configuration information.
The following routes are available for use by your client. These routes live relative to the path at which this engine is mounted (auth
by default). These routes correspond to the defaults used by the ng-token-auth module for AngularJS and the jToker plugin for jQuery.
path | method | purpose |
---|---|---|
/ | POST | Email registration. Requires email , password , and password_confirmation params. A verification email will be sent to the email address provided. Accepted params can be customized using the devise_parameter_sanitizer system. |
/ | DELETE | Account deletion. This route will destroy users identified by their access_token and client headers. |
/ | PUT | Account updates. This route will update an existing user's account settings. The default accepted params are password and password_confirmation , but this can be customized using the devise_parameter_sanitizer system. If config.check_current_password_before_update is set to :attributes the current_password param is checked before any update, if it is set to :password the current_password param is checked only if the request updates user password. |
/sign_in | POST | Email authentication. Requires email and password as params. This route will return a JSON representation of the User model on successful login along with the Authorization and client in the header of the response. |
/sign_out | DELETE | Use this route to end the user's current session. This route will invalidate the user's authentication token. You must pass in client , and Authorization in the request headers. |
/:provider | GET | Set this route as the destination for client authentication. Ideally this will happen in an external window or popup. Read more. |
/:provider/callback | GET/POST | Destination for the oauth2 provider's callback uri. postMessage events containing the authenticated user's data will be sent back to the main client window from this page. Read more. |
/validate_token | GET | Use this route to validate tokens on return visits to the client. Requires client , and Authorization as params. These values should correspond to the columns in your User table of the same names. |
/password | POST | Use this route to send a password reset confirmation email to users that registered by email. Accepts email and redirect_url as params. The user matching the email param will be sent instructions on how to reset their password. redirect_url is the url to which the user will be redirected after visiting the link contained in the email. |
/password | PUT | Use this route to change users' passwords. Requires password and password_confirmation as params. This route is only valid for users that registered by email (OAuth2 users will receive an error). It also checks current_password if config.check_current_password_before_update is not set false (disabled by default). |
/password/edit | GET | Verify user by password reset token. This route is the destination URL for password reset confirmation. This route must contain reset_password_token and redirect_url params. These values will be set automatically by the confirmation email that is generated by the password reset request. |
Jump here for more usage information.
The following settings are available for configuration in config/initializers/devise_token_auth.rb
:
Name | Default | Description |
---|---|---|
token_lifespan |
2.weeks |
Set the length of your tokens' lifespans. Users will need to re-authenticate after this duration of time has passed since their last login. |
omniauth_prefix |
"/omniauth" |
This route will be the prefix for all oauth2 redirect callbacks. For example, using the default '/omniauth' setting, the github oauth2 provider will redirect successful authentications to '/omniauth/github/callback'. Read more. |
default_confirm_success_url |
nil |
By default this value is expected to be sent by the client so that the API knows where to redirect users after successful email confirmation. If this param is set, the API will redirect to this value when no value is provided by the client. |
default_password_reset_url |
nil |
By default this value is expected to be sent by the client so that the API knows where to redirect users after successful password resets. If this param is set, the API will redirect to this value when no value is provided by the client. |
redirect_whitelist |
nil |
As an added security measure, you can limit the URLs to which the API will redirect after email token validation (password reset, email confirmation, etc.). This value should be an array containing matches to the client URLs to be visited after validation. Wildcards are supported. |
enable_standard_devise_support |
false |
By default, only Bearer Token authentication is implemented out of the box. If, however, you wish to integrate with legacy Devise authentication, you can do so by enabling this flag. NOTE: This feature is highly experimental! |
remove_tokens_after_password_reset |
false |
By default, old tokens are not invalidated when password is changed. Enable this option if you want to make passwords updates to logout other devices. |
default_callbacks |
true |
By default User model will include the DeviseTokenAuth::Concerns::UserOmniauthCallbacks concern, which has email , uid validations & uid synchronization callbacks. |
Additionally, you can configure other aspects of devise by manually creating the traditional devise.rb file at config/initializers/devise.rb
. Here are some examples of what you can do in this file:
Devise.setup do |config|
# The e-mail address that mail will appear to be sent from
# If absent, mail is sent from "[email protected]"
config.mailer_sender = "[email protected]"
# If using rails-api, you may want to tell devise to not use ActionDispatch::Flash
# middleware b/c rails-api does not include it.
# See: http://stackoverflow.com/q/19600905/806956
config.navigational_formats = [:json]
end
If you wish to use omniauth authentication, add all of your desired authentication provider gems to your Gemfile
.
OmniAuth example using github, facebook, and google:
gem 'omniauth-github'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
Then run bundle install
.
In config/initializers/omniauth.rb
, add the settings for each of your providers.
These settings must be obtained from the providers themselves.
Example using github, facebook, and google:
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :github, ENV['GITHUB_KEY'], ENV['GITHUB_SECRET'], scope: 'email,profile'
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET']
provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET']
end
The above example assumes that your provider keys and secrets are stored in environmental variables. Use the figaro gem (or dotenv or secrets.yml or equivalent) to accomplish this.
The "Callback URL" setting that you set with your provider must correspond to the omniauth prefix setting defined by this app. This will be different than the omniauth route that is used by your client application.
For example, the demo app uses the default omniauth_prefix
setting /omniauth
, so the "Authorization callback URL" for github must be set to "http://devise-token-auth-demo.herokuapp.com**/omniauth**/github/callback".
Github example for the demo site:
The url for github authentication will be different for the client. The client should visit the API at /[MOUNT_PATH]/:provider
for omniauth authentication.
For example, given that the app is mounted using the following settings:
# config/routes.rb
mount_devise_token_auth_for 'User', at: 'auth'
The client configuration for github should look like this:
Angular.js setting for authenticating using github:
angular.module('myApp', ['ng-token-auth'])
.config(function($authProvider) {
$authProvider.configure({
apiUrl: 'http://api.example.com'
authProviderPaths: {
github: '/auth/github' // <-- note that this is different than what was set with github
}
});
});
**jToker settings for github should look like this:
$.auth.configure({
apiUrl: 'http://api.example.com',
authProviderPaths: {
github: '/auth/github' // <-- note that this is different than what was set with github
}
});
This incongruence is necessary to support multiple user classes and mounting points.
If you receive redirect-uri-mismatch
errors from your provider when using pow or xip.io urls, set the following in your development config:
# config/environments/development.rb
# when using pow
OmniAuth.config.full_host = "http://app-name.dev"
# when using xip.io
OmniAuth.config.full_host = "http://xxx.xxx.xxx.app-name.xip.io"
If you wish to use email authentication, you must configure your Rails application to send email. Read here for more information.
I recommend using mailcatcher for development.
# config/environments/development.rb
Rails.application.configure do
config.action_mailer.default_url_options = { :host => 'your-dev-host.dev' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { :address => 'your-dev-host.dev', :port => 1025 }
end
If you wish to send custom e-mails instead of using the default devise templates, you can do that too.
Devise Token Auth ships with intelligent default wording for everything you need. But that doesn't mean you can't make it more awesome. You can override the devise defaults by creating a YAML file at config/locales/devise.en.yml
and assigning whatever custom values you want. For example, to customize the subject line of your devise e-mails, you could do this:
en:
devise:
mailer:
confirmation_instructions:
subject: "Please confirm your e-mail address"
reset_password_instructions:
subject: "Reset password request"
If your API and client live on different domains, you will need to configure your Rails API to allow cross origin requests. The rack-cors gem can be used to accomplish this.
The following dangerous example will allow cross domain requests from any domain. Make sure to whitelist only the needed domains.
# gemfile
gem 'rack-cors', :require => 'rack/cors'
# config/application.rb
module YourApp
class Application < Rails::Application
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
:expose => ['Authorization', 'client'],
:methods => [:get, :post, :options, :delete, :put]
end
end
end
end
Make extra sure that the Access-Control-Expose-Headers
includes Authorization
, and client
(as is set in the example above by the:expose
param). If your client experiences erroneous 401 responses, this is likely the cause.
CORS may not be possible with older browsers (IE8, IE9). I usually set up a proxy for those browsers. See the ng-token-auth readme or the jToker readme for more information.
The authentication routes must be mounted to your project. This gem includes a route helper for this purpose:
mount_devise_token_auth_for
- similar to devise_for
, this method is used to append the routes necessary for user authentication. This method accepts the following arguments:
Argument | Type | Default | Description |
---|---|---|---|
class_name |
string | 'User' | The name of the class to use for authentication. This class must include the model concern described here. |
options |
object | {at: 'auth'} | The routes to be used for authentication will be prefixed by the path specified in the at param of this object. |
Example:
# config/routes.rb
mount_devise_token_auth_for 'User', at: 'auth'
Any model class can be used, but the class will need to include DeviseTokenAuth::Concerns::User
for authentication to work properly.
You can mount this engine to any route that you like. /auth
is used by default to conform with the defaults of the ng-token-auth module and the jToker plugin.
This gem includes a Rails concern called DeviseTokenAuth::Concerns::SetUserByToken
. Include this concern to provide access to controller methods such as authenticate_user!
, user_signed_in?
, etc.
The concern also runs an after_action that changes the auth token after each request.
It is recommended to include the concern in your base ApplicationController
so that all children of that controller include the concern as well.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
end
This gem provides access to all of the following devise helpers:
Method | Description |
---|---|
before_action :authenticate_user! |
Returns a 401 error unless a User is signed-in. |
current_user |
Returns the currently signed-in User , or nil if unavailable. |
user_signed_in? |
Returns true if a User is signed in, otherwise false . |
devise_token_auth_group |
Operate on multiple user classes as a group. Read more |
Note that if the model that you're trying to access isn't called User
, the helper method names will change. For example, if the user model is called Admin
, the methods would look like this:
before_action :authenticate_admin!
admin_signed_in?
current_admin
# app/controllers/test_controller.rb
class TestController < ApplicationController
before_action :authenticate_user!
def members_only
render json: {
data: {
message: "Welcome #{current_user.name}",
user: current_user
}
}, status: 200
end
end
The authentication information should be included by the client in the headers of each request. The headers follow the RFC 6750 Bearer Token format:
"Authorization": "Bearer wwwww",
"client": "xxxxx"
The authentication headers consists of the following params:
param | description |
---|---|
Authorization |
This serves as the user's password for each request. A hashed version of this value is stored in the database for later comparison. This value should be changed on each request. |
client |
This enables the use of multiple simultaneous sessions on different clients. (For example, a user may want to be authenticated on both their phone and their laptop at the same time.) |
The authentication headers required for each request will be available in the response from the previous request. If you are using the ng-token-auth AngularJS module or the jToker jQuery plugin, this functionality is already provided.
Typical use of this gem will not require the use of any of the following model methods. All authentication should be handled invisibly by the controller concerns described above.
Models that include the DeviseTokenAuth::Concerns::User
concern will have access to the following public methods (read the above section for context on token
and client
):
-
valid_token?
: check if an authentication token is valid. Accepts atoken
andclient
as arguments. Returns a boolean.Example:
# extract token + client_id from auth header client_id = request.headers['client'] token = request.headers['Authorization'] @resource.valid_token?(token, client_id)
-
create_new_auth_token
: creates a new auth token with all of the necessary metadata. Acceptsclient
as an optional argument. Will generate a newclient
if none is provided. Returns the authentication headers that should be sent by the client as an object.Example:
# extract client_id from auth header client_id = request.headers['client'] # update token, generate updated auth headers for response new_auth_header = @resource.create_new_auth_token(client_id) # update response with the header that will be required by the next request response.headers.merge!(new_auth_header)
-
build_auth_header
: generates the auth header that should be sent to the client with the next request. Acceptstoken
andclient
as arguments. Returns a string.Example:
# create client id and token client_id = SecureRandom.urlsafe_base64(nil, false) token = SecureRandom.urlsafe_base64(nil, false) # store client + token in user's token hash @resource.tokens[client_id] = { token: BCrypt::Password.create(token), expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i } # generate auth headers for response new_auth_header = @resource.build_auth_header(token, client_id) # update response with the header that will be required by the next request response.headers.merge!(new_auth_header)
This gem supports the use of multiple user models. One possible use case is to authenticate visitors using a model called User
, and to authenticate administrators with a model called Admin
. Take the following steps to add another authentication model to your app:
- Run the install generator for the new model.
rails g devise_token_auth:install Admin admin_auth
This will create the Admin
model and define the model's authentication routes with the base path /admin_auth
.
- Define the routes to be used by the
Admin
user within adevise_scope
.
Example:
Rails.application.routes.draw do
# when using multiple models, controllers will default to the first available
# devise mapping. routes for subsequent devise mappings will need to defined
# within a `devise_scope` block
# define :users as the first devise mapping:
mount_devise_token_auth_for 'User', at: 'auth'
# define :admins as the second devise mapping. routes using this class will
# need to be defined within a devise_scope as shown below
mount_devise_token_auth_for "Admin", at: 'admin_auth'
# this route will authorize requests using the User class
get 'demo/members_only', to: 'demo#members_only'
# routes within this block will authorize requests using the Admin class
devise_scope :admin do
get 'demo/admins_only', to: 'demo#admins_only'
end
end
- Configure any
Admin
restricted controllers. Controllers will now have access to the methods described here:
before_action :authenticate_admin!
current_admin
admin_signed_in?
It is also possible to control access to multiple user types at the same time using groups. The following example shows how to limit controller access to both User
and Admin
users.
class DemoGroupController < ApplicationController
devise_token_auth_group :member, contains: [:user, :admin]
before_action :authenticate_member!
def members_only
render json: {
data: {
message: "Welcome #{current_member.name}",
user: current_member
}
}, status: 200
end
end
In the above example, the following methods will be available (in addition to current_user
, current_admin
, etc.):
before_action: :authenticate_member!
current_member
member_signed_in?
By default, almost all of the Devise modules are included:
You may not want all of these features enabled in your app. That's OK! You can mix and match to suit your own unique style.
The following example shows how to disable email confirmation.
Just list the devise modules that you want to include before including the DeviseTokenAuth::Concerns::User
model concern.
# app/models/user.rb
class User < ActiveRecord::Base
# notice this comes BEFORE the include statement below
# also notice that :confirmable is not included in this block
devise :database_authenticatable, :recoverable,
:trackable, :validatable, :registerable,
:omniauthable
# note that this include statement comes AFTER the devise block above
include DeviseTokenAuth::Concerns::User
end
Some features include routes that you may not want mounted to your app. The following example shows how to disable OAuth and its routes.
First instruct the model not to include the omniauthable
module.
# app/models/user.rb
class User < ActiveRecord::Base
# notice that :omniauthable is not included in this block
devise :database_authenticatable, :confirmable,
:recoverable, :trackable, :validatable,
:registerable
include DeviseTokenAuth::Concerns::User
end
Now tell the route helper to skip
mounting the omniauth_callbacks
controller:
Rails.application.routes.draw do
# config/routes.rb
mount_devise_token_auth_for 'User', at: 'auth', skip: [:omniauth_callbacks]
end
The built-in controllers can be overridden with your own custom controllers.
For example, the default behavior of the validate_token
method of the TokenValidationController
is to return the User
object as json (sans password and token data). The following example shows how to override the validate_token
action to include a model method as well.
# config/routes.rb
Rails.application.routes.draw do
...
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
token_validations: 'overrides/token_validations'
}
end
# app/controllers/overrides/token_validations_controller.rb
module Overrides
class TokenValidationsController < DeviseTokenAuth::TokenValidationsController
def validate_token
# @resource will have been set by set_user_by_token concern
if @resource
render json: {
data: @resource.as_json(methods: :calculate_operating_thetan)
}
else
render json: {
success: false,
errors: ["Invalid login credentials"]
}, status: 401
end
end
end
end
To customize json rendering, implement the following protected controller methods, for success methods, assume that the @resource object is available:
- render_create_error_missing_confirm_success_url
- render_create_error_redirect_url_not_allowed
- render_create_success
- render_create_error
- render_create_error_email_already_exists
- render_update_success
- render_update_error
- render_update_error_user_not_found
- render_new_error
- render_create_success
- render_create_error_not_confirmed
- render_create_error_bad_credentials
- render_destroy_success
- render_destroy_error
- render_create_error_missing_email
- render_create_error_missing_redirect_url
- render_create_error_not_allowed_redirect_url
- render_create_success
- render_create_error
- render_update_error_unauthorized
- render_update_error_password_not_required
- render_update_error_missing_password
- render_update_success
- render_update_error
- render_validate_token_success
- render_validate_token_error
mount_devise_token_auth_for 'User', at: 'auth', controllers: {
confirmations: 'devise_token_auth/confirmations',
passwords: 'devise_token_auth/passwords',
omniauth_callbacks: 'devise_token_auth/omniauth_callbacks',
registrations: 'devise_token_auth/registrations',
sessions: 'devise_token_auth/sessions',
token_validations: 'devise_token_auth/token_validations'
}
Note: Controller overrides must implement the expected actions of the controllers that they replace.
It may be that you simply want to add behavior to existing controllers without having to re-implement their behavior completely. In this case, you can do so by creating a new controller that inherits from any of DeviseTokenAuth's controllers, overriding whichever methods you'd like to add behavior to by passing a block to super
:
class Custom::RegistrationsController < DeviseTokenAuth::RegistrationsController
def create
super do |resource|
resource.do_something(extra)
end
end
end
Your block will be performed just before the controller would usually render a successful response.
You will probably want to override the default email templates for email sign-up and password-reset confirmation. Run the following command to copy the email templates into your app:
rails generate devise_token_auth:install_views
This will create two new files:
app/views/devise/mailer/reset_password_instructions.html.erb
app/views/devise/mailer/confirmation_instructions.html.erb
These files may be edited to suit your taste. You can customize the e-mail subjects like this.
Note: if you choose to modify these templates, do not modify the link_to
blocks unless you absolutely know what you are doing.
When posting issues, please include the following information to speed up the troubleshooting process:
- Version: which version of this gem (and ng-token-auth, jToker or Angular2-Token if applicable) are you using?
- Request and response headers: these can be found in the "Network" tab of your browser's web inspector.
- Rails Stacktrace: this can be found in the
log/development.log
of your API. - Environmental Info: How is your application different from the reference implementation? This may include (but is not limited to) the following details:
- Routes: are you using some crazy namespace, scope, or constraint?
- Gems: are you using MongoDB, Grape, RailsApi, ActiveAdmin, etc.?
- Custom Overrides: what have you done in terms of custom controller overrides?
- Custom Frontend: are you using ng-token-auth, jToker, Angular2-Token, or something else?
Yes! But you will need to enable the support of separate routes for standard Devise. So do something like this:
DeviseTokenAuth.setup do |config|
# config.enable_standard_devise_support = false
end
Rails.application.routes.draw do
# standard devise routes available at /users
# NOTE: make sure this comes first!!!
devise_for :users
# token auth routes available at /api/v1/auth
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
end
end
end
Removing the new
routes will require significant modifications to devise. If the inclusion of the new
routes is causing your app any problems, post an issue in the issue tracker and it will be addressed ASAP.
I'm having trouble using this gem alongside ActiveAdmin...
For some odd reason, ActiveAdmin extends from your own app's ApplicationController
. This becomes a problem if you include the DeviseTokenAuth::Concerns::SetUserByToken
concern in your app's ApplicationController
.
The solution is to use two separate ApplicationController
classes - one for your API, and one for ActiveAdmin. Something like this:
# app/controllers/api_controller.rb
# API routes extend from this controller
class ApiController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
end
# app/controllers/application_controller.rb
# leave this for ActiveAdmin, and any other non-api routes
class ApplicationController < ActionController::Base
end
None of the following information is required to use this gem, but read on if you're curious.
Tokens should be invalidated after each request to the API. The following diagram illustrates this concept:
These measures are taken by default when using this gem.
By default, the API should update the auth token for each request (read more). But sometimes it's necessary to make several concurrent requests to the API, for example:
$scope.getResourceData = function() {
$http.get('/api/restricted_resource_1').success(function(resp) {
// handle response
$scope.resource1 = resp.data;
});
$http.get('/api/restricted_resource_2').success(function(resp) {
// handle response
$scope.resource2 = resp.data;
});
};
This gem takes the following steps to ensure security.
This gem uses auth tokens that are:
- changed after every request (can be turned off),
- of cryptographic strength,
- hashed using BCrypt (not stored in plain-text),
- securely compared (to protect against timing attacks),
- invalidated after 2 weeks (thus requiring users to login again)
These measures were inspired by this stackoverflow post.
This gem further mitigates timing attacks by using this technique.
But the most important step is to use HTTPS. You are on the hook for that.
Thanks to the following contributors:
- @booleanbetrayal
- @guilhermesimoes
- @jasonswett
- @m2omou
- @smarquez1
- @jartek
- @nicolas-besnard
- @tbloncar
- @nickL
- @mchavarriagam
- Create a feature branch with your changes.
- Write some test cases.
- Make all the tests pass.
- Issue a pull request.
I will grant you commit access if you send quality pull requests.
To run the test suite do the following:
- Clone this repo
- Run
bundle install
- Run
rake db:migrate
- Run
RAILS_ENV=test rake db:migrate
- Run
guard
The last command will open the guard test-runner. Guard will re-run each test suite when changes are made to its corresponding files.
To run just one test:
- Clone this repo
- Run
bundle install
- Run
rake db:migrate
- Run
RAILS_ENV=test rake db:migrate
- See this link for various ways to run a single file or a single test: http://flavio.castelli.name/2010/05/28/rails_execute_single_test/
This project uses the WTFPL