title: Rails 5 API custom authentication: 'skip_before_action' on GET routes
body: <p>I've used <a href="https://scotch.io/tutorials/build-a-restful-json-api-with-rails-5-part-two" rel="nofollow noreferrer">this</a> series as a starting point for a Rails backend for a work portfolio website. Adapting it has been mostly straightforward, and it's doing what I want it to. The one big problem is that the 'index' and 'show' (read actions) should be available without authentication, while 'create', 'update', and 'delete' (write actions) should require a valid JWT.</p>
  
  <p>Following the approach used to exclude the signup and login routes from authentication, I've tried </p>
  
  <pre><code>skip_before_action :authorize_request, only: [:index, :show]
  </code></pre>
  
  <p>in the appropriate controller. This will however crash the application, with</p>
  
  <pre><code>NoMethodError (undefined method `works' for nil:NilClass):
  app/controllers/works_controller.rb:10:in `index'
  </code></pre>
  
  <p>While the problem seems apparent - if skipping the authentication action the class doesn't get instantiated - the fix isn't, to me at least. Could anyone please help?</p>
  
  <p>The code for the project is <a href="https://git.brnrds.com/brnrds/brnrds-server" rel="nofollow noreferrer">here</a>.</p>
  
  <p><strong>Application controller</strong></p>
  
  <pre><code>class ApplicationController &lt; ActionController::API
    include Response
    include ExceptionHandler
  
    # called before every action on controllers
    before_action :authorize_request
    attr_reader :current_user
  
    private
  
    # Check for valid request token and return user
    def authorize_request
      @current_user = (AuthorizeApiRequest.new(request.headers).call)[:user]
    end
  end
  </code></pre>
  
  <p><strong>'Works' controller</strong></p>
  
  <pre><code>class WorksController &lt; ApplicationController
  
    #skip_before_action :authorize_request, only: [:index, :show]
  
    before_action :set_work, only: [:show, :update, :destroy]
  
    # GET /works
    def index
      @works = current_user.works
      json_response(@works)
    end
  
    # POST /works
    def create
      @work = current_user.works.create!(work_params)
      json_response(@work, :created)
    end
  
    # GET /works/:id
    def show
      json_response(@work)
    end
  
    # PUT /works/:id
    def update
      @work.update(work_params)
      head :no_content
    end
  
    # DELETE /works/:id
    def destroy
      @work.destroy
      head :no_content
    end
  
    private
  
    def work_params
      # whitelist params
      params.permit(:title, :nature, :role, :client, :timeframe, :description, :images, :url, :blog_post)
    end
  
    def set_work
      @work = Work.find(params[:id])
    end
  end
  </code></pre>
  
  <p><strong>'Users' controller</strong></p>
  
  <pre><code>class UsersController &lt; ApplicationController
  
    skip_before_action :authorize_request, only: :create
  
    def create
      user = User.create!(user_params)
      auth_token = AuthenticateUser.new(user.username, user.password).call
      response = { message: Message.account_created, access_token: auth_token }
      json_response(response, :created)
    end
  
    def show
      json_response(username: current_user.username)
    end
  
    private
  
    def user_params
      params.permit(
        :username,
        :password,
        :password_confirmation
      )
    end
  end
  </code></pre>
  
  <p><strong>'Authentication' controller</strong></p>
  
  <pre><code>class AuthenticationController &lt; ApplicationController
  
    skip_before_action :authorize_request, only: :authenticate
  
    # return auth token once user is authenticated
    def authenticate
      auth_token =
        AuthenticateUser.new(auth_params[:username], auth_params[:password]).call
      json_response(access_token: auth_token)
    end
  
    private
  
    def auth_params
      params.permit(:username, :password)
    end
  end
  </code></pre>
  
  <p><strong>'AuthenticateUser' helper</strong></p>
  
  <pre><code>class AuthenticateUser
    def initialize(username, password)
      @username = username
      @password = password
    end
  
    # Service entry point
    def call
      JsonWebToken.encode(user_id: user.id) if user
    end
  
    private
  
    attr_reader :username, :password
  
    # verify user credentials
    def user
      user = User.find_by(username: username)
      return user if user &amp;&amp; user.authenticate(password)
      # raise Authentication error if credentials are invalid
      raise(ExceptionHandler::AuthenticationError, Message.invalid_credentials)
    end
  end
  </code></pre>
  
  <p><strong>'AuthorizeApiRequest' helper</strong></p>
  
  <pre><code>class AuthorizeApiRequest
    def initialize(headers = {})
      @headers = headers
    end
  
    # Service entry point - return valid user object
    def call
      {
        user: user
      }
    end
  
    private
  
    attr_reader :headers
  
    def user
      # check if user is in the database
      # memoize user object
      @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
      # handle user not found
    rescue ActiveRecord::RecordNotFound =&gt; e
      # raise custom error
      raise(
        ExceptionHandler::InvalidToken,
        ("#{Message.invalid_token} #{e.message}")
      )
    end
  
    # decode authentication token
    def decoded_auth_token
      @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
    end
  
    # check for token in `Authorization` header
    def http_auth_header
      if headers['Authorization'].present?
        return headers['Authorization'].split(' ').last
      end
        raise(ExceptionHandler::MissingToken, Message.missing_token)
    end
  end
  </code></pre>
  
  <p><strong>'ExceptionHandler' helper</strong></p>
  
  <pre><code>module ExceptionHandler
    extend ActiveSupport::Concern
  
    # Define custom error subclasses - rescue catches `StandardErrors`
    class AuthenticationError &lt; StandardError; end
    class MissingToken &lt; StandardError; end
    class InvalidToken &lt; StandardError; end
  
    included do
      # Define custom handlers
      rescue_from ActiveRecord::RecordInvalid, with: :four_twenty_two
      rescue_from ExceptionHandler::AuthenticationError, with: :unauthorized_request
      rescue_from ExceptionHandler::MissingToken, with: :four_twenty_two
      rescue_from ExceptionHandler::InvalidToken, with: :four_twenty_two
  
      rescue_from ActiveRecord::RecordNotFound do |e|
        json_response({ message: e.message }, :not_found)
      end
    end
  
    private
  
    # JSON response with message; Status code 422 - unprocessable entity
    def four_twenty_two(e)
      json_response({ message: e.message }, :unprocessable_entity)
    end
  
    # JSON response with message; Status code 401 - Unauthorized
    def unauthorized_request(e)
      json_response({ message: e.message }, :unauthorized)
    end
  end
  </code></pre>