I have a Rails 5 API and I'm setting up authentication. I have added some custom password requirements, and everything works for account creation and logging an account out, but I get a Completed 422 Unprocessable Entity
error that goes along with a Can't verify CSRF token authenticity
message whenever I try to log a user in. When I remove the line setting the custom password validation, everything works.
I've added protect_from_forgery with: :null_session
to my sessions controller but it had no impact.
Registration Model:
class Registration < ApplicationRecord
@username_length = (3..20)
PASSWORD_CONFIRMATION = /\A
(?=.{8,}) # Must contain 8 or more characters
(?=.*\d) # Must contain a digit
(?=.*[a-z]) # Must contain a lower case character
(?=.*[A-Z]) # Must contain an upper case character
(?=.*[[:^alnum:]]) # Must contain a symbol
/x
validates :username, uniqueness: true, length: @username_length
has_secure_password
validates :password, format: PASSWORD_CONFIRMATION
has_secure_token :auth_token
#used to logout
def invalidate_token
self.update_columns(auth_token: nil)
end
# makes sure use of built-in auth method bcrypt gives and hashes the password
# against the password_digest in the db
def self.validate_login(username, password)
registration = find_by(username: username)
if registration && registration.authenticate(password)
registration
end
end
end
Sessions Controller
class SessionsController < ApiController
skip_before_action :require_login, only: [:create], raise: false
protect_from_forgery with: :null_session
def create
if registration = Registration.validate_login(params[:username], params[:password])
allow_token_to_be_used_only_once_for(registration)
send_token_for_valid_login_of(registration)
else
render_unauthorized('Error with your login or password')
end
end
def destroy
logout
head :ok
end
private
def send_token_for_valid_login_of(registration)
render json: { token: registration.auth_token }
end
def allow_token_to_be_used_only_once_for(registration)
registration.regenerate_auth_token
end
def logout
current_registration.invalidate_token
end
end
Registration Controller
class RegistrationController < ApplicationController
skip_before_action :verify_authenticity_token
def index; end
def custom
user = Registration.create!(registration_params)
puts "NEW USER #{user}"
render json: { token: user.auth_token, id: user.id }
end
def profile
user = Registration.find_by_auth_token!(request.headers[:token])
render json: {
user: { username: user.username, email: user.email, name: user.name }
}
end
private
def registration_params
params.require(:registration).permit(:username, :email, :password, :name)
end
end
Ideally, logging in should return a 200 message with an auth token as it does for account creation.
There were two things I had to tweak. Firstly, I needed to pass the validations false
flag in my registration model, so has_secure_password
became has_secure_password :validations => false
.
Secondly I was calling the wrong field name on password
in the model. By calling the PASSWORD_CONFIRMATION
variable on password_digest
instead of password
, my login started to work.