Question

I have a front-end in AngularJS and back-end in RoR with Devise + Doorkeeper + RocketPants. For now I have properly working CORS, I can succesfully get json responses from my API (if I turn off doorkeeper protection). But now I'm trying to implement user password credentials flow:

On doorkeeper:

  resource_owner_from_credentials do |routes|
    request.params[:user] = {:email => request.params[:username], :password => request.params[:password]}
    request.env["devise.allow_params_authentication"] = true
    request.env["warden"].authenticate!(:scope => :user)
  end

On angularjs (src: nils-blum):

var payload = "username="+username+"&password="+password+"&" +
        "client_id="+client_id+"&client_secret="+client_secret+
        "&grant_type=password"

$http({method: 'POST',
    url: scope.booksh_server + '/oauth/token',
    data:   payload,
    headers: {'Content-Type':'application/x-www-form-urlencoded'}
    }
).success(function (data) {
            tokenHandler.set(data.access_token);
            scope.$broadcast('event:authenticated');
});

Note: Nils's approach uses payload as object with params, not a string. In my case it gives me POST with payload, not with FORM params, and thus not working.

When I enter wrong user/password, rails log says:

    Started POST "/oauth/token" for 127.0.0.1 at 2013-11-22 15:21:08 +0400
  Doorkeeper::Application Load (0.5ms)  SELECT "oauth_applications".* FROM "oauth_applications" WHERE "oauth_applications"."uid" = '981d6d654f5e709b2ca3437401c993a6d09cc91cc3fb16b8e2b3191e6421029c' AND "oauth_applications"."secret" = 'f92c4ec969525352bd03ec1eb810a9952cd0814d37ce5b2b02e8a928b2561e10' ORDER BY "oauth_applications"."id" ASC LIMIT 1
  User Load (0.9ms)  SELECT "users".* FROM "users" WHERE "users"."email" = 'undefined' ORDER BY "users"."id" ASC LIMIT 1

Chrome dev tools show this request as cancelled. Firebug shows empty response with 302 Found status. When I perform same POST from chrome REST App, UPD: server does the same, and I get 302 Found and then it's redirected to sign_in page of devise.

Moreover, if I enter correct user/password, firefox shows empty response with 200 OK, REST receives proper json with access token, and chrome shows cancelled response and throws CORS error:

XMLHttpRequest cannot load http://0.0.0.0:3000/oauth/token. Origin http://0.0.0.0:9000 is not allowed by Access-Control-Allow-Origin. 

Server log:

Started POST "/oauth/token" for 127.0.0.1 at 2013-11-22 15:33:22 +0400
  Doorkeeper::Application Load (0.5ms)  SELECT "oauth_applications".* FROM "oauth_applications" WHERE "oauth_applications"."uid" = '981d6d654f5e709b2ca3437401c993a6d09cc91cc3fb16b8e2b3191e6421029c' AND "oauth_applications"."secret" = 'f92c4ec969525352bd03ec1eb810a9952cd0814d37ce5b2b02e8a928b2561e10' ORDER BY "oauth_applications"."id" ASC LIMIT 1
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."email" = '123@gmail.com' ORDER BY "users"."id" ASC LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.4ms)  UPDATE "users" SET "last_sign_in_at" = $1, "current_sign_in_at" = $2, "sign_in_count" = $3, "remember_token" = $4, "updated_at" = $5 WHERE "users"."id" = 1  [["last_sign_in_at", Fri, 22 Nov 2013 11:32:49 UTC +00:00], ["current_sign_in_at", Fri, 22 Nov 2013 11:33:23 UTC +00:00], ["sign_in_count", 24], ["remember_token", "N5sRI6vE7B6vRNUlih3G2Q"], ["updated_at", Fri, 22 Nov 2013 11:33:23 UTC +00:00]]
   (17.5ms)  COMMIT
   (1.6ms)  BEGIN
  Doorkeeper::AccessToken Exists (0.3ms)  SELECT 1 AS one FROM "oauth_access_tokens" WHERE "oauth_access_tokens"."token" = '72dd81fd85b638fb14f9d081193b1eda0e58f85d6820718ab635fe195c36a689' LIMIT 1
  SQL (0.3ms)  INSERT INTO "oauth_access_tokens" ("application_id", "created_at", "expires_in", "resource_owner_id", "scopes", "token") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["application_id", 1], ["created_at", Fri, 22 Nov 2013 11:33:23 UTC +00:00], ["expires_in", 7200], ["resource_owner_id", 1], ["scopes", ""], ["token", "72dd81fd85b638fb14f9d081193b1eda0e58f85d6820718ab635fe195c36a689"]]
   (28.1ms)  COMMIT

How can I make this work? I need POST /oauth/token to process authentication and respond with access_token in case email/password were correct. Any help is appreciated, I'm tired of being stuck at this )

Was it helpful?

Solution

Yay! Finally I made it work. Still dunno why in tutorials all over the web it's much easier.

So, where the problem comes from: in my ApplicationController (root one, not the one that handles API) I had following for CORS:

  before_filter :set_headers

  def set_headers
    headers['Access-Control-Allow-Origin'] = 'http://0.0.0.0:9000'
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD'
    headers['Access-Control-Allow-Headers'] = '*,X-Requested-With,Content-Type,If-Modified-Since,If-None-Match'
    headers['Access-Control-Max-Age'] = '86400'
  end

Digging into the problem I've found that this function is not called when I call /oauth/token but is called when I go to any other route outside of API which is processed with ApplicationController

Doorkeeper has its own controllers independent from your app controller. Docs describes changing of their behavior well (link), so here is what helped me:

routes.rb:

use_doorkeeper do
    controllers tokens: 'custom_tokens'
end

custom_tokens_controller.rb:

class CustomTokensController < Doorkeeper::TokensController

  include AbstractController::Callbacks
  before_filter :set_headers

  def set_headers
    puts 'headers set'
    headers['Access-Control-Allow-Origin'] = 'http://0.0.0.0:9000'
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD'
    headers['Access-Control-Allow-Headers'] = '*,X-Requested-With,Content-Type,If-Modified-Since,If-None-Match'
    headers['Access-Control-Max-Age'] = '86400'
  end
end

Important thing is to include Callbacks into controller, as Doorkeeper controllers are inherited from Metal, so without inclusion rails can't find before_filter.

It looks good now: It authenticates my user/password pair, and returns access_token (I hope, it works :D ). If something is wrong, now there should come redirect. This is the problem which I know solution to (custom warden failure), so everything is OK now. Huh! It was long... If anyone knows why internet-based recipes didn't help, please post your thoughts =)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top