Skip to content

Devise Server Setup

Tim Scott edited this page Aug 26, 2017 · 3 revisions

Eventually we want to create a gem to avoid repeating a lot of boilerplate. For now, you have to set it up yourself.

JWT Authentication

First, we'll use the devise-jwt gem.

# Gemfile

 gem 'devise-jwt'

Add the devise-jwt bits in the user model (or whatever your authenticated resource is). The gem supports various revocations strategies.

# /app/models/user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :recoverable, :trackable, :validatable, :confirmable,
    :jwt_authenticatable, jwt_revocation_strategy: Devise::JWT::RevocationStrategies::Null

  def jwt_payload
    {
      user_id: id,
      email: email,
      firstName: first_name,
      lastName: last_name
    }
  end

end

Devise Routes

Next, we might need to set a custom path in our routes to match the apiResourceName if it's not the same as your user model name.

# routes.rb

devise_for :users, path: :auth

Devise Failure App

Next, we need to change auth failure behavior:

# /app/controllers/custom_auth_failure.rb

class CustomAuthFailure < Devise::FailureApp

  def respond
    self.status = :unauthorized
    self.content_type = :json
    self.response_body = {errors: ['Invalid login credentials']}.to_json
  end

end

Devise Emails

Next, we need to change the URLs in our emails to be the client side routes. (NOTE: Replace "user" and "users" if you set a different clientResourceName.)

# /app/helpers/users_mailer_helper.rb

module UsersMailerHelper

  url_defaults = Rails.configuration.action_mailer.default_url_options
  protocol = url_defaults[:protocol] || 'http'
  port = ":#{url_defaults[:port]}" if url_defaults[:port].present?

  Devise::URL_HELPERS.each do |module_name, actions|
    actions.each do |action|
      method = ['user', action, module_name, 'url'].compact.join '_'
      path = ['users', module_name, action].compact.join '/'

      define_method method do |params = nil|
        query = "?#{params.map {|k,v| "#{k}=#{v}"}.join('&')}" if params.present?
        "#{protocol}://#{url_defaults[:host]}#{port}/#{path}#{query}"
      end
    end
  end

end
# /app/mailers/users_mailer.rb

class UsersMailer < Devise::Mailer
  helper :users_mailer # gives access to all helpers defined within `mailer_helper`.
  default template_path: 'devise/mailer' # to make sure that your mailer uses the devise views
end

Next, you need to edit your mailers to use the client side route helpers. For example:

<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', user_confirmation_url(confirmation_token: @token) %></p>

Devise Config

# config/initializers/devise.rb

config.warden do |manager|
  manager.failure_app = CustomAuthFailure
end

config.mailer = 'UsersMailer'

# Needs to not include :json or wildcards that match json.
config.navigational_formats = [:html]
# config/application.rb

config.to_prepare do
  DeviseController.respond_to :json
end

Edit User

If you want to implement editing user profile with react-devise, you need to create a custome RegistrationController.

class RegistrationsController < Devise::RegistrationsController
  def edit
    respond_with resource
  end

protected

  def update_resource(resource, params)
    if params[:password].present?
      resource.update_with_password params
    else
      resource.update_without_password params
    end
  end
end

The overidden #update_resource method is only needed if you want to omit password fields. This is an existing limitation with Devise.

Modify the devise routes to use the custom controller.

# config/routes.rb

devise_for :users, path: :auth, controllers: {registrations: :registrations}

Getting an Updated The Auth Token

If you update any user attributes that are included in your auth token (based on User#jwt_payload), then the current_user in your client will go stale. To fix that add the following to tell devise-jwt to send a fresh auth token.

# config/initializers/devise.rb

config.jwt do |jwt|
  jwt.dispatch_requests = [
    ['PATCH', %r{^/auth$}]
  ]
end

# where 'auth' is the path for your devise routes.

DISCLAIMER: You might want to consider NOT putting user related info into the token. Also, the code above does not revoke the old token, which also might NOT be a best practice. Please read this issue for more detail.

Clone this wiki locally