'How to use authenticate_or_request_with_http_token for a http request in Rails?

If use authenticate_or_request_with_http_token in an API application, it works fine as this guide:

https://www.mccartie.com/tech/2016/11/03/token-based-api-authentication.html

But tried it in a normal rails application without json format, not work.

The source as:

# application_controller.rb

class ApplicationController < ActionController::Base
  def set_account
    current_user = authenticate_token
    ...
  end

  def authenticate
    authenticate_or_request_with_http_token do |token, options|
      ActiveSupport::SecurityUtils.secure_compare(token, 'a-test-token-here')
    end
  end
end

However, it got this error in an action controller:

AbstractController::DoubleRenderError in PostController#index
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".

From here, got information that not json api:

http://railscasts.com/episodes/352-securing-an-api?view=comments#comment_164947

Rails version: 5.2.2

Edit

Have changed to these files:

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
end

app/controllers/api_controller.rb

class ApiController < ActionController::Base
  def require_login
    authenticate_token
  end

  private

  def authenticate_token
    authenticate_or_request_with_http_token do |token, options|
      Rails.logger.info '------------token---------'
      Rails.logger.info token
      Rails.logger.info '------------token---------'
      ActiveSupport::SecurityUtils.secure_compare(token, 'HrcLNGkq8T7Hc4Kxs8bYQw1z')
    end
  end
end

app/controllers/post_controller.rb

class PostController < ApiController
  before_action :require_login

  def index
  end
end

Have used post method created an user with token:

app/controllers/sessions_controller.rb

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

  def create
    if user = User.valid_login?(params[:email], params[:password])
      generate_token(user)
    end
  end

  private

  def generate_token(user)
    user.regenerate_token
  end
end

app/models/user.rb

class User < ApplicationRecord
  has_secure_password
  has_secure_token
end

DB

User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "abc", token: "HrcLNGkq8T7Hc4Kxs8bYQw1z", email: "[email protected]", password_digest: "$2a$12$EqrQXjUWZIswSpX5J3BBIp4gsQ8GcpZPJ4eKUS4/oY4...", created_at: "2019-08-22 13:57:57", updated_at: "2019-08-22 14:31:01">

But when access endpoint: http://localhost:3000/post/index, got log:

Started GET "/post/index" for ::1 at 2019-08-22 23:54:11 +0900
Processing by PostController#index as HTML
  Rendering text template
  Rendered text template (0.0ms)
Filter chain halted as :require_login rendered or redirected
Completed 401 Unauthorized in 1ms (Views: 0.8ms | ActiveRecord: 0.0ms)

and this message in the browser:

HTTP Token: Access denied.

It seems didn't go into the authenticate_or_request_with_http_token block.



Solution 1:[1]

This way works:

# app/controllers/api_controller.rb
class ApiController < ActionController::Base
  def require_login
    @current_user = User.find_by(id: 1)
    authenticate_token
  end

  private

  def authenticate_token
    auth = ActionController::HttpAuthentication::Token.encode_credentials(@current_user.token)
    request.headers['Authorization'] = auth
    # request['authorization'] = "Token token=HrcLNGkq8T7Hc4Kxs8bYQw1z"

    authenticate_or_request_with_http_token do |token, options|
      Rails.logger.info '------------token---------'
      Rails.logger.info token
      Rails.logger.info '------------token---------'
      if user = User.find_by(token: token)
        ActiveSupport::SecurityUtils.secure_compare(token, user.token)
      end
    end
  end
end

Ref: https://stackoverflow.com/a/30720495

Solution 2:[2]

The issue is more that the method you're calling fallbacks to a request method (with a builtin response) on failure:

def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure)
      authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message)
end

If you don't want the render response happening, just use authenticate_with_http_token as a drop in replacement. If you do want the render response happening then you need to stop processing on authentication failure.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 02040402
Solution 2 toxaq