Something that I would like to see in the README and if we can solve it here, I would gladly submit a pull request.
Given that a Web App has an expired access_token and a refresh_token, how does the client obtain a new access_token using that refresh token.
stored_user_credentials = {
refresh_token: 'TOKEN',
access_token: 'TOKEN',
expires_in: 3600,
issued_at: DateTime.parse('Fri, 15 Jan 2016 02:38:13 UTC +00:00')
}
client_secrets = GOOGLE_CALENDAR_CLIENT_SECRETS
client = Google::APIClient.new(application_name: GOOGLE_CALENDAR_APP)
calendar_api = client.discovered_api('calendar', 'v3')
client.authorization = client_secrets.to_authorization
client.authorization.update!(
scope: GOOGLE_CALENDAR_SCOPE,
additional_parameters: {
"access_type" => "offline",
"include_granted_scopes" => "true"
},
redirect_uri: GOOGLE_CALLBACK_URL
)
client.authorization.update_token!(stored_user_credentials)
#...
So far, I've not found an example of using the refresh token to obtain an access token anywhere. This setup step I've shown above works up until the access_token has expired and then no more. In my real app, stored_user_credentials is coming from an ActiveRecord model that caches these credentials.
Generally speaking, you don't need to do much to exchange the refresh token. So long as you're properly serializing/deserializing the oauth2 client to restore the refresh token, all that needs to be done is call fetch_access_token!() to refresh. In this example, that would have to be after you call update_token!() to restore the refresh token.
That said, see https://github.com/google/google-auth-library-ruby for more current authorization code. It's a wrapper around signet. that makes things a bit easier. I'll put together a better sample at some point in the near future too.
The offending configuration seems to have been the
additional_parameters: {
"access_type" => "offline",
"include_granted_scopes" => "true"
},
Throwing a binding.pry to investigate, lead me to discover that calling client.authorization.refresh! directly was generating a message:
Signet::AuthorizationError: Authorization failed. Server message:
{
"error" : "invalid_request",
"error_description" : "Parameter not allowed for this message type: access_type"
}
Removing those additional parameters seems to have lead to the client exchanging a refresh token for a new access token as expected in this instant example.
Thank you @sqrrrl. I'll be sure to take a look.
Ah, smallish bug in signet there.
For that error I'd suggest moving where you specify additional_parameters. Instead of setting them as properties on the client, just pass them in as options to authorization_uri() so they're only used in that context (which is the case anyway, they're just used when building the initial authorization uri)
Can someone point me to an example of how I can get a refresh token working?
this is what I've got in my controller currently:
class Calendars::EventsController < ApplicationController
def index
if current_user
client = ::Google::Apis::CalendarV3::CalendarService.new
client.key = '...'
client.authorization = {}
client.authorization[:access_token] = current_user.oauth_token
client.authorization[:refresh_token] = current_user.refresh_token if current_user.refresh_token
@events = client.list_events(params[:calendar_id])
end
end
end
Added a small sinatra web app in the samples dir that hopefully helps.
@lifeiscontent did you ever figure that out?
Most helpful comment
The offending configuration seems to have been the
Throwing a binding.pry to investigate, lead me to discover that calling
client.authorization.refresh!directly was generating a message:Signet::AuthorizationError: Authorization failed. Server message: { "error" : "invalid_request", "error_description" : "Parameter not allowed for this message type: access_type" }Removing those additional parameters seems to have lead to the client exchanging a refresh token for a new access token as expected in this instant example.