0

I've implemented Matteo Melanis great blog article A Simple Token Authentication Service for Mobile Devices. It works beautifully with the Chrome extension Postman. However, when I try to fetch a user authentication token with cUrl, I'm running into a bizzare problem.

First the development.log entry for the (successful) authentication fetch using Postman:

Started POST "/api/v1/tokens.json?email=my@mail.com&password=[FILTERED]" for 127.0.0.1 at 2013-11-25 11:28:21 +0100
Processing by Api::V1::TokensController#create as JSON
  Parameters: {"email"=>"my@mail.com", "password"=>"[FILTERED]"}
  [1m[36mUser Load (1.4ms)[0m  [1mSELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1[0m
Entered create method
  [1m[35mUser Load (3.9ms)[0m  SELECT "users".* FROM "users" WHERE "users"."email" = 'my@mail.com' LIMIT 1
Completed 200 OK in 99ms (Views: 0.3ms | ActiveRecord: 5.3ms)

Then, when I run $ curl -X POST "https://localhost:3000/api/v1/tokens.json?email=my@mail.com&password=somepassword" -d "email=my@mail.com&password=somepassword" -v -k -i, I get

Started POST "/api/v1/tokens.json?email=my@mail.com&password=[FILTERED]" for 127.0.0.1 at 2013-11-25 11:29:01 +0100
Processing by Api::V1::TokensController#create as JSON
  Parameters: {"email"=>"my@mail.com", "password"=>"[FILTERED]"}
Completed 401 Unauthorized in 12ms

You might ask why I provided the parameters both as HTTP Post data, and as a query string. Well, my initial research with the curl lib sample http-post.c suggest the former, while Postmans successful query suggests the latter. I've tried all combinations of these, but nothing works, so I'm pretty lost.

In the Api::V1::TokensController, I've added logging whenever the create method is being called, that is

class Api::V1::TokensController  < ApplicationController
  skip_before_filter :verify_authenticity_token
  respond_to :json

  def create

    Rails.logger.debug("Entered create method")

    email = params[:email]
    password = params[:password]
    if request.format != :json
      render :status=>406, :json=>{:message=>"The request must be json"}
      return
    end

    if email.nil? or password.nil?
       render :status=>400,
              :json=>{:message=>"The request must contain the user email and password."}
       return
    end

    @user=User.find_by_email(email.downcase)

    if @user.nil?
      logger.info("User #{email} failed signin, user cannot be found.")
      render :status=>401, :json=>{:message=>"Invalid email or password."}
      return
    end

    # http://rdoc.info/github/plataformatec/devise/master/Devise/Models/TokenAuthenticatable
    @user.ensure_authentication_token!

    if not @user.valid_password?(password)
      logger.info("User #{email} failed signin, password \"#{password}\" is invalid")
      render :status=>401, :json=>{:message=>"Invalid email or password."}
    else
      render :status=>200, :json=>{:token=>@user.authentication_token}
    end
  end

  def destroy
    @user=User.find_by_authentication_token(params[:id])
    if @user.nil?
      logger.info("Token not found.")
      render :status=>404, :json=>{:message=>"Invalid token."}
    else
      @user.reset_authentication_token!
      render :status=>200, :json=>{:token=>params[:id]}
    end
  end

end

As can be seen from the logs, create method is being called in the first place, but not in the second. It is as if Api::V1::TokensController's skip_before_filter :verify_authenticity_token is ignored altogether. But how can that be?

Any suggestion is greatly appreciated!

conciliator
  • 6,078
  • 6
  • 41
  • 66
  • My hunch is that you're not sending the curl correctly for json. Take a look here http://stackoverflow.com/a/7173011/600953 – Dty Nov 25 '13 at 11:05
  • 1
    In the network tab in chrome dev tools, you can copy a request to cURL format. Try comparing that to what you are sending to see what's missing. – Slicedpan Nov 25 '13 at 11:50
  • Try basic post with curl like `curl -d '' https://localhost:3000/api/v1/tokens.json?email=my@mail.com&password=password` also I see your making and `https` request to localhost see if that is required at all if yes , then perhaps you provide certificate to curl or use `--no-check-certificate` either way it shoyld work because I use it all the time – Viren Nov 25 '13 at 18:17
  • Thanks to all three for great suggestions. In the end, @Slicedpan led me to the solution - it seems that there is a cookie being set when using Postman. I'll write up an answer on it in due time. :) – conciliator Nov 25 '13 at 18:37
  • 1
    Postman will use any existing cookies set for the domain, (caused me a few headaches in the past!) – Slicedpan Nov 26 '13 at 09:29

1 Answers1

0

So here's what I did. For the regular html views, I'd like to keep using cookies for user convenience. For the (mobile) REST API, I'd like to disable cookies, as auth_token will be used throughout.

I'm not a Rails/Devise super hero, so I'm a little shaky about this, but I think it should be ok ... anyway: I disabled authentication before hitting the tokens controller altogether. In order to get anything sensible out of the tokens controller, you'd have to supply a matching email/password credentials pair anyway, so I can't (at the moment) see any obvious security vulnerabilities with this approach.

Thus, my new TokensController look like this:

class Api::V1::TokensController  < ApplicationController
  skip_before_filter :verify_authenticity_token
  skip_before_filter :authenticate_player!
  respond_to :json

  def create
    # ....

Hope this helps someone else stuck out there. And any feedback on possible security issues is greatly appreciated!

Update:

I forgot to mention that, as of now, the entire application is communicated over HTTPS.

conciliator
  • 6,078
  • 6
  • 41
  • 66