Knock: Filter chain halted as :authenticate_user rendered or redirected Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)

Created on 1 Feb 2017  路  5Comments  路  Source: nsarno/knock

I am trying to setup a Rails API using auth0 for authentication. I have followed the instructions at Auth0 rails API quickstart.
This is how my User model is:

class User < ActiveRecord::Base
  acts_as_paranoid
  has_secure_password
  has_many :developed_apps, class_name: "App", foreign_key: "developer_id", dependent: :destroy
  has_many :installations, foreign_key: "user_id", dependent: :destroy
  has_many :non_installations, foreign_key: "user_id", dependent: :destroy
  has_many :installed_apps, through: :installations, class_name: "App", source: :app#, counter_cache: true
  has_many :non_installed_apps, through: :non_installations, class_name: "App", source: :app#, counter_cache: true
  validates :auth0_id_string, uniqueness: true, presence: true
  def self.from_token_payload payload
    # Returns a valid user, `nil` or raise
    # e.g.
    self.find_or_create_by!(auth0_id_string: payload["sub"])
    #self.first
    #nil
  end
end

I have just auth0_id_string and password_digest fields in user column of db.
My controllers and routes are namespaced like:
/controllers/api/v1/apps_controller.rb

module Api
  module V1
    class AppsController < ApplicationController
      before_action :authenticate_user
      before_action :set_app, only: [:show, :update, :destroy]
      before_action :require_developer, only: [:update, :destroy]

      ITEMS_PER_PAGE = 2

      def index
        if params[:page]
          @page_no = params[:page].to_i
        else
          @page_no = 1
        end

        if !@page_no.to_i.between?(1,((App.count.to_f/ITEMS_PER_PAGE).ceil))
          render json: { errors: ["Invalid page number."] }, status: 400 and return
        end

        @apps = App.offset((@page_no - 1) * ITEMS_PER_PAGE).limit(ITEMS_PER_PAGE)
        render json: { :last => (@page_no == (App.count.to_f/ITEMS_PER_PAGE).ceil), :data => @apps }
      end

      def show
        render json: @app
      end

      def create
        @app = App.new(app_params)
        @app.developer = current_user

        if @app.save
          render json: @app
        else
           render json: { errors: ["Invalid attributes."] }, status: 400
        end
      end

      def update
        if @app.update(app_params)
          render json: @app
        else
          render json: { errors: ["Invalid attributes."] }, status: 400
        end
      end

      def destroy
        if @app.destroy
          render json: { errors: ["Deletion successful."] }, status: 200
        else
          render json: { errors: ["Deletion failed."] }, status: 500
        end
      end

      private

      def set_app
        @app = App.find(params[:id])
      end

      def require_developer
        if current_user != @app.developer
          render json: { errors: ["Authorized users only."] }, status: :unauthorized
        end
      end

      def app_params
        params.require(:app).permit(:name, :description, :blob_location)
      end

    end
  end
end

routes.rb:

Rails.application.routes.draw do
  namespace :api, defaults: {format: :json} do
    namespace :v1 do
      resources :apps
    end
  end
end

I have added bcrypt to Gemfile for has_secure_password.
application_controller.rb:

/controllers/application_controller.rb:  
class ApplicationController < ActionController::API
  include Knock::Authenticable
end

I am including Authorization in header as Bearer jwt_token, the token I have verified is correct from jwt.io and other sites.
HTTP snipet from Postman:

GET /api/v1/apps HTTP/1.1
Host: localhost:3000
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InNpZC5zd2FwbmlsZGV2ZXNoQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJpc3MiOiJodHRwczovL3NpZGV2ZXNoLmF1dGgwLmNvbS8iLCJzdWIiOiJhdXRoMHw1ODkxNDMxZDg1OGQwNDY5YjgwOGFmZmEiLCJhdWQiOiJ1NFhpZUxoRzBJMUZ5YmxGNFFaSWZVN2JkdDhkcm5CZSIsImV4cCI6MTQ4NTk1MjA0NCwiaWF0IjoxNDg1OTE2MDQ0fQ.rRExJkabeB_xrdHpsUIELA9jUSJZq4mxlkDMt6T-OgA
Cache-Control: no-cache
Postman-Token: 4dc93848-3f9d-6366-3a01-c4f6a86a7640

Here is my Gemfile for version info:

source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end


# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.0.1'
# Use postgresql as the database for Active Record
gem 'pg', '~> 0.18'
# Use Puma as the app server
gem 'puma', '~> 3.0'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

gem 'knock', '~> 2.0'
gem "paranoia", "~> 2.2"
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platform: :mri
end

group :development do
  gem 'listen', '~> 3.0.5'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Everytime I am sending a request all I am getting is the response mentioned in title.
Although I noticed that if I remove Authorization from header, I am getting this in log:

Started GET "/api/v1/apps" for 127.0.0.1 at 2017-02-01 15:27:38 +0530
Processing by Api::V1::AppsController#index as JSON
  User Load (77.1ms)  SELECT  "users".* FROM "users" WHERE "users"."deleted_at" IS NULL AND "users"."auth0_id_string" IS NULL LIMIT $1  [["LIMIT", 1]]
   (0.1ms)  BEGIN
  User Exists (0.4ms)  SELECT  1 AS one FROM "users" WHERE "users"."auth0_id_string" IS NULL AND "users"."deleted_at" IS NULL LIMIT $1  [["LIMIT", 1]]
   (0.2ms)  ROLLBACK
Filter chain halted as :authenticate_user rendered or redirected
Completed 401 Unauthorized in 299ms (ActiveRecord: 80.8ms)

Why is this not working with this configuration ? Am I missing something ?
I noticed similar issue in auth0 forums but that was regarding some old issue in ruby-jwt gem. Link.

Most helpful comment

OK, figured this out...
First,
the secret key I got from auth0 was not base64 encoded, so the commented out line in the generated initializer of knock (/config/initializers/knock.rb) for auth0, which decoded the secret key as base64 ended up giving wrong key,
Now I am using this:
config.token_secret_signature_key = -> { Rails.application.secrets.auth0_client_secret }

Second,
I was only able to figure this out by overriding define_current_entity_getter method in my application_controller.rb and removing the rescue.
I agree with Issue #122 , the rescue is too agressive and fails silently, maybe pull request #133 can solve the issue.

All 5 comments

OK, figured this out...
First,
the secret key I got from auth0 was not base64 encoded, so the commented out line in the generated initializer of knock (/config/initializers/knock.rb) for auth0, which decoded the secret key as base64 ended up giving wrong key,
Now I am using this:
config.token_secret_signature_key = -> { Rails.application.secrets.auth0_client_secret }

Second,
I was only able to figure this out by overriding define_current_entity_getter method in my application_controller.rb and removing the rescue.
I agree with Issue #122 , the rescue is too agressive and fails silently, maybe pull request #133 can solve the issue.

@SiDevesh Thanks for posting this. Knock, I'd really appreciate if you didn't fail silently. This issue took me hours to find.

@adammcnamara whole day for me. At some point I realized that from_token_payload gets called only if no token is sent. A hint in the generated initializer would have helped me already.

I'm still having issues with this. Im seeing that Auth0 always uses RS256 algorithm, so I changed it in the knock.rb config config.token_signature_algorithm = 'RS256' and added the config.token_public_key as well.

Stepping through I see the algorithms match:

(byebug) options
{:verify_expiration=>true, :verify_not_before=>true, :verify_iss=>false, :verify_iat=>false, :verify_jti=>false, :verify_aud=>true, :verify_sub=>false, :leeway=>0, :aud=>"...secret...", :algorithm=>"RS256"}
(byebug) algo
"RS256"

My JWT validates and signature verifies at jwt.io but when I step through the decoding it always falls right into:

``` 142: key = yield(header) if keyfinder
143: [header['alg'], key]
144: end
145:
146: def verify_signature(algo, key, signing_input, signature)
=> 147: verify_signature_algo(algo, key, signing_input, signature)
148: rescue OpenSSL::PKey::PKeyError
149: raise JWT::VerificationError, 'Signature verification raised'
150: ensure
151: OpenSSL.errors.clear
(byebug) next

[146, 155] in .gem/ruby/2.4.0/gems/jwt-1.5.6/lib/jwt.rb
146: def verify_signature(algo, key, signing_input, signature)
147: verify_signature_algo(algo, key, signing_input, signature)
148: rescue OpenSSL::PKey::PKeyError
149: raise JWT::VerificationError, 'Signature verification raised'
150: ensure
=> 151: OpenSSL.errors.clear
152: end
153:
154: def verify_signature_algo(algo, key, signing_input, signature)
155: if %w(HS256 HS384 HS512).include?(algo)
(byebug) next

[47, 56] in .gem/ruby/2.4.0/gems/knock-2.1.1/lib/knock/authenticable.rb
47: unless instance_variable_defined?(memoization_var_name)
48: current =
49: begin
50: Knock::AuthToken.new(token: token).entity_for(entity_class)
51: rescue
=> 52: nil
53: end
54: instance_variable_set(memoization_var_name, current)
55: end
56: instance_variable_get(memoization_var_name)
```

Continuing to result in 'Filter chain halted as :authenticate_user rendered or redirected Completed 401 Unauthorized'

Moving this to a separate issue #146

Was this page helpful?
0 / 5 - 0 ratings