Google Service Account authorize() returns invalid_grant error - google-api-ruby-client

I've carefully reviewed Steve Bazyl's presentation at https://www.youtube.com/watch?v=iK14bfd6qhs and relevant API docs on google. I'm using the Service Account email id for my gmail account, and am using the private key downloaded from the Console.
But when I run the test client modeled after the one Steve showed in his presentation I consistently get
Signet::AuthorizationError:
Authorization failed. Server message:
{
"error" : "invalid_grant"
}
I get the same error message if I add garbage letters to the email or scope passed to JWTAsserter. Clearly something wrong but I can't seem to figure out how to troubleshoot this.
Here's the client code I'm running (in a rails rspec file):
client = Google::APIClient.new
key_file = '/Users/stu/projects/br/rails-app/######-privatekey.p12'
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, 'notasecret')
Rails.logger.info "Private key? #{key.private?}"
asserter = Google::APIClient::JWTAsserter.new(
'#####-#######knp#developer.gserviceaccount.com',
"https://www.googleapis.com/auth/calendar",
key)
client.authorization = asserter.authorize()
I'm pretty well stuck, would definitely appreciate any troubleshooting advice.
Thanks!
Update
Thanks for sharing code that works for you Jack.
I've gone to my site's dev console and created a service account client p12 key. I then went to the site's Admin Console and added my client id granting site-wide authorization to the calendar API
In the Admin Console after adding the authorization it looks like this:
XXXXXXXXXXXhnq.apps.googleusercontent.com Calendar (Read-Write) https://www.googleapis.com/auth/calendar
I downloaded the p12 key and used it in the code structure you provided. I also tried with the approach from Steve Bazyl's presentation:
asserter = Google::APIClient::JWTAsserter.new(
"XXXXXXXXXXX-hnq#developer.gserviceaccount.com",
"https://www.googleapis.com/auth/calendar",
key)
client.authorization = asserter.authorize("stu#XXXXXXXXXX.com")
In both cases I get the same output as before:
Signet::AuthorizationError:
Authorization failed. Server message:
{
"error" : "invalid_grant"
}
I get that same output if I type in junk instead of "XXXXs://www.googleapis.com/auth/calendar". The key is valid, and while it's clear I'm doing something wrong, I can't find any clues in the API or google about how to tell what it is.
Any ideas how to troubleshoot?

Have you given service account access to your Google Apps account? You can find out how to that here: https://developers.google.com/+/domains/authentication/delegation#delegate_domain-wide_authority_to_your_service_account
Following code works for me:
key = Google::APIClient::KeyUtils.load_from_pkcs12('tmp/##########-privatekey.p12', 'notasecret')
client = Google::APIClient.new({:application_name => "example-app", :application_version => "1.0"})
client.authorization = Signet::OAuth2::Client.new(
:person => 'name#example.com',
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/drive.readonly',
:issuer => '123456789#developer.gserviceaccount.com',
:signing_key => key)
client.authorization.fetch_access_token!
drive = client.discovered_api('drive', 'v2')
result = client.execute(api_method: drive.files.list)

There is a long thread regarding this on GitHub which seems to indicate the problem is related to the system's date and time being "a little off." The suggested solution is to run sudo ntpdate ntp.ubuntu.com.
The thread is closed but it does have a link to this information from Google about "Invalid Grant".

Never was able to figure out how to get past the invalid_grant error.
My solution was to get a refresh token using the normal webapp api. Once retrieved it seems to be usable indefinitely so the end result appears to be the same.

Related

Google API Client Library for Pub/Sub

I am trying to get Pub/Sub set up for my Google Account and am running into a snag that doesn't really make much sense.
I have my "Google Cloud Pub/Sub" turned on per this: https://cloud.google.com/pubsub/prereqs
Then, I was able to generate a "service account" with associated JSON credentials file and use it to create an API client with an access token using the "Ruby Google API Client." This all worked fine.
Then, when I try to hit the API to create a new Pub/Sub Topic as follows:
pubsub = client.discovered_api('pubsub', 'v1')
results = client.execute!(
:api_method => pubsub.projects.topics.create,
:parameters => { :name => 'projects/myprojectid/topics/gmail-push-notifications') }
)
and I get: "Google::APIClient::ClientError: Not Found" from Google.
Now, I know I am hitting the right API with credentials, because if I omit the step to get an access token then I get a 403 with the same call, and I also know that the format is correct since I get back the following if I mess with the "name" parameter:
"ArgumentError: Parameter 'name' has an invalid value: project/myprojectid/topics/gmail-push-notifications. Must match: /^^projects/[^/]/topics/[^/]$$/."
I have checked my Project ID from the Google Developer console a hundred times to make sure it's correct, and it is, yet it still says not found! Any ideas here?
This name is invalid:
project/myprojectid/topics/gmail-push-notifications
It should be:
projects/myprojectid/topics/gmail-push-notifications
The first word is the plural projects, not project.

Google Developer API keys & YouTube API

I started a web based dev project using YouTube and when I signed up for the API all I received was a client ID and email ID. However in the Ruby example text for calling the data API it states:
def initialize(scope)
credentials = Google::APIClient::ClientSecrets.load
#authorization = Signet::OAuth2::Client.new(
:authorization_uri => credentials.authorization_uri,
:token_credential_uri => credentials.token_credential_uri,
:client_id => credentials.client_id,
:client_secret => credentials.client_secret,
:redirect_uri => credentials.redirect_uris.first,
:scope => scope
I don't know where to go to grab these credentials. Ideas?
Since YouTube is now Google-account-based, these credentials need to be acquired on the Google Developers Console (pick the YouTube Data API), and you'll have to initiate the normal Google sign-in process. Your client_id and client_secret are generated when you create a new application on the console, your redirect_uri is set based on your application structure.
While Signet has a nice OAuth2 module associated with it, I've found Omniauth to be very easy to work with (and you can still use it in conjunction with Signet). If you don't need to customize the sign in process, Omniauth puts most of this in a black box (it's very high-touch), and handles token refreshes, etc. for you. I might use the Omniauth Google strategy to get your tokens, then the Google Ruby client to make requests.

Integrating Google Plus with Rails

I already have the login (OAuth) piece working with my app, what I am trying to do now is pull down the authenticated users' activity list (status feed, for example).
The user has the option to pull this list down after the fact of them being authenticated and here is my code, thus far:
# User Model
def gplus
auth = authorizations.find_by_provider("gplus")
client = Google::APIClient.new(application_name: "AppName", application_version: '1.0', authorization: nil)
plus = client.discovered_api('plus', 'v1')
result = client.execute(
key: API["gplus"][Rails.env]["secret"],
api_method: plus.people.get,
parameters: { 'collection' => 'public', 'userId' => 'me' }
)
return result.data
end
Here is the problem I keep running into (from rails console)
#<Google::APIClient::Schema::Plus::V1::Person:0x3fe649ed04bc
DATA:{"error"=>{"errors"=>[{"domain"=>"usageLimits", "reason"=>"keyInvalid", "message"=>"Bad Request"}], "code"=>400, "message"=>"Bad Request"}}>
I am using the https://github.com/google/google-api-ruby-client... any reason why this won't work?
Code is close, but not quite there!
You're getting the auth object, but not actually passing it to your client there (you're setting it to nil).
You seem to be passing your client secret as the API key, which will cause problems. They API key is for a "simple API access" key from the API console - you don't need to pass anything if you're using an oAuth 2.0 token. If you'd like, you can pass a Server simple API key. This actually catches incorrectly using access tokens for a different project, so can be handy, but isn't required.
You don't need to specify a collection argument for plus.people.get
Additionally, make sure that the Google+ API is enabled under Services in the API console: http://developers.google.com/+

Using ruby google-api-client to get youtube video data

Bonjour !
I have seen a lot of similar questions, but can't figure out why none of them brings a clear answer.
I'm trying to use the google-api-client gem to connect to youtube data api to retrieve a thumbnail image from a youtube video link.
I assume I don't need to deal with that (obscure to me) oAuth authentication.
First step : I create the project with google api console, authorizing the Youtube Data API. In the "API Access" tab, I select "Create a client ID..." with option "Service Account".
It gave me a client ID, email address and public key fingerprints.
Second : I create my ruby script using the google-api-client gem. I just copy/paste the example in the documentation that is :
require 'google/api_client'
client = Google::APIClient.new
From my client object I thought I could directly call
client.execute()
to get my atom feed with all my video information. But I just have an idea how to do it.
Maybe I also have to get oAuth authorization ? To get the token ?
What am I missing ?
Many thanks for your contributions.
That wasn't so hard, I found out how to do it.
You don't need to deal with oAuth authentication in that case. You will need to generate a server API Key and send it on each request.
The following snippet will help retrieving all information about a video the API provides.
require 'google/api_client'
require 'json'
client = Google::APIClient.new
youtube = client.discovered_api('youtube', 'v3')
client.authorization = nil
result = client.execute :key => "<YOUR_SERVER_API_KEY>", :api_method => youtube.videos.list, :parameters => {:id => '<YOUR_VIDEO_ID>', :part => 'snippet'}
result = JSON.parse(result.data.to_json)

how to authorizate use google-api-ruby-client

i am using google api for ruby, but not know how to start, just give me an ABC example someone, thanks very much?
If you are creating a Service Account application to access Google Analytics.
Register it with Google through https://code.google.com/apis/console.
On API Access tab, click Create client ID, choose Service Account. Store the key file Google will generate, and remember the password for that key.
Here is some code to get you started
require 'rubygems'
require 'google/api_client'
api_client = Google::APIClient.new
path_to_key_file ="/path/to/key/file-privatekey.p12"
passphrase = "google_generated_password"
key = Google::APIClient::PKCS12.load_key(path_to_key_file, passphrase)
Once a key is available, initialize the asserter with your client ID (email in APIs console)
and authorization scopes.
asserter = Google::APIClient::JWTAsserter.new(
'super_long_client_id_from_api_console#developer.gserviceaccount.com',
'https://www.googleapis.com/auth/analytics.readonly',
key)
# To request an access token, call authorize:
api_client.authorization = asserter.authorize()
puts api_client.authorization.access_token
http://code.google.com/p/google-api-ruby-client/wiki/ServiceAccounts
I've answered something similar in a couple of other posts I found that were like this one... so incase its relevant, for ruby, using the google-api-client (for any of the google apis), there's a few ins and outs with authentication when using an api key as opposed to OAuth...
I've outlined this process (using an api key server side) at the code abode.
You have to explicitly set the authorzation param to nil when constructing the client, otherwise the gem tries to use OAuth to authenticate, so if calling from a server using an api key only, you will always get a 401 Unauthorized.
the code abode - google-api-client for ruby

Resources