Eki Eqbal
Eki Eqbal

Reputation: 6057

How to handle authentication token string in Grape API and token_and_options?

I'm using Ember front-end with Grape API and to authenticate a page I have something like:

  def current_user
    return nil if headers['Authorization'].nil?

    @current_user ||= User.find_by_authentication_token(
      headers['Authorization']
    )
  end

At the moment I'm using ember-simple-auth which sends something like as a header:

Token token="n2Rs5tokenLH", email="[email protected]"

Check out the Grape logger:

  User Load (38.1ms)  SELECT  "users".* FROM "users"  WHERE "users"."authentication_token" = 'Token token="n2Rs5tokenLH", email="[email protected]"' LIMIT 1

Now in rails I usually use authenticate_or_request_with_http_token to handle this. and I don't want to handle this manually using stuff like gsub or regular expressions. What is the best way to handle this in Grape?

Please note that the Grape API is mounted inside a rails project and inside app/api/backend/v1/default.rb:

module Backend
  module V1
    module Defaults
      extend ActiveSupport::Concern

      included do
        version 'v1', using: :path
        format :json
        prefix :api

        rescue_from :all do |e|
          Backend::Base.logger.error e
        end

        helpers do
          def current_user
            return nil if headers['Authorization'].nil?

            @current_user ||= User.find_by_authentication_token(
              headers['Authorization']
            )
          end

          def authenticate!
            error!('401 Unauthorized', 401) unless current_user
          end
        end
      end
    end
  end
end

Edit:

I just found out that I can use ActionController::HttpAuthentication::Token.token_and_options to do the job, however I'm not sure how should I include this in Grape project.

The structure of the app looks like:

⇒  tree app/api
app/api
└── backend
    ├── base.rb
    └── v1
        ├── defaults.rb
        └── ....

I tried to include ActionController::HttpAuthentication::Token in defaults.rbsomething like:

helpers do
  include ActionController::HttpAuthentication::Token

  def current_user
    return nil if headers['Authorization'].nil?

    @current_user ||= User.find_by_authentication_token(
      token_and_options(headers['Authorization'])
    )
  end

  def authenticate!
    error!('401 Unauthorized', 401) unless current_user
  end
end

But now I'm getting:

 undefined method `authorization' for #<String:0x007ff51cdb85f8>

Upvotes: 0

Views: 3429

Answers (2)

Brad Nauta
Brad Nauta

Reputation: 131

You're on the right path: use token_params_from and pass in the Authorization header directly.

helpers do
  include ActionController::HttpAuthentication::Token

  def current_user
    return nil if headers['Authorization'].nil?

    @current_user ||= User.find_by_authentication_token(token)
  end

  def token
    token_params_from(headers['Authorization']).shift[1]
  end

  def authenticate!
    error!('401 Unauthorized', 401) unless current_user
  end
end

The undefined method exception is due to token_and_options expecting a ActionDispatch::Request object where you're providing a String.

Upvotes: 4

Eki Eqbal
Eki Eqbal

Reputation: 6057

I had to add a customized token_and_options function which looks like:

  def token_and_options(auth_string)
    if header = auth_string.to_s[/^Token (.*)/]
      values = $1.split(',').
        inject({}) do |memo, value|
          value.strip!
          key, value = value.split(/\=\"?/)
          value.chomp!('"')
          value.gsub!(/\\\"/, '"')
          memo.update(key => value)
        end
      values.delete("token")
    end
  end

And then parse token with something like: token_and_options(headers['Authorization'])

Upvotes: 0

Related Questions