Not Authorizing on custom devise signup route
maqsudinamdar opened this issue · comments
Problem Statement
I'm Newbie in Rails and following this tutorial for setting up JWT based authentication in API.
This rails project works fine for a web applications. However, Incase of API I'm getting empty resource
while I still have value in params
.
Expected behavior
Return Authorization token after signup
Environment
rails
(6.1.4)
devise
(4.8.0)
devise-jwt
(0.9.0)
warden
(1.2.9)
warden-jwt_auth
(0.6.0)
Controller & Route
app/controllers/api/v1/users/registrations_controller.rb
class Api::V1::Users::RegistrationsController < Devise::RegistrationsController
respond_to :json
skip_before_action :verify_authenticity_token
# POST /resource
def create
super
end
private
def respond_with(resource, _opts = {})
if resource.persisted?
render json: {
status: { code: 200, message: "Signed up sucessfully." },
data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
}
else
render json: {
status: { message: "User couldn't be created successfully. #{resource.errors.full_messages.to_sentence}" }
}, status: :unprocessable_entity
end
end
end
config/routes.rb
namespace :api do
namespace :v1 do
devise_for :users, path: '', path_names: {
sign_in: 'login',
sign_out: 'logout',
registration: 'signup'
},
controllers: {
sessions: 'api/v1/users/sessions',
registrations: 'api/v1/users/registrations'
}
end
end
Debugging information
| 66: private
| 67: def respond_with(resource, _opts = {})
| 68: byebug
| => 69: if resource.persisted?
| 70: render json: {
| 71: status: { code: 200, message: "Signed up sucessfully." },
| 72: data: UserSerializer.new(resource).serializable_hash[:data][:attributes]
| 73: }
| (byebug) resource
| #<User
id: nil,
email: "",
first_name: "",
last_name: "",
role: "member",
created_at: nil,
updated_at: nil,
jti: nil
>
| (byebug) params
| #<ActionController::Parameters
{
"email"=>"test@test.com",
"first_name"=>"John",
"last_name"=>"Wick",
"password"=>"password",
"controller"=>"api/v1/users/registrations",
"action"=>"create",
"registration"=>{
"email"=>"test@test.com",
"first_name"=>"John",
"last_name"=>"Wick",
"password"=>"password"
}
} permitted: false>
Setup and configuration
- Output of
Devise::JWT.config
Devise::JWT.config
=> #<Dry::Configurable::Config
values={
:secret=>"secret",
:expiration_time=>1800,
:dispatch_requests=>[
["POST", /^\/api\/v1\/login$/],
["POST", /^\/users\/sign_in$/],
["POST", /^\/users$/],
["POST", /^\/api\/v1\/login$/],
["POST", /^\/api\/v1\/signup$/]
],
:revocation_requests=>[
["DELETE", /^\/api\/v1\/logout$/],
["DELETE", /^\/users\/sign_out$/],
["DELETE", /^\/api\/v1\/logout$/]
],
:aud_header=>"JWT_AUD",
:request_formats=>{}
}>
- Output of
Warden::JWTAuth.config
Warden::JWTAuth.config
=> #<Dry::Configurable::Config
values={
:secret=>"secret",
:algorithm=>"HS256",
:expiration_time=>1800,
:aud_header=>"JWT_AUD",
:mappings=>{
:user=>User (call 'User.connection' to establish a connection),
:api_v1_user=>User (call 'User.connection' to establish a connection)},
:dispatch_requests=>[
["POST", /^\/api\/v1\/login$/],
["POST", /^\/users\/sign_in$/],
["POST", /^\/users$/],
["POST", /^\/api\/v1\/login$/],
["POST", /^\/api\/v1\/signup$/]
],
:revocation_requests=>[
["DELETE", /^\/api\/v1\/logout$/],
["DELETE", /^\/users\/sign_out$/],
["DELETE", /^\/api\/v1\/logout$/]
],
:revocation_strategies=>{
:user=>User (call 'User.connection' to establish a connection),
:api_v1_user=>User (call 'User.connection' to establish a connection)
}
}>
- Output of
Devise.mappings
Devise.mappings => {
:user=>#<Devise::Mapping:0x000055a4187118f0
@scoped_path="users",
@singular=:user,
@class_name="User",
@klass=#<Devise::Getter:0x000055a418711558 @name="User">,
@path="users",
@path_prefix=nil,
@sign_out_via=:delete,
@format=nil,
@router_name=nil,
@failure_app=Devise::FailureApp,
@controllers={
:registrations=>"registrations",
:sessions=>"devise/sessions",
:passwords=>"devise/passwords"
},
@path_names={
:registration=>"",
:new=>"new",
:edit=>"edit",
:sign_in=>"sign_in",
:sign_out=>"sign_out",
:password=>"password",
:sign_up=>"sign_up",
:cancel=>"cancel"
},
@modules=[
:database_authenticatable,
:rememberable,
:recoverable,
:registerable,
:validatable,
:trackable,
:jwt_authenticatable
],
@routes=[:session, :password, :registration ],
@used_routes=[:session, :password, :registration ],
@used_helpers=[:session, :password, :registration ]
>,
:api_v1_user=>#<Devise::Mapping:0x000055a418563620
@scoped_path="api_v1/users",
@singular=:api_v1_user,
@class_name="User",
@klass=#<Devise::Getter:0x000055a4185630a8 @name="User">,
@path="",
@path_prefix="/api/v1",
@sign_out_via=:delete,
@format=nil,
@router_name=nil,
@failure_app=Devise::FailureApp,
@controllers={
:sessions=>"api/v1/users/sessions",
:registrations=>"api/v1/users/registrations",
:passwords=>"api/v1/passwords"},
@path_names={
:registration=>"signup",
:new=>"new",
:edit=>"edit",
:sign_in=>"login",
:sign_out=>"logout",
:password=>"password",
:sign_up=>"sign_up",
:cancel=>"cancel"
},
@modules=[
:database_authenticatable,
:rememberable,
:recoverable,
:registerable,
:validatable,
:trackable,
:jwt_authenticatable
],
@routes=[:session, :password, :registration],
@used_routes=[:session, :password, :registration],
@used_helpers=[:session, :password, :registration]
>
}
- Request
curl -X POST \
http://127.0.0.1:3000/api/v1/signup \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-d '{
"email": "test@test.com",
"first_name": "John",
"last_name": "Wick",
"password": "password"
}'
- Response
{
"status": {
"message": "User couldn't be created successfully. Email can't be blank and Password can't be blank"
}
}
It doesn't seem an issue with devise-jwt. Your user is not being created, and that's a responsibility that doesn't belong to this library but to devise itself. At first sight, I think you have a problem with your params, as they should be within the name of the user scope, like in `{"user": {"email": "...",...}}"
Hi @waiting-for-dev,
Thanks for your response. I tried keeping parameter inside "user"
but then to it was not working.
- Request
curl -X POST \
http://127.0.0.1:3000/api/v1/signup \
-H 'Cache-Control: no-cache' \
-H 'Content-Type: application/json' \
-d '{
"user": {
"email": "test@test2.com",
"password": "password",
"password_confirmation": "password"
}
}'
- Response
{
"status": {
"message": "User couldn't be created successfully. Email can't be blank and Password can't be blank"
}
}
I'm really sorry for not being able to give more support here. You'd better be off trying to find a solution in stack overflow as it seems it's a problem with your setup (devise-jwt is not involved here).
I dig around devise-jwt, warden-jwt_auth, warden and devise codebases for hours to figure how to get a token on the headers after registration. Hopefully it will save people time.
Your RegistrationsController has to look like this:
class RegistrationsController < Devise::RegistrationsController
# Needed by devise to authenticate the user after registration and return the auth token
prepend_before_action :allow_params_authentication!, only: :create
respond_to :json
# Sign in after sign up
def sign_up(resource_name, resource)
resource = warden.authenticate!
sign_in(resource_name, resource)
end
# By default devise tries to build the resources with a session like this:
# resource_class.new_with_session(hash, session)
# but because we are on API mode we don't have a session so we need to build the user
# without it
def build_resource(hash = {})
self.resource = resource_class.new(hash)
end
end
And the routes looks like this
Rails.application.routes.draw do
devise_for :users,
path: '',
path_names: {
sign_in: 'login',
sign_out: 'logout',
registration: 'signup'
},
controllers: {
sessions: 'sessions',
registrations: 'registrations'
}
end
@maqsudinamdar override resource_name
method in registration controller. By default it takes your controller path as a resource. So it looks like api_v1_user
. And it tried to fetch params with this key. But params contains another hash which called user
for registration. So you need to override method to be like that:
def resource_name
:user
end