I am using Devise auth token gem for authenticating some parts of my rails app. But when I try to create a new user with the registration path, it is giving me the following error{"errors":["Authorized users only."]}.
Here is the rspec code that I am using for the test,
it 'creates a user using email/password combo' do
post api_user_registration_path, { email: 'xxx', password: 'yyy',password_confirmation: 'yyy'}
puts last_response.body
expect(last_response.body).not_to have_content('error')
end
Additional info: the model name is 'User' and the routes looks like,
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
end
end
I understand that the devise is expecting the user to be authenticated before accessing this path, but this being the user registration, it needs to be outside the authentication. Can you suggest a solution for this? Is there any configuration that I am missing here?
Try with:
namespace :api do
namespace :v1 do
mount_devise_token_auth_for 'User', at: '/auth'
end
end
This will create the following routes:
new_api_v1_user_session GET /api/v1/auth/sign_in(.:format) devise_token_auth/sessions#new
api_v1_user_session POST /api/v1/auth/sign_in(.:format) devise_token_auth/sessions#create
destroy_api_v1_user_session DELETE /api/v1/auth/sign_out(.:format) devise_token_auth/sessions#destroy
api_v1_user_password POST /api/v1/auth/password(.:format) devise_token_auth/passwords#create
new_api_v1_user_password GET /api/v1/auth/password/new(.:format) devise_token_auth/passwords#new
edit_api_v1_user_password GET /api/v1/auth/password/edit(.:format) devise_token_auth/passwords#edit
PATCH /api/v1/auth/password(.:format) devise_token_auth/passwords#update
PUT /api/v1/auth/password(.:format) devise_token_auth/passwords#update
cancel_api_v1_user_registration GET /api/v1/auth/cancel(.:format) devise_token_auth/registrations#cancel
api_v1_user_registration POST /api/v1/auth(.:format) devise_token_auth/registrations#create
new_api_v1_user_registration GET /api/v1/auth/sign_up(.:format) devise_token_auth/registrations#new
edit_api_v1_user_registration GET /api/v1/auth/edit(.:format) devise_token_auth/registrations#edit
PATCH /api/v1/auth(.:format) devise_token_auth/registrations#update
PUT /api/v1/auth(.:format) devise_token_auth/registrations#update
DELETE /api/v1/auth(.:format) devise_token_auth/registrations#destroy
api_v1_auth_validate_token GET /api/v1/auth/validate_token(.:format) devise_token_auth/token_validations#validate_token
Also create an controller in app/controllers/api/v1/api_base_controller.rb
class Api::V1::BaseApiController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
end
Also add to your file app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?
Related
Currently I am working on sample web application. In this application, user can sign up via JSON API as well as from browser.
For JSON API authentication I have used gem devise_token_auth https://github.com/lynndylanhurley/devise_token_auth and it is working fine. I am able to sign up via API.
But now I have to provide sign up functionality from the web browser.
(URL: http://localhost:3000/auth/sign_up) getting following error
The action 'new' could not be found for DeviseTokenAuth::RegistrationsController
So how can I allow sign up from the web browser.
Here is my routes.rb file
Rails.application.routes.draw do
mount_devise_token_auth_for 'User', at: 'auth'
end
Generated routes
Prefix Verb URI Pattern Controller#Action
new_user_session GET /auth/sign_in(.:format) devise_token_auth/sessions#new
user_session POST /auth/sign_in(.:format) devise_token_auth/sessions#create
destroy_user_session DELETE /auth/sign_out(.:format) devise_token_auth/sessions#destroy
new_user_password GET /auth/password/new(.:format) devise_token_auth/passwords#new
edit_user_password GET /auth/password/edit(.:format) devise_token_auth/passwords#edit
user_password PATCH /auth/password(.:format) devise_token_auth/passwords#update
PUT /auth/password(.:format) devise_token_auth/passwords#update
POST /auth/password(.:format) devise_token_auth/passwords#create
cancel_user_registration GET /auth/cancel(.:format) devise_token_auth/registrations#cancel
new_user_registration GET /auth/sign_up(.:format) devise_token_auth/registrations#new
edit_user_registration GET /auth/edit(.:format) devise_token_auth/registrations#edit
user_registration PATCH /auth(.:format) devise_token_auth/registrations#update
PUT /auth(.:format) devise_token_auth/registrations#update
DELETE /auth(.:format) devise_token_auth/registrations#destroy
POST /auth(.:format) devise_token_auth/registrations#create
auth_validate_token GET /auth/validate_token(.:format) devise_token_auth/token_validations#validate_token
auth_failure GET /auth/failure(.:format) devise_token_auth/omniauth_callbacks#omniauth_failure
GET /auth/:provider/callback(.:format) devise_token_auth/omniauth_callbacks#omniauth_success
GET|POST /omniauth/:provider/callback(.:format) devise_token_auth/omniauth_callbacks#redirect_callbacks
omniauth_failure GET|POST /omniauth/failure(.:format) devise_token_auth/omniauth_callbacks#omniauth_failure
GET /auth/:provider(.:format) redirect(301)
Do I have to add separate devise here. Please let me know your thoughts.
Best way to achieve this will be to mount normal devise routes to one scope, and API devise_token_auth routes to separate scope.
Rails.application.routes.draw do
# standard devise routes at /users
devise_for :users
# token auth routes available at /api/auth/
namespace :api, defaults: { format: :json } do
scope module: :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
end
end
end
new_user_registration GET /auth/sign_up(.:format) devise_token_auth/registrations#new
which does not lead to anything, because in the GEM controller devise_token_auth/registrations_controller there is no new action.
1 - change the routing so that the http GET request is rooted to your controller#new action.
2 - create the controller#new action
3 - have the form in new.html.erb call the following action
POST /auth(.:format) devise_token_auth/registrations#create
4 - To create a user, you just need to make an HTTP POST request to the following URL
POST /auth(.:format) devise_token_auth/registrations#create
with the parameters that you need to pass to this controller action
def create
#resource = resource_class.new(sign_up_params)
#resource.provider = "email"
# honor devise configuration for case_insensitive_keys
if resource_class.case_insensitive_keys.include?(:email)
#resource.email = sign_up_params[:email].try :downcase
else
#resource.email = sign_up_params[:email]
end
# give redirect value from params priority
#redirect_url = params[:confirm_success_url]
# fall back to default value if provided
#redirect_url ||= DeviseTokenAuth.default_confirm_success_url
# success redirect url is required
if resource_class.devise_modules.include?(:confirmable) && !#redirect_url
return render_create_error_missing_confirm_success_url
end
# if whitelist is set, validate redirect_url against whitelist
if DeviseTokenAuth.redirect_whitelist
unless DeviseTokenAuth::Url.whitelisted?(#redirect_url)
return render_create_error_redirect_url_not_allowed
end
end
begin
# override email confirmation, must be sent manually from ctrl
resource_class.set_callback("create", :after, :send_on_create_confirmation_instructions)
resource_class.skip_callback("create", :after, :send_on_create_confirmation_instructions)
if #resource.save
yield #resource if block_given?
unless #resource.confirmed?
# user will require email authentication
#resource.send_confirmation_instructions({
client_config: params[:config_name],
redirect_url: #redirect_url
})
else
# email auth has been bypassed, authenticate user
#client_id = SecureRandom.urlsafe_base64(nil, false)
#token = SecureRandom.urlsafe_base64(nil, false)
#resource.tokens[#client_id] = {
token: BCrypt::Password.create(#token),
expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
}
#resource.save!
update_auth_header
end
render_create_success
else
clean_up_passwords #resource
render_create_error
end
rescue ActiveRecord::RecordNotUnique
clean_up_passwords #resource
render_create_error_email_already_exists
end
end
This way the user will be created, but it is not a good approach. You are using an api when you should just be using devise
I added user profile to Devise from some tutorials I found online and linking to profile works great. However now I'm having problem with user signup.
I assume, since I have created users_controller.rb for user profile, it is looking into users_controller.rb now for all actions. So, regarding the sign up, I added create def, then it asked def new, then def update and so on... Things got really complicated and I got different type of errors!
MY QUESTION:
Is it possible to redirect all actions to the default Devise signup, login, update, ... while I keep user_controllers.rb only for user profile?
Thank you!
route
Rails.application.routes.draw do
resources :users
devise_for :users
devise_scope :user do
get 'register', to: 'devise/registrations#new', as: :register
get 'login', to: 'devise/sessions#new', as: :login
get 'logout', to: 'devise/sessions#destroy', as: :logout
end
root 'posts#index'
end
Controller
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
Routing and Controllers in Devise
Your config/routes.rb determines the actions triggered with each GET/POST/PUT/PATCH request made to urls
You can read more about this at the following link
Routing/Controller in Devise
When you configure Devise you set the routes by using the devise_for method in your routes.rb
# config/routes.rb
Rails.application.routes.draw do
devise_for :users
end
to read more about the method devise_for read the documentation
devise_for will generate the following routes:
# Session routes for Authenticatable (default)
new_user_session GET /users/sign_in {controller:"devise/sessions", action:"new"}
user_session POST /users/sign_in {controller:"devise/sessions", action:"create"}
destroy_user_session DELETE /users/sign_out {controller:"devise/sessions", action:"destroy"}
# Password routes for Recoverable, if User model has :recoverable configured
new_user_password GET /users/password/new(.:format) {controller:"devise/passwords", action:"new"}
edit_user_password GET /users/password/edit(.:format) {controller:"devise/passwords", action:"edit"}
user_password PUT /users/password(.:format) {controller:"devise/passwords", action:"update"}
POST /users/password(.:format) {controller:"devise/passwords", action:"create"}
# Confirmation routes for Confirmable, if User model has :confirmable configured
new_user_confirmation GET /users/confirmation/new(.:format) {controller:"devise/confirmations", action:"new"}
user_confirmation GET /users/confirmation(.:format) {controller:"devise/confirmations", action:"show"}
POST /users/confirmation(.:format) {controller:"devise/confirmations", action:"create"}
If the client/browser performs a GET request to the server via the url /users/sign_in, the server will execute the action new from the controller in the folder app/controllers/devise/sessions
That is where my views are generated and even if I do not have the devise controller, this is how it is mapped.
You can override this behavior, as explained in this guide, bu using the following syntax:
devise_for :users, controllers: { sessions: 'users/sessions' }
This means that for sessions you are going to use the controller located in the folder app/controllers/users/sessions and not devise/sessions.
You can test this, generate your routes and see how you will have the routes for every action.
The Best Practice
The best practice as suggested from Devise is just generating the devise controller, which will have actions that are already wired with the Devise routing. Each action from the controller will call with super the Devise controller actions, if you want to enhance or change that logic you can do it by reading the Devise API
To that controller you can add any action you want then configure appropriately your routing
For routing and controller info from ruby on rails guide
http://guides.rubyonrails.org/action_controller_overview.html
http://guides.rubyonrails.org/routing.html
I'm new to rails, and I'm trying to build API following Code School tutorial.
I get this error while trying to post to '/users' path.
routes file code :
Rails.application.routes.draw do
namespace :api,constraints: {subdomain: 'api'}, path: '/' do
resources :users, except: :delete
end
end
and the test code is :
require 'test_helper'
class CreatingUsersTest < ActionDispatch::IntegrationTest
test 'create users' do
post api_users_path,
{user: {name: 'test', email:'test#test.com'}}.to_json,
{'Accept' => Mime::JSON, 'Content-Type': Mime::JSON.to_s}
assert_equal response.status, 201
assert_equal response.content_type, Mime::JSON
user = json(response.body)
assert_equal api_user_url(user[:id]), response.location
end
end
And when I use rake routes :
api_users GET /users(.:format) api/users#index {:subdomain=>"api"}
POST /users(.:format) api/users#create {:subdomain=>"api"}
....
In the route you constraint the subdomain to be api
namespace :api,constraints: {subdomain: 'api'}, path: '/' do
but then in the test you call api_user_path
post api_users_path
that uses a generic hostname (test.host), and not the api hostname. The solution is to pass a specific host to the helper that satisfies the requirement.
post api_users_path(host: "api.test.host")
I'm requesting for any user from a different rails application. I'm using grape gem. The below code is the requested user's information sending controller action.
but unfortunately it is showing error:
NoMethodError (undefined method `profile_url' for #<Grape::Endpoint:0xdadef8c>):
What is the problem? My rake routes showing the url does exists.
module V1
class User < Grape::API
version 'v1', using: :path
format :json
prefix :api
resource :user do
desc "find any user"
params do
requires :email, type: String
requires :password, type: String
end
get :create do
user = User.find_by_email(params[:email])
if user.present?
if user.valid_password?(params[:password])
{user: user, profile_url: profile_url(user.profile)}
else
{:notice => "Invalid password for email: #{params[:email]}"}
end
else
{:notice => "No user found with email: #{params[:email]}"}
end
end
end
end
end
rake routes | grep profile:
profiles GET /profiles(.:format) profiles#index
POST /profiles(.:format) profiles#create
new_profile GET /profiles/new(.:format) profiles#new
edit_profile GET /profiles/:id/edit(.:format) profiles#edit
profile GET /profiles/:id(.:format) profiles#show
PATCH /profiles/:id(.:format) profiles#update
PUT /profiles/:id(.:format) profiles#update
DELETE /profiles/:id(.:format) profiles#destroy
Make sure you mounted the resource properly.
You may need something like this:
mount API::v1::User
See more about mounting here
Also, take a look at this post. Might be helpful to see how you can mount your resource while using Grape gem.
You can do it like this:
Rails.application.routes.url_helpers.profile_url(user.profile)
I'm writing an API and am trying to login with the API and am getting uninitialized constant SessionsController error followed by the tip to "Try running rake routes for more information on available routes." when I try to go to the URL
http://localhost:3000/api/v1/login as a POST
My routes show that the route and action exist as shown:
api_v1_users GET /api/v1/users(.:format) api/v1/users#index {:format=>"json"}
POST /api/v1/users(.:format) api/v1/users#create {:format=>"json"}
new_api_v1_user GET /api/v1/users/new(.:format) api/v1/users#new {:format=>"json"}
edit_api_v1_user GET /api/v1/users/:id/edit(.:format) api/v1/users#edit {:format=>"json"}
api_v1_user GET /api/v1/users/:id(.:format) api/v1/users#show {:format=>"json"}
PUT /api/v1/users/:id(.:format) api/v1/users#update {:format=>"json"}
DELETE /api/v1/users/:id(.:format) api/v1/users#destroy {:format=>"json"}
new_api_v1_user_session GET /api/v1/login(.:format) sessions#new {:format=>"json"}
api_v1_user_session POST /api/v1/login(.:format) sessions#create {:format=>"json"}
destroy_api_v1_user_session DELETE /api/v1/logout(.:format) sessions#destroy {:format=>"json"}
api_v1_user_omniauth_authorize GET|POST /auth/:provider(.:format) authentications#passthru {:provider=>/twitter|facebook/, :format=>"json"}
api_v1_user_omniauth_callback GET|POST /auth/:action/callback(.:format) authentications#(?-mix:twitter|facebook) {:format=>"json"}
api_v1_user_password POST /api/v1/password(.:format) api/v1/passwords#create {:format=>"json"}
new_api_v1_user_password GET /api/v1/password/new(.:format) api/v1/passwords#new {:format=>"json"}
edit_api_v1_user_password GET /api/v1/password/edit(.:format) api/v1/passwords#edit {:format=>"json"}
PUT /api/v1/password(.:format) api/v1/passwords#update {:format=>"json"}
cancel_api_v1_user_registration GET /api/v1/cancel(.:format) registrations#cancel {:format=>"json"}
api_v1_user_registration POST /api/v1(.:format) registrations#create {:format=>"json"}
new_api_v1_user_registration GET /api/v1/sign_up(.:format) registrations#new {:format=>"json"}
edit_api_v1_user_registration GET /api/v1/edit(.:format) registrations#edit {:format=>"json"}
PUT /api/v1(.:format) registrations#update {:format=>"json"}
DELETE /api/v1(.:format) registrations#destroy {:format=>"json"}
When I try to access the account using the authentication_token url parameter, it works fine.
http://localhost:3000/api/v1/users/39?user_token=DxwspVzYSpsiLosd9xcE
Using that I can make PUT and GET requests no problem.
My routes look like this:
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, :path => '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I assume it's a problem in my SessionsController, but can't figure out why the PUT request would work, but the POST wouldn't. Here is the SessionsController:
module Api
module V1
class SessionsController < Devise::SessionsController
before_filter :authenticate_user!, except: [:create, :destroy]
before_filter :ensure_params_exist
skip_before_filter :verify_authenticity_token
def create
resource = User.find_for_database_authentication(email: params[:user_login][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user_login][:password])
sign_in("user", resource)
resource.ensure_authentication_token!
render 'api/v1/sessions/new.json.jbuilder', status: 201
return
end
invalid_login_attempt
end
def destroy
current_user.reset_authentication_token
render json: {success: true}
end
protected
def ensure_params_exist
return unless params[:user_login].blank?
render json: {success: false, message: "missing user_login parameter"}, status: 422
end
def invalid_login_attempt
render 'api/v1/sessions/invalid.json.jbuilder', status: 401
end
end
end
end
So in my routes file I had
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, path: '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I removed sessions: "sessions" and everything works great. I'm not going to mark this as the correct answer because I don't know why this fixed the problem, or more specifically, why adding sessions: "sessions" was causing the error in the first place. Hopefully someone can explain this for future readers.
EDIT:
I realized much later that the problem was that I was referencing the sessions_controller, when I wanted to reference the api/v1/sessoins_controller There is no regular sessions_controller I just use the Devise one, but there is a sessions_controller in the API/V1 namespace. So that is why the problem was happening.
For future reference for anyone else who comes across this problem, the error is telling you the controller you're looking for doesn't exist as it should, so to troubleshoot, make sure you are looking for the controller in the correct place, and with the correct name.
In a similar case for an API app with rails 6 and devise 4.8.1 I used scope and devise_scopeto configure routes according to devises doc. devise_for was skip-ed.
In my case:
scope :api, defaults: { format: :json } do
scope :v1 do
devise_for :users, skip: :all
devise_scope :user do
post '/sign_in', to: 'devise/sessions#create'
delete '/sign_out', to: 'devise/sessions#destroy'
post '/sign_up', to: 'devise/registrations#create'
put '/account_update', to: 'devise/registrations#update'
delete '/account_delete', to: 'devise/registrations#destroy'
end
end
end