I included devise_token_auth to login from a webapp hosted elsewhere. But I'd like also to be able to sign in directly into my rails app.
My routes.rb looks like this:
#...
devise_for :users
namespace :api, defaults: {format: :json} do
mount_devise_token_auth_for 'User', at: 'auth'
#...
To reset the password the webapp sends a POST to /api/auth/password. With the configuration above, the link in the email to reset the password uses the wrong controller (the one on users/password). The redirect_url doesn't get applied and the user sees the login form of the rails app, not the webapp:
http://localhost:3000/users/password/edit?redirect_url=http://localhost:8080/reset_password&reset_password_token=...
If I comment the line devise_for :users the email link is correct, using api/auth/password/edit:
http://localhost:3000/api/auth/password/edit?redirect_url=http://localhost:8080/reset_password&reset_password_token=...
But of course, by commenting devise_for :users I can't login using just the rails app (on http://localhost:3000/users/sign_in).
Devise, on its mailer template to reset the password (reset_password_instructions.html.erb) calls onto the model to get the url:
<p><%= link_to 'Change my password', edit_password_url(#resource, reset_password_token: #token) %></p>
Although the controller that initially handled the client intent to reset the password was DeviseTokenAuth::PasswordsController, the function edit_password_url on User resolves to a path to be handled by Devise::PasswordsController (users/password/edit vs api/auth/password/edit).
The solution in this case was to use a different model:
routes.rb
#...
devise_for :users
namespace :api, defaults: {format: :json} do
mount_devise_token_auth_for 'Api::User', at: 'auth'
#...
api/user.rb
class Api::User < ActiveRecord::Base
devise :database_authenticatable, :recoverable,
:rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
#...
end
Related
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
Rails receives the request to /users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc
Rails forward the request to the Javascript router
Javascript framework loads the corresponding page
Javascript page loads a service which will call the /api/users/confirmation?confirmation_token=XLDVqqMZwXg6dszyV_nc Devise route to confirm the account
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.
There are two ways you can solve your problem.
Make the links in the emails be of json format (have '.json' at the end)
or
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.
I have a Rails 4.1 Application running with Devise for authentication.
For access via mobile apps i would like to implement token auth with the recommended devise_token_auth gem. I do not use Omniauth
The functionality of the existing app should not be altered.
What i did:
Installed devise_token_auth via gemfile.
Used the generator: rails g devise_token_auth:install User auth
Changed the migration to add the required fields.
Migration failed due missing of Omniauth. So i also installed it.
Changed routes.rb
devise_for :users, :skip => [:sessions, :registrations, :omniauth_callbacks]
as :user do
get 'register' => 'users/registrations#new', :as => :new_user_registration
post 'register' => 'users/registrations#create', :as => :user_registration
get 'sign_in' => 'devise/sessions#new', :as => :new_user_session
post 'sign_in' => 'devise/sessions#create', :as => :user_session
delete '/' => 'users/sessions#destroy', :as => :destroy_user_session
end
added:
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth', skip: [:omniauth_callbacks]
end
end
In User Model i have:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:confirmable, :timeoutable, :lockable
include DeviseTokenAuth::Concerns::User
Now when i try to sign_up a new user it gives me the validation error:
Uid can't be blank
Does someone had the same problem and resolved it?
What i find strange is that it needs to have Omniauth installed.
Update:
I overwrite the Devise registration controller create action:
build_resource(sign_up_params)
resource.uid = resource.email
resource.provider = ''
Now when i sign_in i get:
{"errors":["Authorized users only."]}
in Browser.
Adding the following to app/models/user.rb:
before_validation do
self.uid = email if uid.blank?
end
did it for me. Also make sure the provider is set to "email" for "provider".
Well I'm currently struggling with the same thing. Trying to add devise_token_auth to Devise, and it is not working so far for me.
As far as this goes, are you talking about "sign_up" for Devise, or devise_token_auth? If it is for Devise, I supposed setting uid=email before creating the record would solve this.
This error is raised by devise_token_auth, not by devise. So essentially, devise_token_auth is trying to authenticate your normal devise routes the same way it would normally authenticate an api request. Your normal devise routes are authenticating via session, not via token, so you'll get this error:
{"errors":["Authorized users only."]}
There are a couple of things that could be happening here. First, make sure that you're only looking for token validation on the actions of your API controllers. So make sure that this line is included in your BaseAPIController, and not in your ApplicationController.
include DeviseTokenAuth::Concerns::SetUserByToken
The other possibility is that you have some namespacing issues in your routes.rb. Make sure that you have something like this. You need to have devise_for first, and the token_auth properly namespaced or it will cause validations issues on your other routes.
Rails.application.routes.draw do
devise_for :admins
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'user', at: 'auth'
end
end
end
Good luck!
I have model User. And models
CaBase < User
LaUser < User
Both models has:
devise :database_authenticatable, :recoverable, :rememberable,
:trackable, :validatable
LaUser- Users that have access to Admin area.
CaBase - Users that has access only for front-end.
When I click on forgot password on CaBase login form, it mails me with link like:
site_name/admin/password/edit?reset_password_token=gtwSWQK8HH2-6p4CHp
But I need it to
site_name/password/edit?reset_password_token=gtwSWQK8HH2-6p4CHp
Also I have actions in ApplicationController
def after_sign_in_path_for(resource)
if resource.is_a?(LaUser)
admin_root_path
else
dashboard_index_path
end
end
def after_sending_reset_password_instructions_path_for(resource)
if resource.is_a?(LaUser)
admin_root_path
else
dashboard_index_path
end
end
What can I do with it?
UPD1:
get 'dashboard/index'
ActiveAdmin.routes(self)
devise_for :la_users, ActiveAdmin::Devise.config
devise_for :ca_base, path: '', path_names: {sign_in: 'login'}
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'welcome#index'
resource :ca_administrators, only: [:new, :create]
Problem was in that I don't checked which role have current user.
Code from Devise Mailer.
if record.is_a?(LaUser)
controller = 'active_admin/devise/passwords'
else
controller = 'devise/passwords'
end
edit_password_url = url_for(
controller: controller,
action: :edit,
reset_password_token: token
)
I have customized the devise paths like this :
devise_for :users, path: '', path_names: { sign_in: 'signin', sign_out: 'signout', password: 'secret', confirmation: 'verification', unlock: 'unblock', registration: 'signup', sign_up: 'new' }
So for example it makes the user signup path like this http://localhost:3000/signup/new. Now the problem is, I don't know how to test this page. Here's the test for users_controller :
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
test "should get signup" do
get :signup_new
assert_response :success
assert_select "title", "SignUp | #{Figaro.env.app_name}"
end
end
How to visit these custom paths in my test files?
You are probably testing the wrong controller. Check your routes (rake routes) to see which controller is assigned to handle your Devise authentication routes. By default I believe it is Devise::SessionsController.
I do not think this is your intent, but it is worth mentioning that you should not test the devise gem.
Note that requests for ActionController::TestCase are simulated. If you want to test true requests then you should look into integration testing. A brief overview can be found here
UPDATE
Take a look in the Devise gem for some examples of how to log a user in here. Notice that include Devise::TestHelpers is added near the top of the test class which adds a sign_in method.
I am using Devise for authenticating a Rails application. I am now able to successfully route /users/sign_in and /users/sign_out to /sign_in and /sign_out via this code in routes.rb:
devise_for :user, :as => ''
How do I map /registration/sign_up to /sign_up?
So that sign_in, sign_out, and sign_up all have the same pattern.
Note that I am using Devise only for users. No admins.
You need to add the following block to your routes.rb file:
devise_scope :user do
get "/sign_up" => "devise/registrations#new"
end
It's explained in: http://github.com/plataformatec/devise/wiki/How-To:-Change-the-default-sign_in-and-sign_out-routes