简体   繁体   中英

Rails 4 - Google One Time Code Flow with omniauth-google-oauth2, keep getting “Invalid code”

I have a Rails app in which I want users to be able to sign in / up with Google. I'm using the following gem:

#gemfile
gem 'omniauth-google-oauth2'

I've almost got it to work (I actually received the access token once, not sure why) - but before getting the access_token I constantly get the following error:

"error"=>"invalid_grant", "error_description"=>"Invalid code."

I have checked so that the code is unique for each request and that it isn't nil. The relevant parts of the method where I try to get the access_token after I've received the one time authorisation code looks like this:

def google_authentication
  respond_to do |format|
    # authorisation code

    code = params[:code]
    unless code.blank?

      client_id = ENV["GOOGLE_CLIENT_ID"]
      client_secret = ENV["GOOGLE_CLIENT_SECRET"]
      redirect_uri = 'postmessage'
      grant_type = 'authorization_code'

      load = {client_id: client_id, client_secret: client_secret,  redirect_uri: redirect_uri, grant_type: grant_type, code: code}
      payload = load.to_json
      url = "https://www.googleapis.com/oauth2/v3/token"
      response = HTTParty.post(url, :query => load)

      json = JSON.parse(response.body)

      unless json.nil?
        unless json[:error].present?
          # TODO: Handle data      
          format.json { render :json => {:message => "Success"} }              
        else
          # ERROR "Invalid code" always happen
        end
      end
    end
  end
end

In Google's developer console I have the following credentials:

Client ID           [CLient ID]
Email address       [Email]
Client secret       [Secret]
Redirect URIs       http://127.0.0.1:3000/
JavaScript origins  http://127.0.0.1:3000

Would be thankful for any ideas or tips.

Update for completion

This is how I set up omniauth-google-oauth2:

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"],  ENV["GOOGLE_CLIENT_SECRET"],
    {
      :scope => "email, profile",
      :prompt => "select_account",
      :provider_ignores_state => true
    }
end

Update 2

As mentioned above I once managed to get the access token once, I managed to reproduce it again. I did it by clicking my sign in button three times.The first time I got:

"error"=>"invalid_grant", "error_description"=>"Invalid code."

The second click resulted in:

"error"=>"invalid_grant", "error_description"=>"Code was already redeemed."

And the third time I successfully got the access_token . I find it really strange that I sometimes get the access_token , but most of the time get:

"error"=>"invalid_grant", "error_description"=>"Invalid code."

And the success / error "rate" isn't 100% consistent. Sometimes it takes more than three clicks. I find it quite strange that it sometimes works and that I sometimes get different error responses without changing anything in my code.

Could it be related to time / expiration date of the code?

Update 3

For additional completion. This is how my Javascript (or CoffeeScript) looks like when the user clicks the Google Sign in button:

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


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

        jQuery.ajax
          type: 'POST'
          url: '/auth/google_oauth2/callback'
          dataType: 'json'
          data: response
          success: (json) ->
            # response from server
            console.log "JSON: " + json
            return
      else
        # google authentication failed

I cant help with the ruby part but I may be able to help you figure out what's wrong.

There are in fact 3 codes returned by Google's authentication server. Authorization code, Access token, and refresh token.

  • Authorization code can only be used once to get the first refresh token.
  • Access Token used to access data on the apis, expires after an hour.
  • Refresh Token used to get a new access token when it expires. good until the user removes access.

Authorization code

This is the code that gets returned when the user hits accept to your application.

Example:

Change the client id, secret, and scope in this URI to the ones you are using. then paste it into a browser location bar.

https://accounts.google.com/o/oauth2/auth?client_id={clientid}.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope={scope}&response_type=code

在此处输入图片说明

It will prompt you for authentication. If you click except you get another window with a long code in it looking something like this.

在此处输入图片说明

That is the Authentication code, its only purpose in life is for you to use it to get an access token and a refresh token. It can only be used once , and its probably short lived though I have never tested how long they are good for.

In the uri above Note: the response type code.

Exchange:

Once you have that authentication code you need to exchange it for an access token and a refresh token. this is a HTTP POST so cant be placed in a browser window.

https://accounts.google.com/o/oauth2/token
code=4/X9lG6uWd8-MMJPElWggHZRzyFKtp.QubAT_P-GEwePvB8fYmgkJzntDnaiAI&client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code

Note: grant_type=authorization_code this tells the server you are sending it an authorization code.

response

{
"access_token" : "ya29.1.AADtN_VSBMC2Ga2lhxsTKjVQ_ROco8VbD6h01aj4PcKHLm6qvHbNtn-_BIzXMw",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/J-3zPA8XR1o_cXebV9sDKn_f5MTqaFhKFxH-3PUPiJ4"
}

You now have an access token that can be used to access the Google APIs, it is short live lasts only 3600 seconds or 1 hour. After that you must use the refresh token to get access again.

Use refreshtoken

https://accounts.google.com/o/oauth2/token
client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&refresh_token=1/ffYmfI0sjR54Ft9oupubLzrJhD1hZS5tWQcyAvNECCA&grant_type=refresh_token

response

{
"access_token" : "ya29.1.AADtN_XK16As2ZHlScqOxGtntIlevNcasMSPwGiE3pe5ANZfrmJTcsI3ZtAjv4sDrPDRnQ",
"token_type" : "Bearer",
"expires_in" : 3600
}

Now that you understand all of that

"error"=>"invalid_grant", "error_description"=>"Code was already redeemed."

means that you are sending the authorization code again you can only use it once you should be sending the refresh token again. There is something up with your authentication flow. Again sorry I cant help with the ruby part.

code ripped from Google 3 legged oauth2 flow

So ...as it is wrote:

Authorized redirect URIs One URI per line. Needs to have a protocol, no URL fragments, and no relative paths. Can't be a public IP Address.

Your setting:

Redirect URIs http://127.0.0.1:3000/ JavaScript origins http://127.0.0.1:3000/

...is wrong.

It should be:

Redirect URIs: http://my.true.domain/users/auth/google/callback

and

 provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"],  ENV["GOOGLE_CLIENT_SECRET"],
    {
      :scope => "email, profile",
      :name => "google",
      ...

I hope it help!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM