ruby-on-railsrubyruby-on-rails-5bcryptbcrypt-ruby

Can't verify CSRF token authenticity with Custom Password Requirements


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.


Solution

  • 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.