3
votes

I'm building a Rails application with a Javascript framework, so Rails is serving the backend API.

For now, the app is simply implementing all the devise views and actions.

In order to do so, the Rails app accepts only JSON calls to its /api/ URLs, and requires that Devise is working with JSON calls only, so that I defined them like the following:

Rails.application.routes.draw do
  scope :api, module: :api, constraints: { format: 'json' } do
    devise_for :users, controllers: {
      confirmations: 'devise/confirmations',
      registrations: 'devise/registrations',
      sessions: 'sessions'
    }

    resources :users
  end

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
  root to: 'home#index'
  get '*path', to: 'home#index'
end

To explain what is going on here:

  • First part is defining the API URLs including the devise ones and defines the API resources (for now only :users)
  • Then it defines the root route
  • Finally forwards any calls to the Javascript router (allowing to manage page reload or external links like the email links).

All is working fine with this, excepted the account confirmation email link. The confirmation email sent has a link including /api/ (http://localhost:3001/api/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc) while it should be without (Expected URL: http://localhost:3001/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc).

How can I make devise sending the confirmation email (and all the other emails) without the /api/ part?

Update

Looking deeper the devise source code I found that the confirmation mailer view template is using the confirmation_url Rails named route, which is correct.

In my case, I need ALL the devise routes to be limited to the /api/ route, and to the JSON format, excepted few routes to be 2 times defined: a first time out of the /api/ scope in HTML format (which will be forwarded to my JavaScript app), and a second route within the /api/ scope which will be called by the JavaScript app.

Example: Expected account creation confirmation execution stack

  1. Rails receives the request to /users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc
  2. Rails forward the request to the Javascript router
  3. Javascript framework loads the corresponding page
  4. Javascript page loads a service which will call the /api/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc Devise route to confirm the account
2

2 Answers

0
votes

You should be able to simply add the two additional routes after the initial API routes. Like this:

Rails.application.routes.draw do
  scope :api, module: :api, constraints: { format: 'json' } do
    devise_for :users, controllers: {
      confirmations: 'devise/confirmations',
      registrations: 'devise/registrations',
      sessions: 'sessions'
    }

    resources :users
  end

  devise_for :users, controllers: {
    confirmations: 'devise/confirmations',
    registrations: 'devise/registrations'
  }

  root to: 'home#index'
  get '*path', to: 'home#index'
end

It is hard to say for sure without also seeing the front end code, but it is fine to have both routes.

0
votes

There are two ways you can solve your problem.

  1. Make the links in the emails be of json format (have '.json' at the end)

or

  1. Allow actions for links from emails to be available through html (without format: 'json' constraint).

This example shows how to do it for email confirmation action.

scope :api, module: :api, constraints: { format: 'json' } do
  # skip routes generation for `confirmations` controller
  devise_for :users, skip: [:confirmations]

  # add allowed `confirmations` actions manually (all except for `show`)
  as :user do
    get 'confirmation/new', to: 'devise/confirmations#new', as: :new_user_confirmation
    post 'confirmation', to: 'devise/confirmations#create'
  end
end

# add `confirmations#show` action outside of `/api` scope to be available from email link by `html`
as :user do
  get 'confirmation', to: 'devise/confirmations#show', as: :user_confirmation
end

If you don't want to use Devise's HTML views and actions, you can customize Devise controllers or write your own. This example shows how to customize Devise confirmations controller:

# app/controllers/confirmations_controller.rb
class ConfirmationsController < Devise::ConfirmationsController
  def show
    # do whatever you want
  end
end 

# config/routes.rb
scope :api, module: :api, constraints: { format: 'json' } do
  # tell Devise to use your custom confirmations controller
  devise_for :users, controllers: {confirmations: "confirmations"}
end

Updated

Also, if you want your email links to have nothing to do with Devise API, you can customize the Devise mailer views to change the email texts and put there the links you need.

The rails generate devise:views command will generate standard Devise views including mailer templates that you can customize.