4

I'm implementing Sign In with Facebook on my project, which consists in two applications:

  • Frontend: plain HTML+CSS+JS;
  • Backend: Rails 5 API (omniauth 1.3.1, omniauth-facebook 3.0.0);

Even in my local environment, both applications run on different domains (frontend.dev.azk.io and api.dev.azk.io).

I run the FB SDK in my frontend app, and once the login succeeds, I should ping the OAuth callback (http://api.dev.azk.io/api/v1/users/auth/facebook/callback) so that omniauth-facebook could access the fbsr_XXX cookie, parse it and exchange the current accessToken for a long term one.

The main issue is that the cookie set by FB SDK belongs to the frontend domain and isn't sent in the header of the request to the API, i.e. omniauth-facebook cannot access it:

ERROR -- omniauth: (facebook) Authentication failure! no_authorization_code: OmniAuth::Strategies::Facebook::NoAuthorizationCodeError, must pass either a `code` (via URL or by an `fbsr_XXX` signed request cookie)

At this point, I thought in two possible approaches:

1) Check CORS and AJAX configs so that the cookie could be sent in the request to the API;

2) Parse the FB response in the frontend (using this algorithm) and send the code attribute to the API;

Approach 1

Inspired by this incredibly similar question here in SO, I've made the following configurations:

routes.rb

devise_for :users, path_prefix: 'api/v1', controllers: {
  sessions:           'api/v1/sessions',
  registrations:      'api/v1/registrations',
  omniauth_callbacks: 'api/v1/omniauth_callbacks',
}

application.rb

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'frontend.dev.azk.io'
    resource '*',
      headers: :any,
      credentials: true,
      methods: [:get, :put, :post, :patch, :delete, :options]
  end
end

devise.rb

config.omniauth :facebook, oauth_providers['facebook']['app_id'], oauth_providers['facebook']['secret'], {
  scope: 'email,user_birthday,user_about_me,public_profile',
  provider_ignores_state: true
}

Client Application

$.ajax({
  type: 'GET',
  url: 'http://api.dev.azk.io/api/v1/users/auth/facebook/callback',
  crossDomain: true,
  xhrFields: { withCredentials: true },
}) . . .

However, this isn't enough for sending the fbsr_XXX cookie (present in the browser). Is there anything I'm missing?

Approach 2

Since omniauth-facebook can't access the aforementioned cookie, I've decided to parse the signedRequest value returned by FB and send the code attribute for the API.

However, seems that omniauth-facebook generates the callback_url, which mismatch with the one used in the previous request:

ERROR -- omniauth: (facebook) Authentication failure! invalid_credentials: OAuth2::Error, : 
{"error":{"message":"Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"BavpcDFblHy"}}

I really don't like the 2nd approach, I think the first one is way more elegant. I think this process should be simple, probably I'm missing something silly.

Community
  • 1
  • 1
Felipe Arenales
  • 991
  • 1
  • 8
  • 19
  • in my case, using Frontend & API backend, I no longer use cookies because APIs should be stateless, I use JsonWebTokens (JWT). When you call the FB SDK login from the Frontend you should receive the token on the frontend and store it on browser's local storage. then, in any transaction with the api you should send the token, you shouldn't store the token on the api to keep it stateless – Cristian Sepulveda Dec 23 '16 at 11:14

0 Answers0