Search code examples
javascriptruby-on-railsgoogle-apigoogle-appsgoogle-sso

Rails / Google SSO - auth popup causing: Blocked a frame with origin


I have a Rails app that uses Google SSO to sign in / up users. It works fine in Safari, but in Chrome I keep getting this error:

Blocked a frame with origin "[domain]" from accessing a frame with origin "https://accounts.google.com". Protocols, domains, and ports must match.

I have previously used this "solution" to solve it: https://github.com/zquestz/omniauth-google-oauth2/issues/122#issuecomment-60510241

But I recently noticed that it suddenly stopped working for Safari with the above solution instead.

My current implementation looks like this (coffee script):

$(document).ready ->
  $.ajax
    url: 'https://apis.google.com/js/client:plus.js?onload=gpAsyncInit'
    dataType: 'script'
    cache: false

window.gpAsyncInit = ->

  $('.googleplus-login').click (e) ->
  e.preventDefault()

  gapi.auth.authorize {
    immediate: false
    response_type: 'code'
    cookie_policy: 'single_host_origin'
    client_id: '[id]'
    scope: 'email profile'
  }, (response) ->
    if response and !response.error
      # google authentication succeed, now post data to server and handle data  securely
      jQuery.ajax
        type: 'POST'
        url: '/auth/google_oauth2/callback'
        dataType: 'json'
        data: response
        success: (json) ->
          # response from server
          [this doesn't happen]

          return
    else
      # google authentication failed

I haven't seen this error described anywhere in Google's documentation, so I'm not quite sure how to fix it.

I'm sure that I have the same protocol https, so it must be something else. I'm guessing the domain.

I've seen that other sites (like Stack-overflow) uses a different flow where a popup isn't displayed but the user instead gets redirected to another page. Wonder if that could be a solution (and / or) the recommended way to do it to avoid my error.

In that case, where can I find documentation for this in Google's documentation jungle?

Update

This is the relevant parts of my controller code.

def google_authentication

  respond_to do |format|

    code = params[:code]

    unless code.blank?

      [..]

      load = { code: code, client_id: client_id, client_secret: client_secret, grant_type: grant_type, redirect_uri: redirect_uri }
      url = "https://www.googleapis.com/oauth2/v3/token/"
      response = HTTParty.post(url, :query => load, headers: { "content-type" => "application/x-www-form-urlencoded"})
      json = JSON.parse(response.body)

      unless json.nil?

        unless json["error"].present?

          [..]

          email = decoded_hash["email"]
          user = User.find_by_email(email)

          if user
            sign_in_existing_user(user)
            format.json { render :json => {:status => "Success", user_id: "# {user.id}"} }
          else
            # Create user
            [..]
            format.html { redirect_to current_user, notice: "Welcome #{current_user.name}!" }
            format.json { render :json => {:status => "Success", user_id: "#{current_user.id}"} }

          end
        else
          #format.html { redirect_to root_path, error: "Could not sign up / in" }
          format.json { head :no_content }
        end
      end
    end
  end
end

I have updated my JS based on the answer below by @EugZol, to:

data: {access_token: response['access_token'], error: response['error'], expires_in: response['expires_in']}

And I'm currently getting the following error:

Started POST "/auth/google_oauth2/callback" [..]
Processing by UsersController#google_authentication as JSON
Parameters: {"expires_in"=>"86400", "provider"=>"google_oauth2"}
Completed 406 Not Acceptable in 1ms (ActiveRecord: 0.0ms)

ActionController::UnknownFormat (ActionController::UnknownFormat):
app/controllers/users_controller.rb:237:in `google_authentication'

Solution

  • The only thing I could assume that here, when you are trying to make request to your own server:

    data: response
    

    ...your code is accessing in an indirect way a frame created by GAPI. I.e., response object references in one of its internal fields that frame, and jQuery is trying to serialize that.

    The solution is to pick required fields manually:

    data: {
      state: response['state'],
      code: response['code'],
      scope: response['scope'],
      client_id: response['client_id'],
      g_user_cookie_policy: response['g_user_cookie_policy']
    }