Search code examples
ruby-on-railsruby-on-rails-5bcrypt

Error on Logout: undefined method 'invalidate_token'


I'm building authentication on a Rails 5 API, and I'm having issues with the logout piece. I'm hitting the /logout route and receive a 500 error reading NoMethodError: undefined method 'invalidate_token' for nil:NilClass>, and it then references my sessions_controller. My sessions_controller references an invalidate_token method in the logout method that's declared in the Users model, and I'm stumped as to why the error is undefined in the controller when I have defined it in the model.

sessions_controller:

class SessionsController < ApiController
  skip_before_action :require_login, only: [:create], raise: false

  def create
    if (user = User.validate_login(params[:username], params[:password]))
      allow_token_to_be_used_only_once_for(user)
      send_token_for_valid_login_of(user)
    else
      render_unauthorized('Error creating the session with provided credentials')
    end
  end

  def destroy
    logout
    head :ok
  end

  private

  def send_token_for_valid_login_of(user)
    render json: { token: user.auth_token }
  end

  def allow_token_to_be_used_only_once_for(user)
    user.regenerate_auth_token
  end

  def logout
    @current_user.invalidate_token
  end
end

Users model

class User < ApplicationRecord
  # validate unique username and email
  validates_uniqueness_of :username, :email
  has_secure_password
  has_secure_token :auth_token

  # clear token value, used to logout
  def invalidate_token
    self.update_columns(auth_token: nil)
  end

  def self.validate_login(username, password)
    user = User.find_by(username: username)
    if user && user.authenticate(password)
      user
    end
  end
end

API Controller:

class ApiController < ApplicationController
  def require_login
    authenticate_token || render_unauthorized('Access Denied: Unauthorized Access')
  end

  def current_user
    @current_user ||= authenticate_token
  end

  protected

  def render_unauthorized(message)
    errors = { errors: [detail: message] }
    render json: errors, status: :unauthorized
  end

  private

  def authenticate_token
    authenticate_with_http_token do |token|
      User.find_by(auth_token: token)
    end
  end
end

Ideally, this should get past the logout method so it can render a 200 and actually invalidate the logged in users token.


Solution

  • You are doing a find_by when fetching the user record in authenticate_token. If no record is found, nil is returned.

    Either you got bad data, or your header is not valid.