Basic example application with Devise default signup/login views.

cat >> Gemfile <<HERE_DOC

# user authentication
gem 'devise', '~> 4.5.0'
rails generate devise:install
git add . && git commit -m "rails g devise:install"
rails g devise User
rake db:migrate
git add . && git commit -m "rails g devise user"

# optional generate views
rails g devise:views && git add . && git commit -m "rails g devise:views"
# optional create home page and navbar links
rails g controller pages home
sed -i config/routes.rb -e "/^end$/i \\
  # root page\n\
  root 'pages#home'\
sed -i app/views/layouts/application.html.erb -e "/<body>/a \\
    <%= link_to 'Home', root_path %>\n\
    <% if current_user %>\n\
      <strong><%= %></strong>\n\
      <%= link_to 'Sign out', destroy_user_session_path, method: :delete %>\n\
    <% else %>\n\
      <%= link_to 'Sign up', new_user_registration_path %>\n\
      <%= link_to 'Log in', new_user_session_path %>\n\
    <% end %>\
git add . && git commit -m "Adding login/logout header in layout"

You can add facebook auth below.

Read config/initializers/devise.rb about default configuration.

You can use before_action :authenticate_user! in controllers that will redirect to /users/sign_in. You need to set up emails to actually receive registration email.

If you enable lockable than user will be locked when number of failed login attempts reaches 20. You can send internal notification when that happens by overriding devise mailer and method def unlock_instructions(record, token, opts={})

When user is logged in, than in session there is a id of current_user session['warden.user.user.key'] # => [[9], "$2a$10$TUfyHaPAWV.A1/6JLuCTGO"]

Errors like ActionView::Template::Error (undefined method new_confirmation_path for Did you mean? new_user_confirmation_path user_confirmation_path): occurs when you add, for example confirmable in model, but gem are already loaded in spring, so you need to spring stop and restart rails server.

To enable additional fields to be permited attributes for new columns, for example :username

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?


  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])

Errors like NoMethodError (undefined method 'users_url' for #<DeviseRegistrationsController:0x007ff6068d92b8>):

IF you need to check user.authenticate_with password you can use valid password

user.valid_password? 'new_password'

There are some modules which you can use

# app/models/user.rb
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable,

Devise and Omniauth

Read wiki to add facebook and google authentication. It’s easy installation. For facebook we use omniauth-facebook and excellent rails casts #360

cat >> Gemfile <<HERE_DOC
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2', '0.2.5'
# fix to 1.3.1 because of redirect_uri_mismatch
gem "omniauth-oauth2", '1.3.1'
# 1.2.1 to skip_jwt issue
# Could not find a valid mapping for path "/omniauth/google_oauth2/callback"
rails g migration AddOmniauthToUsers provider:index uid:index
rake db:migrate
sed -i config/initializers/devise.rb -e '/APP_ID/a \
  config.omniauth :facebook,\
                  scope: "public_profile,email",\
                  info_fields: "email,name"\
  config.omniauth :google_oauth2,\
                  {} # skip_jwt: true'
# skip_jwt only needed for omniauth > 1.2.2

sed -i config/secrets.yml -e '/^test:/i \
  # Facebook Autentication\
  facebook_key: <%= ENV["FACEBOOK_KEY"] %>\
  facebook_secret: <%= ENV["FACEBOOK_SECRET"] %>\
  # Google signup\
  google_client_id: <%= ENV["GOOGLE_CLIENT_ID"] %>\
  google_client_secret: <%= ENV["GOOGLE_CLIENT_SECRET"] %>\

If you need to send some params to callback (for example current user, or some other state) you can do it and access using params['omniauth.params'] env field. There are also: ["omniauth.strategy", "omniauth.origin", "omniauth.params", "omniauth.auth"]. omniauth.origin is usefull to redirect back to the page on which he logs in.

sed -i app/views/layouts/application.html.erb -e '/<body>/a \
<%= link_to "Sign in with Facebook",
user_facebook_omniauth_authorize_path(my_param: 1) %>'

sed -i app/models/user.rb -e '/end/i \
  def self.from_omniauth(auth)\
    user = find_by(email:\
    return user if user\
    # user changed his email on facebook\
    user = find_by(provider: auth.provider, uid: auth.uid)\
    return user if user\
    # create new user with some password
    user = User.create!(\
      password: Devise.friendly_token[0, 20],\
      provider: auth.provider,\
      uid: auth.uid,\
    user.skip_confirmation! # this will just add confirmed_at =\
    # = # assuming the user model has a name\
    # user.image = # assuming the user model has an image\

vi app/models/user.rb # add :omniauthable
# no need for , :omniauth_providers => [:facebook], but :omniauthable is needed

# config/routes.rb
  devise_for :users, controllers: {
    omniauth_callbacks: 'devise/my_omniauth_callbacks',
    confirmations: 'devise/my_confirmations',
    registrations: 'devise/my_registrations',

# app/controllers/devise/my_omniauth_callbacks_controller.rb
module Devise
  class MyOmniauthCallbacksController < OmniauthCallbacksController
    # data is in request.env["omniauth.auth"]
    [:facebook, :google_oauth2, :twitter].each do |provider| # yaho_oauth2
      define_method provider do
        # use request.env["omniauth.params"]["my_param"]
        @user = User.from_omniauth(request.env["omniauth.auth"])

        if @user.persisted?
          sign_in_and_redirect @user, event: :authentication
          # this will throw if @user is not activated
          set_flash_message(:notice, :success, kind: provider) if is_navigational_format?
          session["devise.facebook_data"] = request.env["omniauth.auth"]
          redirect_to new_user_registration_url
    def failure
      # this could be: no_authorization_code # when we did not whitelisted domain
      # on facebook app settings
      redirect_to root_path, alert: "Can't sign in. #{request.env['omniauth.error'].try(:message)} #{request.env['omniauth.error.type']}"

If you need multiple identities per account than create separate table for identity.

Note this situations:

  • user changed email address (lost password for old one) and updated on facebook profile. He should be able to add new email address, not just to change old one. Maybe he used the same password which he can not recover. If user can recover password only using original email than they are blocked. Hackerrank has a button to “Add another email”. So we should check if his facebook email address was updated and add add that email to our database as well.

  • user click forgot password, it change password, than it is asked to login, than he should not see again forgot password page
  • when user change the password it should stay logged in
      bypass_sign_in current_user

Facebook app

Is you use omniauth-facebook alone than there are problems with devise (it double redirect and raise csrf exception). So do not use with devise, or configure devise to use facebook auth.

  1. Create app on By default fb app is in development mode and can only be used by app admins, developers and testers. Add Contact Email if not already there on

  2. For production you need to go App Review and toggle switch on

  3. Add Facebook Login product and go to settings (before it was inside advance settings Find input for Valid OAuth redirect URIs and fill with all links that are redirected. You can find the link on facebook error page url You can add just domain (/omniauth/facebook/callback is not needed) Note that this are server url (not frontend url):

    • http://localhost.local:3003
    • http://localhost:9000 and for https also
    • https://localhost.local:3003

Changes are visible immediatelly. More on blog facebook share buttons.


  • no need to check if email is verified, since facebook will not allow unverified accounts to use oauth

Google console

Create a project in and enable Google+ API. Create OAuth 2.0 client ID (or edit exists one clicking on its name) and set Authorized redirect URIs to all urls that will be used (not just domain like for facebook, we need whole url path), like

  • http://localhost:3000/users/auth/google_oauth2/callback this is default devise_for :users
  • http://localhost:9000/omniauth/google_oauth2/callback and also for https
  • https://localhost.local/omniauth/google_oauth2/callback
  • also fill the Authorized JavaScript origins with domains and port like http://localhost.local.

Dont forget to save Changes needs 5 min to propagate

Twitter app

On you can create application. Setup callbacks urls to point to you server, but this is not required with twitter. app//keys will give you TWITTER_API_KEY and TWITTER_API_SECRET

Twitter response do not provide user’s email address.


Create app on and save to LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET. Add gem 'omniauth-linkedin'

Angular authentication

I tried two approaches for authentication

For demo usage checkout my repository angular-devise-ng-token-auth

ng-token-auth demo example

Angular ng-token-auth is used with devise-token-auth.

Run client with:

cd ng-token-auth
npm install
cd test && bower install
vi config/default.yml # edit API_URL to match server port
# SITE_DOMAIN is only required for sitemap
cd ..
gem install sass
gulp dev
gnome-open http://localhost:7777/

For server side you can see devise_token_auth_demo compare

cd devise_token_auth_demo
vi config/database.yml # change to your username
echo -e 'gem "letter_opener", :group => :development' >> Gemfile
sed -i '/end$/i \  config.action_mailer.delivery_method = :letter_opener' config/environments/development.rb

# hosts should be the same as in ng-token-auth/test/config/default.html
# config/environments/development.rb redirection url
# OmniAuth.config.full_host = "http://localhost.local:3003"

rails s

ng-token-auth from scratch with Yeoman

When use signin, he get access-token in Response Header. Than he uses that access-token for next request (Request Header), and for it he gets new access-token in response Token Header Format


Mount path is important. mount_devise_token_auth_for 'User', at: '/auth' so if you access api/v1 and api/v2/... it will send headers. If you mount under api/v1/auth than headers will not be send for api/v2/articles

Rails 4.2.5 use uppercase header names (Access-Token), but that does not affect the app. Also the method you fetch the resource does not matter, it could be $http, angular-rails-resource… headers will be send with ng-token-auth

rails new my_app
cd my_app
cat >> Gemfile <<HERE_DOC
# user auth with devise and ng-token-auth
gem 'devise_token_auth', '=0.1.37.beta4' # fix version because of url localhost:3000//api/ issue
# github: 'lynndylanhurley/devise_token_auth'
gem 'omniauth'
rails g devise_token_auth:install User auth
rake db:migrate
git add . && git commit -m "rails g devise_token_auth:install User auth"
rails g devise:install
git add . && git commit -m "rails g devise:install"

# leave original route auth for mount_devise_token_auth_for

# config/environments/development.rb
# OmniAuth.config.full_host = "http://localhost:9000/" # this is url for google callback, not needed since it will read from request

# on registration, do not raise exception
sed -i app/controllers/application_controller.rb \
-e '/end$/i\
  protect_from_forgery with: :null_session\
  respond_to :json'

# allow unconfirmed access
sed -i config/initializers/devise.rb -e '/config.allow_unconfirmed_access_for/a \
  config.allow_unconfirmed_access_for = 7.days'

Perform some common bootstrap stuff for secrets and emails and add oauth installation as above.

For client start with steps at 2015-11-26-angular-and-ruby-on-rails.

We will use md-dialog to show signup buttons or login form in another tab.

I have some problems to stay logged in immediatelly after log in and than hit refresh (while some params in url). We need to remove those params.

cd client
bower install ng-token-auth --save

# add module dependency
sed -i src/app/  -e "/]/i \  'ng-token-auth',"

# inject dependencies
sed -i src/app/components/navbar/ -e ":a;N;\$!ba;s/    NavbarController.*return/$(sed ':a;N;$!ba;s/\n/ћ/g;s:\\:\\\\:g;s:/:\\/:g;' <<'MY_TEXT'
    NavbarController = (moment, $mdDialog, $auth, $rootScope) ->
      dialog = null
      vm = this
      # "vm.creation" is avaible by directive option "bindToController: true"
      vm.relativeDate = moment(vm.creationDate).fromNow()

      vm.showLoginDialog = (ev) ->
        dialog = $
          controller: 'LoginController'
          controllerAs: 'vm'
          templateUrl: 'app/login/login.html'
          parent: angular.element(document.body)
          targetEvent: ev
          clickOutsideToClose: true

      vm.signOut = ->

      $rootScope.$on 'auth:login-success', (ev, user) ->
        $mdDialog.hide dialog

# add links
sed -i src/app/components/navbar/navbar.html \
-e '/Contact/a \
    <md-button href="#" ng-show="!$" ng-click="vm.showLoginDialog()">Try For Free</md-button>\
    <div ng-show="!!$">Hello { { $ }}</div>\
    <md-button href="#" ng-show="!!$" ng-click="vm.signOut()">Sign Out</md-button>'

# config routes since default /api/auth/facebook does not match rails /auth/:provider
sed -i src/app/ \
-e 's/config (/config ($authProvider, /' \
-e '$a\
      apiUrl: "/"\
        google:   "/auth/google_oauth2"\
        facebook: "/auth/facebook"'
mkdir src/app/login
cat > src/app/login/login.html <<\HERE_DOC
<md-dialog aria-label="login" ng-cloak>
    <div class="md-toolbar-tools">
      <h2>Try for free!</h2>
    <md-tabs md-selected="vm.tabsData.selectedIndex" md-align-tabs="bottom"
      <md-tab label="defalt">
        <md-content class="md-padding">
          <md-button ng-click="$root.authenticate('google')">Sign in with Gmail</md-button>
          <md-button ng-click="$root.authenticate('facebook')">Sign in with Facebook</md-button>
          <md-button ng-click="vm.tabsData.selectedIndex=1">
            Sign in with email

      <md-tab label="Email">
        <md-content class="md-padding">
          <form ng-submit="vm.submitLogin(vm.login, loginForm)" name="loginForm" role="form">
            <md-input-container class="md-block">
              <input ng-model="" ng-required="true" name="email" placeholder="Email" type="email">
              <div ng-messages="$error">
                <div ng-message="required">Email is required.</div>
                <div ng-message="server">
                  { { }}.
            <md-input-container class="md-block">
              <input ng-model="vm.login.password" name="password"
              placeholder="Password" type="password" ng-required="true">
              <div ng-messages="loginForm.password.$error">
                <div ng-message="required">Password is required.</div>
                <div ng-message="server">
                  { { vm.serverErrors.login.join(', ') }}.
            <md-button type="submit" class="md-raised md-primary">Sign in</md-button>
            <md-button ng-click="vm.tabsData.selectedIndex=2">
              Register with email

      <md-tab label="Registration">
          <form name="registrationForm"
            ng-submit="vm.handleRegBtnClick(vm.registration, registrationForm)" role="form">
            <md-input-container class="md-block">
              <input ng-model="" type="email"
              placeholder="Email (required)" ng-required="true" name="email"
              <div ng-messages="$error">
                <div ng-message="required">Email is required.</div>
                <div  ng-message="email">Must look like email.</div>
                <div ng-message="server">
                  { {', ') }}.
            <md-input-container md-no-float class="md-block">
              <input ng-model="vm.registration.password" type="password"
              placeholder="password" ng-required="true" ng-minlength=6
              ng-maxlength=20 name="password">
              <div ng-messages="registrationForm.password.$error">
                <div ng-message="required">Password is required.</div>
                <div ng-message="minlength,maxlength">Password should be between 6 and 20
                <div ng-message="server">
                  { { vm.serverErrors.registration.password.join(', ') }}.
            <md-input-container md-no-float class="md-block">
              <input ng-model="vm.registration.password_confirmation"
              type="password" placeholder="Password confirmation"
              name="password_confirmation" ng-required="true">
              <div ng-messages="registrationForm.password_confirmation.$error">
                <div ng-message="required">Password confirmation is required.</div>
                <div ng-message="password_match">Passwords don't match.</div>
                <div>{ { vm.serverErrors.registration.password_confirmation.join(', ') }}</div>

            <md-button type="submit" class="md-raised md-primary"

cat > src/app/login/ <<\HERE_DOC
angular.module 'client'
  .controller 'LoginController', ($mdDialog, $auth, $log, $scope) ->
    vm = this
    vm.login = {}
    vm.registration = {}
    # vm.registration =
    #   email: 'asd@asd.asd'
    #   password: 'asdasd'
    #   password_confirmation: 'asdasd'
    # vm.login =
    #   email: 'asd@asd.asd'
    #   password: 'asdasd'

    vm.tabsData = {
      selectedIndex: 0,

    vm.submitLogin = (login, loginForm) ->
      handleError = (resp) ->
        $scope.vm.serverErrors =
          login: resp.errors
        $log.debug loginController: 'submitLogin handleError', resp_errors: resp.errors
        .then (resp) ->
          $log.debug  loginController: 'submitLogin then', resp: resp
        .catch handleError

    vm.handleRegBtnClick = (registration, registrationForm) ->
      handleError = (resp) ->
        for field of
          if registrationForm[field]
        $scope.vm.serverErrors =
        $log.debug 'handleError'
        .then (resp) ->
            email: $
            password: $scope.vm.registration.password
          $log.debug 'submitRegistration then'
        .catch handleError

When you want to override default initial behavior, for example create another table that will hold identities, you need to override a lot of things #23 #453 Another approach (plan B) is to put your business logic in another model let say, participant thas has many users. This has a problem of assigning oauth account to the current email-user participant (we need to pass current email-user token to oauth session so we know that we can connect those two users to the same participant). Also adds new unnecessary model.

Easiest solution is to add uniqueness to email field and rescue with signing in. Then we just repeat whole proccess from omniauth_success from devise_token_auth gem. Similar approach is in this opensource rails angular app manshar

rails g migration add_uniq_index_to_users
sed -i db/migrate/*add_uniq_index_to_users* -e '/change/a\
    remove_index :users, :email\
    add_index :users, :email, unique: true'
rake db:migrate

sed -i config/routes.rb -e "s^\(mount_devise_token_auth_for.*$\)^\1, controllers: {\n\
    omniauth_callbacks: 'users/omniauth_callbacks',\n  }^"
mkdir app/controllers/users
cat > app/controllers/users/omniauth_callbacks_controller.rb <<\HERE_DOC
module Users
  class OmniauthCallbacksController <

    rescue_from ActiveRecord::RecordNotUnique,
                with: :user_already_registered_with_this_email

    def user_already_registered_with_this_email
      # repeat whole omniauth_success method for this user
      @resource = User.find_by_email(
      # get_resource_from_auth_hash

      if resource_class.devise_modules.include?(:confirmable)
        # don't send confirmation email!!!

      sign_in(:user, @resource, store: false, bypass: false)

      unless @resource.image.present?
        @resource.image = auth_hash['info']['image']


Also override session and password controller to use email field instead of uid field. That way oauth users can use email login and email users can use oauth login without rescue exceptions (only first time)

Some links for Ionic authentication:



For angular_devise, path could be default users/sign_in.json (no need to place it on the root). In order to send headers and cookies, you need to add $httpProvider.defaults.withCredentials = true to the

If protect_from_forgery is enabled, you need to pass XSRF-TOKEN token as cookie, and it will be returned later. We will read from that cookie (not from hidden input fields as rails does).

Sometime rails responds multiple times, and last cookie is used.

Note that Set-Cookie is only of GET request Set-cookie could be missing if you use protect_from_forgery with: :null_session. Best way is to always rise exception, so you know when xsrf happens.

Note that cookies are stored per domain. In ajax, if you request two different domains a.local and b.local, they will receive different sessions cookies (in rails for example session[:customer_id] will show different values) Chrome Developer Tools hides cookies for other domains… I do not know how to clear cookies for other domain since I can not see them in Developer Tools Resources… So it is imporant to have same AuthProvider loginPath and logoutPath (login at a.local and logout at b.local will not work).

Protect from forgery is for all requests except HEAD and GET link. link2

It is important if you Set-Cookie on before OR after action. If it is after_action than if request is not authorized (before_action :authenticate_user!) than that after_action will not be done, so NO XSRF-TOKEN will be set. So better is to use before_action.

request.xhr? is strange. It returns nil for angular requests. Also, when there are not cookies and login POST is sent, it passes before_action :set_csrf_cookie_for_ng, if: -> { request.xhr? } but puts request.xhr? 'XHR=true' : 'XHR=nil' shows nil.

Example application angular-devise-ng-token-auth

# app/controllers/application_controller.rb

  protect_from_forgery with: :exception

  after_action :set_csrf_cookie_for_ng # , if: -> { request.xhr? }

  def set_csrf_cookie_for_ng
    cookies['XSRF-TOKEN'] = form_authenticity_token if protect_against_forgery?

  rescue_from ActionController::InvalidAuthenticityToken do |exception|
    respond_to do |format|
      format.html { raise exception }
      format.json do
        render json: { error: 'Invalid authenticity token' }, status: :unprocessable_entity


  def verified_request?
    super || valid_authenticity_token?(session, cookies['XSRF-TOKEN'])

Here is example login controller for ionic

bower install --save angular-devise

# www/index.html
    <!-- Devise -->
    <script src="lib/AngularDevise/lib/devise-min.js"></script>

# www/js/app.js
angular.module('starter', ['ionic', 'Devise'])

# www/js/app.config.cofee
angular.module 'starter'
  .config (AuthProvider, $httpProvider, CONSTANT, AuthInterceptProvider) ->
    AuthProvider.loginPath CONSTANT.SERVER_URL + '/users/sign_in.json'
    AuthProvider.logoutPath CONSTANT.SERVER_URL + '/users/sign_out.json'
    $httpProvider.defaults.withCredentials = true
    $httpProvider.interceptors.unshift 'csrfInterceptor'
    AuthInterceptProvider.interceptAuth true
    console.log 'config'

# www/js/interceptors/
window.csrfRepeated = false
angular.module 'starter'
  .factory 'csrfInterceptor', ($q, $injector) ->
    responseError: (rejection) ->
      if rejection.status == 422 && == 'Invalid authenticity token'
        console.log "CSRF error so try again and only one time"
        if ! window.csrfRepeated
          window.csrfRepeated = true
          deferred = $q.defer()

          successCallback = (resp) ->
          errorCallback = (resp) ->

          $http = $http || $injector.get('$http')
          $http(rejection.config).then(successCallback, errorCallback)
          return deferred.promise


# www/js/login/login.jade
ion-view(view-title="Sign In")
          span.input-label Username
          input(type="text" placeholder="Your username"
          span.input-label Password
          input(type="password" placeholder="Your password"
          button.button.button-block.button-positive Sign In

# www/js/login/
angular.module 'starter'
  .controller 'LoginController', (Auth, $state) ->
    vm = this
    vm.login =
      email: 'asd@asd.asd'
      password: 'asdfasdf'

    init = ->
        (user) ->
          console.log user
          $state.go 'tab.dashboard'
        (error) ->
          console.log error

    vm.handleSubmitLogin = (login) ->
        (user) ->
          console.log user
          $state.go 'tab.dashboard'
        (error) ->
          console.log error


# www/js/
angular.module 'starter'
  .config ($stateProvider, $urlRouterProvider) ->
      .state 'login',
        url: '/login'
        templateUrl: 'jade_build/js/login/login.html'
        controller: 'LoginController'
        controllerAs: 'vm'
      .state 'tab',
        url: '/tab'
        abstract: true
        templateUrl: 'jade_build/js/tabs/tabs.html'
        controller: 'TabsController'
      .state 'tab.dashboard',
        url: '/dashboard'
            templateUrl: 'jade_build/js/dashboard/dashboard.html'
            controller: 'DashboardController'
            controllerAs: 'vm'
      .state 'tab.account',
        url: '/account'
            templateUrl: 'jade_build/js/account/account.html'
            controller: 'AccountController'
            controllerAs: 'vm'

    $urlRouterProvider.otherwise '/login'

# www/js/
angular.module 'starter'
  .run ($rootScope, NotifyService, $state) ->
    $rootScope.$on 'devise:login', (event, currentUser) ->
      console.log 'devise:login'

    $rootScope.$on 'devise:new-session', (event, currentUser) ->
      console.log 'devise:new-session user logs in with Auth.login'

    $rootScope.$on 'devise:unauthorized', (event, xhr, deferred) ->
      NotifyService.toast "Unauthorized. Please log in again"
      $state.go 'login'


# www/js/tabs/
angular.module 'starter'
  .controller 'TabsController', (Auth, $state, $rootScope) ->
    init = ->
        (user) ->
          $rootScope.user = user
        (error) ->
          console.log error
          $state.go 'login'
    console.log 'TabsController'

Resend confirmation email on login

When user has not confirmed email and config.allow_unconfirmed_access_for = 3.days has expired, and when he login there will be an error:

Failed to login because A confirmation email was sent to your account at asd@asd.asd. You must follow the instructions in the email before your account can be activated

We can resend confirmation in this failed login attempt (don’t resend email in case allow_unconfirmed_access_for is not set or zero and you automatically log in user after registration). You can also check if confirmation is send more than ago and update @resource.confirmation_sent_at = - Devise.allow_unconfirmed_access_for (this substraction is needed because someone can try to login right after confirmation email is send).

# config/routes.rb
#   mount_devise_token_auth_for( 'User', controllers: { sessions: 'users/sessions' })
# config/initializers/devise_token_auth.rb define url or use from secrets
#  config.default_confirm_success_url = 'http://localhost:9000'
#  config.default_confirm_success_url = \
#    "http://#{Rails.application.secrets.default_url['host']}" \
#    ":#{Rails.application.secrets.default_url['port']}"

cat > app/controllers/users/sessions_controller.rb <<\HERE_DOC
module Users
  class SessionsController < DeviseTokenAuth::SessionsController
    def render_create_error_not_confirmed
      @redirect_url = DeviseTokenAuth.default_confirm_success_url
        redirect_url: @redirect_url,

Problem with ng-token is that is returns user object with snake user.first_name instead of camelCase user.firstName as it is for Rails Resources

Sign in after user click on confirmation link

# app/controllers/confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
  def show
    sign_in resource if resource.confirmed?
# config/routes.rb
  devise_for :users, controllers: {
    confirmations: :confirmations

Admin sign in as another user

If admin wants to become some other user login_as he can use sign_in(:user, @user, { :bypass => true }).

# app/views/layouts/application.html.erb
<% if Rails.env.development? %>
    <% User.first(10).each do |user| %>
      <%= link_to, sign_in_as_path(user_id: %>
    <% end %>
<% end %>

# config/routes.rb
  get 'sign_in_as', to: 'application#sign_in_as'

# app/controllers/application_controller.rb
  before_action :authenticate_user!, except: [:sign_in_as]
  def sign_in_as
    return unless Rails.env.development?
    user = User.find params[:user_id]
    request.env['devise.skip_trackable'] = true
    sign_in :user, user, byepass: true
    redirect_to root_path

With ng-token is similar

def sign_in_as
  @resource = @user
  # copy original code
  # create client id
  @client_id = SecureRandom.urlsafe_base64(nil, false)
  @token     = SecureRandom.urlsafe_base64(nil, false)

  @resource.tokens[@client_id] = {
    token: BCrypt::Password.create(@token),
    expiry: ( + DeviseTokenAuth.token_lifespan).to_i

  sign_in(:user, @resource, store: false, bypass: false)
  render json: @user

just note that user need to be user.active_for_authentication?. In seed you need to have user.skip_confirmation! since you will not be able to log in as.


Devise will show only id, email, create_at and updated_at. To add more fields override user as_json

# app/models/user.rb
  def as_json(options={})
    r = super(options)
    r["domains"] = ''

Redirection after sign in

Stored location for resource is usually kept in session[:user_return_to]. Yo enable it you have to add before action,-sign-out,-sign-up,-update

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :_store_user_location!, if: :_storable_location?
  # The callback which stores the current location must be added before you authenticate the user
  # as `authenticate_user!` (or whatever your resource is) will halt the filter chain and redirect
  # before the location can be stored.

  # Its important that the location is NOT stored if:
  # - The request method is not GET (non idempotent)
  # - The request is handled by a Devise controller such as Devise::SessionsController as that could cause an 
  #    infinite redirect loop.
  # - The request is an Ajax request as this can lead to very unexpected behaviour.
  def _storable_location?
    request.get? && is_navigational_format? && !devise_controller? && !request.xhr?

  def _store_user_location!
    # :user is the scope we are authenticating
    # this path can be fetched with path = stored_location_for(resource)
    store_location_for(:user, request.path)

  # override in ApplicationController since if you override in
  # SessionsController and it wont be used in RegistrationController (for example
  # user already signed in and needs to be redirected)
  def after_sign_in_path_for(resource)
    # we need save stored location in variable since it is not indenpotent
    # if we call twice stored_location_for(:user) than second will be nil
    # so do not use stored_location_for in views or anywhere else... you can use
    # session[:user_return_to] if you really need
    redirect_url = stored_location_for(resource)
    return redirect_url if redirect_url.present?
    # calculate default paths for user

# app/controllers/users/registrations_controller.rb
note that inside Devise controllers and also in application controller you can
use `stored_location_for :user`

If you need to store landing page for future analitics or you need to redirect users after specific actions, you can implement your own

# app/controllers/application_controller.rb
  before_action :set_back_variable_into_session_if_exists
  before_action :save_landing_page, unless: :current_user

  def set_back_variable_into_session_if_exists
    session[:_back] = params[:_back] if params[:_back].present?
    # facebook login overwrite params and querystring so we need to use request.env to get _back param
    session[:_back] = request.env['omniauth.params'].try(:[],'_back') if request.env['omniauth.params'].try(:[],'_back').present?
  def save_landing_page
    if ! session[:landing_page].present?
      session[:landing_page] = request.fullpath

  # this overrides devise default
  def after_sign_in_path_for(resource)

# app/helpets/application_helper.rb
  def after_log_in_path_for(user)
    track_sign_in(user) if user.customer?
    origin = request.env['omniauth.origin'] unless [new_user_session_url, signup_jobseeker_url, signup_employer_url, user_omniauth_authorize_url(:linkedin), user_omniauth_authorize_url(:facebook)].include? "#{request.env['omniauth.origin']}".split('?').first
    session[:_back] || origin || controller.stored_location_for(user) || if user.user?
    elsif user.customer?
    elsif user.admin?
    elsif user.superadmin?


You can use test login helpers In spec/rails_helper.rb uncomment line that require all spec/support/**/*.rb files and create that login helper.

And you can use in your acceptance tests login_as user where user = User.create email: 'asd@.asd.asd', password: 'asdasd'

To suppress error in tests ERROR -- omniauth: (facebook) Authentication failure! invalid_credentials encountered. you can use this helper

  def silence_omniauth
    previous_logger = OmniAuth.config.logger
    OmniAuth.config.logger ="/dev/null")
    OmniAuth.config.logger = previous_logger

  silence_omniauth { click_link 'Sign in using Facebook' }

HTTP Basic auth

You can use basic http auth but you should config.force_ssl = true for production env.

# app/controllers/admin/admin_controller.rb
module Admin
  class AdminController < ApplicationController
      name: Rails.application.secrets.admin_username,
      password: Rails.application.secrets.admin_password

# app/controllers/admin/users_controller.rb
module Admin
  class UsersController < Admin::AdminController

If you want different password for different request type

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  skip_before_action :verify_authenticity_token, if: :json_request?

  before_action :authenticate

  def authenticate
    return true if Rails.env.test?
    authenticate_or_request_with_http_basic do |username, password|
      return true if request.format.html? && username == Rails.application.secrets.admin_username && password == Rails.application.secrets.admin_password
      return true if request.format.json? && username == Rails.application.secrets.admin_json_username && password == Rails.application.secrets.admin_json_password


Devise send email in background

Read this gem,-Sidekiq-and-Delayed::Job) or you can add manually (just put lines in User model, so it overrides devise’s)

# app/models/user.rb
  devise :database_authenticatable, :device_async

# config/initializers/devise.rb
# Register devise-async model in Devise
Devise.add_module(:devise_async, model: 'devise_async')

# app/models/concerns/devise_async.rb
module DeviseAsync
  extend ActiveSupport::Concern

  included do
    # This method overwrites devise's own `send_devise_notification`
    # message = devise_mailer.send(notification, self, *args)
    # message.deliver_now
    # also need to fetch user in MyDeviseMailer
    # protected is required, or ActionView::Template::Error: undefined method `main_app'
    def send_devise_notification(notification, *args)
      message = devise_mailer.send(notification, self, *args)

or oneliner instead of all above

# app/models/user.rb
  def send_devise_notification(notification, *args)
    devise_mailer.send(notification, self, *args).deliver_later

Look below if you have problem with serilization

Custom devise mailer I override becaus I wante dto use delive_later but have a problem with serilization (Neo4j objects)

ActiveJob::SerializationError: Unsupported argument type: User

so I send id instead of self

# app/models/user.rb
  # This method overwrites devise's own `send_devise_notification`
  # message = devise_mailer.send(notification, self, *args)
  # message.deliver_now
  # also need to fetch user in MyDeviseMailer
  # protected is required, or ActionView::Template::Error: undefined method `main_app'


  def send_devise_notification(notification, *args)
    message = devise_mailer.send(notification, id, *args)

So My devise mailer just call super with same arguments

# app/mailers/my_devise_mailer.rb
class MyDeviseMailer < Devise::Mailer
  helper :application # gives access to all helpers defined within `application_helper`.
  include Devise::Controllers::UrlHelpers # Optional. eg. `confirmation_url`
  default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views

  def confirmation_instructions(record_id, token, opts = {})
    record = User.find record_id
    super record, token, opts

  def reset_password_instructions(record_id, token, opts = {})
    record = User.find record_id
    super record, token, opts

  def unlock_instructions(record_id, token, opts = {})
    record = User.find record_id
    super record, token, opts

  def email_changed(record_id, opts = {})
    record = User.find record_id
    super record, opts

  def password_change(record_id, opts = {})
    record = User.find record_id
    super record, opts

Session expired

For ajax requests when session is expired we need to redirect

Detect if already signed in to social sites