2017년 8월 14일 월요일

OAuth 2.0 400 - error:invalid_grant and ideas?


Apologies if I'm asking a dumb question, did search around and I struggled to find anything that matched. Please feel free to tell me to go search harder if I'm just being dumb....

I have a server app that needs to collect it's own analytics data from the API, and had planned to use the service-accounts scenario to collect the data... At first I tried to use the google libraries, but they seem to come with a world of dependency hell that I'd like to avoid if at all possible. Aside from that, I thought it would be good to try and understand the process from the ground up. 

I've had some limited success getting the JWT request together and signing it.. I *think* everything is as it should be, but when I make my first call the server always returns 400 : error:invalid_grant

I've tried generating a new key, but nothing seems to help, and I'm a bit stuck to know whats going wrong and why.

I've attached a .groovy test that demonstrates the problem if anyone is sufficiently motivated to take a look. To run it, you'll need to generate a new service account from the api console, download the key and then update the test file with your client_id and path to the downloaded .p12 file.

The test script should grab all the dependencies it needs. Hopefully someone out there will take a look and spot that I've just done something really dumb.


--
Typically...

After fighting with this for 14 odd hours, and finally posting here...
I go and fix it in 3 mins!

I thought it was just worth trying the email address from the API
console instead of the client-Id.. Hey presto, 200OK.

Hopefully this will help someone else. Google friends, can I make a
suggestion that https://developers.google.com/accounts/docs/OAuth2ServiceAccount
could use a small edit in under the "Forming the JWT Claim Set"
heading, "Required Claims" section. Specifically, the table listing
the parameters reads "iss - the client_id of the application making
the access token request". I think it should really read "the Email
address as taken from the API Console service account setting".. I'm
pretty sure it says to send client_id in a few other places, but that
page seems to be the authoritative one, and is linked from the API
console. Making it a bit clearer would have saved me a bit of pain.

--
Soooo funny.  I just spent the last two days on the same exact problem.  Yeah, very frustrating.

However, now all I get back is a 403 forbidden from the actual analytics service request. 

--
heh looking forward to that pain then :)

Please drop me a line if you sort it!

--
Yep.. same boat here and stuck again.. only getting 403 responses

--
I suppose it's not just me then.  *please* send me a note if you figure it out - I will do the same.

Also, I would be happy to use the "simple api access" method if I could find some docs on how to use that.  Are you familiar with that approach?

--
Can you post the detailed error response that you are getting ? 403
forbidden generally means that you don't have access to the profile
that you're requesting the data for. 

--
Here is the response body that I get back:


"{"error":{"errors":[{"message":"Forbidden"}],"code":403,"message":"Forbidden"}}"

* Successful OAuth2 token request - I get back an http status 200 with <access_token>
  -- with http header:  "Authorization:  Bearer <access_token>"
* Receive a 403 forbidden

I can look at the GA reports from the web interface for this profile and I believe I am an admin for this profile.

Also, I have run through the google php api client and I get the same response back using that.

Any ideas?

--
Just received a note from Nick - check his reply in my thread.

Bottom line from him is that the analytics service does not yet support OAuth "Server to Server" interactions.  He pointed towards a blog post which lists the services which support this type of interaction


Also note the suggestion in his response. 

--
I was able to get it all working.  First, you have to abandon any idea of getting a "server to server" authentication setup working against the analytics service - it doesn't support it (yet).  And, yes, it's very frustrating because everything I read lead me to believe that we should be using that - at least for my situation.  And, *nothing* I read ever gave me a sign that perhaps the analytics service did not support this authentication model yet.  <sigh>

Ok, so here is how I did it:

⦁ Go to Google APIs Console and make sure you have a "Client ID for web applications"
⦁ If you don't have that type of client ID, click "Create another Client ID..." button and create one
⦁ Go to OAuth 2.0 Playground  https://code.google.com/oauthplayground/
⦁ Click on the settings button in the top-right.
⦁ Click on "Use your own OAuth credentials"
⦁ Enter your OAuth client ID and client secret and close it
⦁ Go to step 1 and select "Analytics" and click "Authorize APIs"
⦁ Go through consent page
⦁ Go to step 2 and click "Exchange authorization code for tokens"
⦁ You should get an access token and a refresh token
⦁ Save this refresh token to a secure file which your groovy code can read 
⦁ Click on the "Refresh access token" button
⦁ Now you should see the POST on the right which gets a new access token using a refresh token
⦁ Write your groovy code which can perform that POST using the refresh token you stored in the file
⦁ You will get back an access code which you can use to perform queries against the analytics API
⦁ When it expires, you will need to obtain a new access code using the same technique

Here is the downside to this approach - first, this refresh token, and hence the queries, are now tied to the actual google user who gave the consent in the OAuth 2.0 playground.  secondly, I'm not sure about this, but I *think* that if you authorized again and got a new refresh token, the original refresh token might not work.  I would need to test this to be sure.

This worked for me and I hope it works for you. If / when the 'server to server' authentication is turned for analytics, I will switch over to that.

Let me know how it goes.

--
We're looking into supporting this.

In the mean time can you help me understand the use case on why you need 'server to server' authentication?

It seems like if you have GA today, you must have signed our TOS, and used our UI once. So how is going through the server side OAuth 2 flow once, (and storing / using refresh) tokens a less appealing option to access data though our API?

--
Hiya Nick, thanks for asking...

To be (brutally) honest, I think I've not explained myself very well..

the data API is completely fine for me.. it's just that the path I had taken through the documentation paints a very clear picture that older authentication mechanisms will be deprecated (In the very near future is the impression given). As this was starting out a new project, it seemed to only make sense to code it to the suggested api rather than the legacy one. If the old api isn't going away, I'll very happily re-code to use that. The docs certainly didn't make it obvious that OAuth2 isn't yet supported.

FWIW tho, I strongly believe that the documentation path devs are led through (from the sign-up / API screens) give the impression that OAuth2 is the only legitimate choice. So I'd suggest it's more a documentation bug than a feature request really.

Cheers, for following up, and many thanks to everyone else who has posted on this. I'll be having a go this weekend and will report back, but looks like it's sorted for now!

--
Wonderful!

thanks so much for this, you've saved me!

--
fixing docs is easy enough.

OAuth2 is both currently supported and our best practice to handle authorization.

Though notice there are 6 OAuth 2 flows documented: https://developers.google.com/accounts/docs/OAuth2

My question is why you need the Service Account flow vs the Web Server flow?

Web Server works fine today. Service Account is quite new and we need to add support in our API for it.

--
In essence, I have a server timer job that's kicked off once every 24 hours that I'd like to call into the api and get the stats from the previous day. Hence there's no browser involvement (There won't even be an attending user).. Whilst pasting the token from a previous browser session works around this, it just doesn't feel like the "right" flow to use for this case?

--
np, happy to hear it helped 

--
I have a similar use case to Ian.  We have a server job which runs periodically to get data for internal reports.

--
I have the same use case.. need to pull stats periodically from GA and
process them automatically on my server.  I didn't want to use the old
api because it will be deprecated eventually and this is a new project
so I want to make sure it is good for some time to come.  What is the
best way to stay updated on the status of GA s2s oauth2 support? 

--
I am facing 400 code as well... this is the response content...
apiHttpRequest Object
(
    [batchHeaders:apiHttpRequest:private] => Array
        (
            [Content-Type] => application/http
            [Content-Transfer-Encoding] => binary
            [MIME-Version] => 1.0
            [Content-Length] => 
        )

    [url:protected] => https://accounts.google.com/o/oauth2/token
    [requestMethod:protected] => POST
    [requestHeaders:protected] => Array
        (
            [content-type] => application/x-www-form-urlencoded
            [content-length] => 534
        )

    [postBody:protected] => grant_type=assertion&assertion_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fjwt%2F1.0%2Fbearer&assertion=quitealongstringtopaste
    [userAgent:protected] => Google Oauth2 Sample google-api-php-client/0.5.0
    [responseHttpCode:protected] => 400
    [responseHeaders:protected] => Array
        (
            [cache-control] => no-cache, no-store, max-age=0, must-revalidate
            [pragma] => no-cache
            [expires] => Fri, 01 Jan 1990 00:00:00 GMT
            [date] => Sun, 27 May 2012 21:52:03 GMT
            [content-type] => application/json
            [x-content-type-options] => nosniff
            [x-frame-options] => SAMEORIGIN
            [x-xss-protection] => 1; mode=block
            [server] => GSE
            [transfer-encoding] => chunked
        )

    [responseBody:protected] => {
  "error" : "invalid_grant"
}
    [accessKey] => 

my script uses the google-api-php-client lib provided by google, and is quite simple...

require_once("../_includes/config/header.php"); //contains the definitions of the following constants
require_once (G_API_DIR.'/src/apiClient.php');
require_once (G_API_DIR.'/src/contrib/apiOauth2Service.php');

$client = new apiClient();
$client->setApplicationName("Google Oauth2 Sample");

// Set your cached access token. Remember to replace $_SESSION with a real database or memcached.
if (isset($_SESSION['token'])) {
 $client->setAccessToken($_SESSION['token']);
}

$key = file_get_contents(G_KEY_FILE);

$cred = new apiAssertionCredentials(
  G_SERVICE_ACCOUNT_NAME,
  array(G_USER_PROFILE, G_USER_EMAIL),
  $key
);

print '<h2>JWT:</h2><pre>' . print_r($cred->generateAssertion(), true) . '</pre>';

$client->setAssertionCredentials($cred);

$client->setClientId(G_CLIENT_ID);
$service = new apiOauth2Service($client);

$result = $service->userinfo->get();
print '<h2>Oauth2 Result:</h2><pre>' . print_r($result, true) . '</pre>';

if ($client->getAccessToken()) {
  $_SESSION['token'] = $client->getAccessToken();
}

any thoughts?

--
Currenly, i have get

 "{"error":{"errors":[{"

message":"Forbidden"}],"code":403,"message":"Forbidden"}}"
when get data from google analytic api. I had create service account and I get access_token in Ruby successfully.
But i can't get data. It return above error.
How to get ids to attack on request: 

GET https://www.googleapis.com/analytics/v3/data/ga
  ?ids=ga:12345
  &start-date=2008-10-01
  &end-date=2008-10-31
  &metrics=ga:visits,ga:bounces

Ids is gotten from profile_id in google analytics
Any ideas.

--
If it helps anyone, i was getting a 400 error, until i changed "redirect_uri" in the POST request to be the same uri as the one i used as the "redirect_uri" parameter when i first retrieved the authorization token.

That fixed it for me. Might also help someone else :)

--
I'm facing an error 400 on the php api.
The strange thing, is that when i'm testing on my local computer it work perfectly an an other computer too, but on my server.
The only difference i've found for now is the return of openssl_pkey_get_private. wich give me something like: Resource id #34 on local but Resource id #33 on server.

Is there anyone who have faced this before?

--
I've print out the HttpRequest array, and they are exactly the same on both server and local computer. no package are missing or anything like that.
Everithing is going great, no errors until the response from the token server, who returned 400 invalid grant.


It's the just like the token server doesn't like my server ip adresse or something like that...

anyone have an idea of what i'm missing? 

--
We also have an identical use case, for periodically (not spamming your API!) downloading and storing GA data for further analysis.

It would be fantastic if you can support this in the GA API. :)

--
THANK YOU TANK YOU, A THOUSAND THANK YOU!!

I've been looking at the invalid request for two days until seeing your post.

--
I am having the same problem, when i try to connect against Google API in my local server, evrething goes fine, but when i try in my server on the web i can't do it. I have this problem a few days, did you solve the problem?

--
Is the redirect_url should absolutly be a https ?

--
No

--
PROBLEM SOLVED.

The time of my server was one minute in the future, compared with google's server time. So i put my server to update the time with ntpdate and now i don't have problem with invalid_grant anymore.

Hi, i had been same problem when i try to make request on webmaster tool and used token from OAuth2ServiceAccount and i got 403 code error.

is Webmaster tool supported in OAuth2ServiceAccount?

Hope anyone help me with this.

--
same error invalid_grant here .. it was the server time (was 5 min in the future compared to google time).

--

댓글 없음:

댓글 쓰기