OAuth2 Google Service Account invalid_grant - php

The keys I've posted here are from a container I've deleted, but were all valid keys that have been provided to me by Google.
I'm attempting to implement: https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtReference/management/uploads/uploadData
The uploadData function works fine, and everything is accepted in Google Analytics. My problem lies with Google's OAuth2: https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization
From what I've gathered, for the end-point I wish to hit, I must use an OAuth2 token, and cannot use an api key. The request doesn't accept ?key={api}, and only Bearer Authorization. Using the Service Account request, all I receive is invalid_grant. I've updated my clocks and does various miss-matches of values to no avail. Mind you, with similar keys, all other workflows work, but I don't wish to have user interaction as this is a background task.
json for key data (provided by Google):
{
"private_key_id": "825119b6ab0eabf2029a4e1cf562fa88090736a0",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANCQ+tGWdTUOL6py\nhk/KGK/ClNFQnzRrzPvOgeHCqENqeunN5LJYBlrf0OOmRzJjV67WZc3cHKu95kYr\nI+Sz0NlsmPYiwP2eMUKL5HX2JEXx/T8Bf7SWK78G7BnPKxA1fKISSftJ1IJ9neH5\nqhe4zEIB2NUcc6h3GHqBoQx4/4/dAgMBAAECgYEAsegpe2RrQEGEmVEtjpwmaK6D\nQPUTiKS36sdhdREVdMQ8anmtrg92BEhMqBNrQekJn2LU3j/22OyYo5wi9vAHohPI\nKYODw6mUemk/ULyuMGesC7nRq9sM7YnJk3KlkYrtLVR9THwAPfZ73k4UswsGFw4e\naCX6SwtNnQTHruCvCAECQQD8ZkxRf2LdP0LZYrqcB0TD2P1rYeX+IHW5sC6mdDjQ\nv6HWXjviEBfQH6kaxpUvRaSHTk1p2a5pHOjVu9DdkGXdAkEA04qc+nXH6xkBf4yE\nLODzUuAMo/QU1C+SC9AS1WbfAuRyRCkuD0SNTbK8Ec+pkqy/Q6VuvjLvvTosB9+O\nVhIyAQJASYY3RypXj2HFRHQZLiiD5JVKRUSwbdXg1WW4QS7r+gtIxpyOzyym8y61\n4SHmBW5BHlU2AdayktYkEVbz4gcVVQJBAI9JOZEwzEyDMI+btz/K0yYUmptHTgB3\nhF45/zfLKU2FPZzLo+Y1kdzKLzeFSKAQILGKUdvFFrw+tepTU88bHgECQAlp4/sy\nJ2m+zo5HsGBRP4gSxoVqiPuysT9tywJoUeo/3f+0jkDbVylTKTHpnqNk2ijFd1YS\n5ARPrKY4iXG7UoU\u003d\n-----END PRIVATE KEY-----\n",
"client_email": "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu#developer.gserviceaccount.com",
"client_id": "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu.apps.googleusercontent.com",
"type": "service_account"
}
The other 2 associated tags to create {Base64url encoded header}.{Base64url encoded claim set}.{Base64url encoded signature} are:
$header = [
'alg' => 'RS256',
'typ' => 'JWT'
];
$body = [
"iss" => "42064665633-fbbnb79350js2h22e8k1s3h9t52rursu.apps.googleusercontent.com",
"scope" => "https:\/\/www.googleapis.com\/auth\/analytics https:\/\/www.googleapis.com\/auth\/analytics.edit",
"aud" => "https:\/\/accounts.google.com\/o\/oauth2\/token",
"exp" => strtotime('1 hour'),
"iat" => strtotime('now')
];
$header = base64_encode(json_encode($header));
$body = base64_encode(json_encode($body));
The signature is defined as the private_key in the google docs link above. I've tried every excerpt of information from Java questions pertaining to this method of authentication, from escaping slashes (shown above), to omitting fields.
What have I done wrong/left out that causes {"error" : "invalid_grant"}?

I am familiar with oauth2 for objective c but here is my best shot at handling this:
invalid_grant trying to get oAuth token from google
Your internal clock may be out of sync with the google clock or your refresh token may be refreshed too much.
Make sure your client key and secret are up to date, if not revoke them and get new ones.
In PHP, make sure you are using the POST request.
Source: https://developers.google.com/analytics/devguides/reporting/core/v2/gdataAuthentication

While not a proper solution to getting JWT Service Accounts to work, I'm now storing a refresh token for the web route. Everything is working well, it's just a lot of information I need to keep in my config.yml file that I'd rather not.
https://developers.google.com/accounts/docs/OAuth2WebServer#refresh

Related

How do I implement RS512 or higher security algorithm in PHP REST website?

I am a completely new person to cryptography. I have tried HS256, RS256 but they got cracked. Where can I find RS512 or higher security algorithm tutorial for PHP REST?
I tried with documentation of
https://github.com/firebase/php-jwt there is documentation for RS256. but token generated using that one gets cracked here https://base64decode.org. I have tried with longer length keys as well.
The I tried with https://github.com/web-token/jwt-framework There is description for components but hardly I could understand about how to use it.
$token = array(
"iss" => $iss,
"aud" => $aud,
"iat" => $iat,
"nbf" => $nbf,
"exp" => $exp,
"data" => array(
"egfield1" => $user->egfield1,
"egfield2" => $user->egfield2,
"egfield3" => $user->egfield3,
"egfield4" => $user->egfield4
)
);
//example private
$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC8kGa1pSjbSYZVebtTRBLxBz5H4i2p/llLCrEeQhta5kaQu/Rn
vuER4W8oDH3+3iuIYW4VQAzyqFpwuzjkDI+17t5t0tyazyZ8JXw+KgXTxldMPEL9
5+qVhgXvwtihXC1c5oGbRlEDvDF6Sa53rcFVsYJ4ehde/zUxo6UvS7UrBQIDAQAB
AoGAb/MXV46XxCFRxNuB8LyAtmLDgi/xRnTAlMHjSACddwkyKem8//8eZtw9fzxz
bWZ/1/doQOuHBGYZU8aDzzj59FZ78dyzNFoF91hbvZKkg+6wGyd/LrGVEB+Xre0J
Nil0GReM2AHDNZUYRv+HYJPIOrB0CRczLQsgFJ8K6aAD6F0CQQDzbpjYdx10qgK1
cP59UHiHjPZYC0loEsk7s+hUmT3QHerAQJMZWC11Qrn2N+ybwwNblDKv+s5qgMQ5
5tNoQ9IfAkEAxkyffU6ythpg/H0Ixe1I2rd0GbF05biIzO/i77Det3n4YsJVlDck
ZkcvY3SK2iRIL4c9yY6hlIhs+K9wXTtGWwJBAO9Dskl48mO7woPR9uD22jDpNSwe
k90OMepTjzSvlhjbfuPN1IdhqvSJTDychRwn1kIJ7LQZgQ8fVz9OCFZ/6qMCQGOb
qaGwHmUK6xzpUbbacnYrIM6nLSkXgOAwv7XXCojvY614ILTK3iXiLBOxPu5Eu13k
eUz9sHyD6vkgZzjtxXECQAkp4Xerf5TGfQXGXhxIX52yH+N2LtujCdkQZjXAsGdm
B2zNzvrlgRmgBrklMTrMYgm1NPcW+bRLGcwgW2PTvNM=
-----END RSA PRIVATE KEY-----
EOD;
$jwt = JWT::encode($token, $privateKey, 'RS256');
//example token RS256 generated
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJkZGQuaW4iLCJhdWQiOiJubm4uY29tIiwiaWF0IjoxNTQ4NTA0MDQzLCJuYmYiOjE1NDg1MDQwNDMsImV4cCI6MTU0ODU5MDQ0MywiZGF0YSI6eyJibGFoIGJsYWggYmxhaCI6ImJsYWggYmxhaCBibGFoIn19.Z9PdtP0ziezMxEpVBvgdsLBKndcy1fHDe5I2ypxvd2BEXSXxifLw2zJ3o3bcVZ5MogBaBbZyJOIxHA5M2XnLah90e48wVo5rXlG13edPCnPy4yt6onSfNO86Jbimr-JmQqDiN2oCeFBVCmqWu0wGxHZsyiOfp1dkBuyCJNz6mi0
issue is that it does get decoded on that site without any key
I am not able to understand am I doing wrong here or the algorithms are crackable or something. Please guide. Thank you
I think you have some concepts mixed up. JWT's, or better known as JSON Web Tokens, are built on the cryptology concept of message authentication code (MAC).
MAC gives the ability to confirm that the message came from the stated sender. It does not 'encrypt' the data thus you being able to see the direct contents of the message.
Base64 is a mutual translator for any datatype, JWT will use this encoding so you can put objects, arrays or whatever data you like inside of these tokens. Only, it ensures that no modification has been made to it.
For example, user A steals user B's JWT. He wants to access more data for more period of time so he tries to alter the exp property, he cannot resign the JWT with the MAC since he does not know the secret (in your case, the $privateKey).
When he then sends the new JWT to your server, it will throw a InvalidSignatureError which you can catch with your try catch finally blocks.
This can throw multiple errors, like expired token, so I'd suggest using catch(Exception $e) and then working with what type of error it was.
If you're looking to encrypt the contents (although it defeats the point of using JWT) you can use LibSodium, or a container I made for making it easier.
If you do not want to encrypt the $token, then do not put sensitive items inside of the token.

Browser uploads to s3 with instance roles

This is a follow-up on my previous question regarding policy document signing using instance profiles.
I'm developing a system that allows drag & drop uploads directly to an S3 bucket; an AJAX request is first made to my server containing the file metadata. Once verified, my server responds with the form parameters that are used to complete the upload.
The process of setting up browser based uploads is well explained here and it all works as expected in my local test environment.
However, once my application gets deployed on an EC2 instance, I'm seeing this error when the browser attempts to upload the file:
<Error>
<Code>InvalidAccessKeyId</Code>
<Message>The AWS Access Key Id you provided does not exist in our records.</Message>
<RequestId>...</RequestId>
<HostId>...</HostId>
<AWSAccessKeyId>ASIAxxxyyyzzz</AWSAccessKeyId>
</Error>
The value of ASIAxxxyyyzzz here comes from the instance role credentials, as obtained from the metadata service; it seems that those credentials can't be used outside of EC2 to facilitate browser based uploads.
I've looked at the Security Token Service as well to generate another set of temporary credentials by doing this:
$token = $sts->assumeRole(array(
'RoleArn' => 'arn:aws:iam::xyz:role/mydomain.com',
'RoleSessionName' => 'uploader',
));
$credentials = new Credentials($token['Credentials']['AccessKeyId'], $token['Credentials']['SecretAccessKey']);
The call givens me a new set of credentials, but it give the same error as above when I use it.
I hope that someone has done this before and can tell me what stupid thing I've missed out :)
The AWS docs are very confusing on this, but I suspect that you need to include the x-amz-security-token parameter in the S3 upload POST request and that its value matches the SessionToken you get from STS ($token['Credentials']['SessionToken']).
STS temporary credentials are only valid when you include the corresponding security token.
The AWS documentation for the POST request states that:
Each request that uses Amazon DevPay requires two x-amz-security-token
form fields: one for the product token and one for the user token.
but that parameter is also used outside of DevPay, to pass the STS token and you would only need to pass it once in the form fields.
As pointed out by dcro's answer, the session token needs to be passed to the service you're using when you use temporary credentials. The official documentation mentions the x-amz-security-token field, but seems to suggest it's only used for DevPay; this is probably because DevPay uses the same type of temporary credentials and therefore requires the session security token.
2013-10-16: Amazon has updated their documentation to make this more obvious.
As it turned out, it's not even required to use STS at all; the credentials received by the metadata service come with such a session token as well. This token is automatically passed for you when the SDK is used together with temporary credentials, but in this case the final request is made by the browser and thus needs to be passed explicitly.
The below is my working code:
$credentials = Credentials::factory();
$signer = new S3Signature();
$policy = new AwsUploadPolicy(new DateTime('+1 hour', new DateTimeZone('UTC')));
$policy->setBucket('upload.mydomain.com');
$policy->setACL($policy::ACL_PUBLIC_READ);
$policy->setKey('uploads/test.jpg');
$policy->setContentType('image/jpeg');
$policy->setContentLength(5034);
$fields = array(
'AWSAccessKeyId' => $credentials->getAccessKeyId(),
'key' => $path,
'Content-Type' => $type,
'acl' => $policy::ACL_PUBLIC_READ,
'policy' => $policy,
);
if ($credentials->getSecurityToken()) {
// pass security token
$fields['x-amz-security-token'] = $credentials->getSecurityToken();
$policy->setSecurityToken($credentials->getSecurityToken());
}
$fields['signature'] = $signer->signString($policy, $credentials);
I'm using a helper class to build the policy, called AwsUploadPolicy; at the time of writing it's not complete, but it may help others with a similar problem.
Permissions were the last problem; my code sets the ACL to public-read and doing so requires the additional s3:PutObjectAcl permission.
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Sid": "Stmt1379546195000",
"Resource": [
"arn:aws:s3:::upload.mydomain.com/uploads/*"
],
"Effect": "Allow"
}
]
}

How do I use Google's "Simple API Access key" to access Google Calendar info (PHP)?

I'm trying to use the Google API v3 to access one google calendar and according to the documentation here : http://code.google.com/apis/calendar/v3/using.html#intro and here : https://code.google.com/apis/console/, the solution I need is the "Simple API Access" & "Key for server apps (with IP locking)".
Now, when I create a page with this code :
session_start();
require_once 'fnc/google-api-php-client/src/apiClient.php';
require_once 'fnc/google-api-php-client/src/contrib/apiCalendarService.php';
$apiClient = new apiClient();
$apiClient->setUseObjects(true);
$service = new apiCalendarService($apiClient);
if (isset($_SESSION['oauth_access_token'])) {$apiClient->setAccessToken($_SESSION['oauth_access_token']);
} else {
$token = $apiClient->authenticate();
$_SESSION['oauth_access_token'] = $token;
}
and in my "config.php" file I add ONLY my developper key (in place of the "X") :
global $apiConfig;
$apiConfig = array(
// True if objects should be returned by the service classes.
// False if associative arrays should be returned (default behavior).
'use_objects' => false,
// The application_name is included in the User-Agent HTTP header.
'application_name' => '',
// OAuth2 Settings, you can get these keys at https://code.google.com/apis/console
'oauth2_client_id' => '',
'oauth2_client_secret' => '',
'oauth2_redirect_uri' => '',
// The developer key, you get this at https://code.google.com/apis/console
'developer_key' => 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
// OAuth1 Settings.
// If you're using the apiOAuth auth class, it will use these values for the oauth consumer key and secret.
// See http://code.google.com/apis/accounts/docs/RegistrationForWebAppsAuto.html for info on how to obtain those
'oauth_consumer_key' => 'anonymous',
'oauth_consumer_secret' => 'anonymous',
But then I get errors and it tells me it's trying to authenticate using the "OAuth 2.0" system which I don't want to use. I only want to access one calendar with an API key.
And amazingly, when I search in google "Simple API Access key" I find nothing, nothing on their docs, no examples, no tutorials, nothing. Am I the only one using this thing?
So can someone tell me what I'm doing wrong?
(i know this is an old question but i would've been glad if someone
gave a real answer here so i'm doing it now)
I came on the same problem, Simple API access is not well documented (or maybe just not where i searched), but using the Google API Explorer i found a way to get what i need, which is in fact pretty straightforward. You don't need specific lib or anything : it's actually really simple.
In my case i simply needed to search a keyword on G+, so i just had to do a GET request:
https://www.googleapis.com/plus/v1/activities?query={KEYWORD}&key={YOUR_API_KEY}
Now, for a calendar access (see here), let's pretend we want to fetch access control rules list. We need to refer to calendar.acl.list which give us the URI :
https://www.googleapis.com/calendar/v3/calendars/{CALENDAR_ID}/acl?key={YOUR_API_KEY}
Fill in the blanks, and that's pretty much all you need to do. Get a server key (API Access submenu), store it somewhere in your project and call it within URIs you're requesting.
You cannot access your calendar information using API Key. API keys (or simple API acess key) are not authorized tokens and can only be used for some API calls such as a Google search query etc; API keys will not let you access any user specific data, which I am assuming is your objective through this calendar application.
Also, from what I see in your code, you are creating a client object which is going to use OAuth 2.0 authentication and hence you are getting authentication error messages.
There is no such a thing called Simple API Access key.
Normally OAuth 2.0 is used for authorization. But since you have your reason not to use it.
If you want to use OAuth1.0 for authorization. You need an API key in Simple API Access section on the API Access page.
If you want to use username & password login instead of OAuth, you can refer to ClientLogin, but this is not recommanded.
I got to this thread when trying to do the same today. Although this is way late, but the answer is YES, there is actually simple API key for those apis that does not need user authorizations, and the official client library support this.
The api library do this by Options, which is key, value pair.
Take the example of get information of a given youtube video, you would use this api: https://godoc.org/google.golang.org/api/youtube/v3#VideosListCall.Do
To use api key, simply make a type that implements the CallOption interface, and let it return the api key:
type APIKey struct {
}
func (k *APIKey) Get() (string, string) {
return "key", "YOU API KEY HERE"
}
Then when calling the API, supply the APIKey to it:
youtube, err := youtube.New(&http.Client{})
call := youtube.Videos.List("snippet,contentDetails,statistics").Id(id)
rsp, err := call.Do(opt)
This way, you can construct the youtube client with the vallina http client, rather than oauth client, and enjoy the simple api key.
The first answer said you can use http GET directly, but then you will need to handle the errors and parse the result yourself.
See below link which is helpfull to you. The Google API Client Library enables you to work with Google APIs such as Analytics, Adsense, Google+, Calendar, Moderator, Tasks, or Latitude on your server, in the language of your choice.
http://code.google.com/p/google-api-php-client/
Thanks,
Chintu

Get access token and secret using OpenID+OAuth (google)

I'm using OAuth with Federated Login (Hybrid Protocol) to allow my users to login once using openID (which works great), and authenticate with the Google Data API at the same time.
The Zend_GData library was giving me a headache, so on the suggestion of someone here on SO I switched to LightOpenID.
The openID part works great, and thanks to this tip I'm able to add the OAuth extension and receive a response like this:
http://www.example.com/checkauth
?openid.ns=http://specs.openid.net/auth/2.0
&openid.mode=id_res
&openid.op_endpoint=https://www.google.com/accounts/o8/ud
&openid.response_nonce=2009-01-17T00:11:20ZlJAgbeXUfSSSEA
&openid.return_to=http://www.example.com/checkauth
&openid.assoc_handle=AOQobUeYs1o3pBTDPsLFdA9AcGKlH
&openid.signed=op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle,ns.ext2,ext2.consumer,ext2.scope,ext2.request_token
&openid.sig=oPvRr++f6%2ul/2ah2nOTg=
&openid.identity=https://www.google.com/accounts/o8/id/id=AItOawl27F2M92ry4jTdjiVx06tuFNA
&openid.claimed_id=https://www.google.com/accounts/o8/id/id=AItOawl27F2M92ry4jTdjiVx06tuFNA
&openid.ns.oauth=http://specs.openid.net/extensions/oauth/1.0
&openid.oauth.scope=http://docs.google.com/feeds/+http://spreadsheets.google.com/feeds/+http://sandbox.gmodules.com/api/
&openid.oauth.request_token=1/fVr0jVubFA83GjYUA
According to the documentation, openid.oauth.request_token is an authorized request token, so it seems I don't need to do the OAuthAuthorizeToken request. All is good so far, but now I need to exchange this request token for an access token and token secret.
Since I have no idea how to generate the OAuth nonce and signatures, I employ the OAuthSimple library for php. Problem is, the code required looks like this:
// Build the request-URL...
$result = $oauth->sign(array(
'path' => 'https://www.google.com/accounts/OAuthGetAccessToken',
'parameters' => array(
'oauth_verifier' => $_GET['oauth_verifier'],
'oauth_token' => $_GET['oauth_token']),
'signatures' => $signatures));
It needs an oauth_verifier value, which you would receive along with the request token with a normal OAuth request. I'm not sure if it's the OAuthSimple library that errors out when trying to omit that or if it's a Google requirement, but it doesn't work.
SO, can someone spot something I'm doing wrong here?
There's nothing wrong. Actually, when you do an Hybrid process, the verifier isn't send, simply because you don't need it.
So in your code, just set verifier to NULL, and it should work just fine, as long as there's no problems somewhere else.

Invalid OAuth Signature returned while using Yammer Api

I am trying to write a small webapp that pulls data from Yammer. I have to go through Yammer's OAuth bridge to access their data. I tried using the Oauth php library and do the 3 way handshake. But at the last step, I get an error stating I have an invalid OAuth Signature.
Here are the series of steps:
The first part involves getting the request Token URL and these are the query parameters that I pass.
[oauth_version] => 1.0
[oauth_nonce] => 4e495b6a5864f5a0a51fecbca9bf3c4b
[oauth_timestamp] => 1256105827
[oauth_consumer_key] => my_consumer_key
[oauth_signature_method] => HMAC-SHA1
[oauth_signature] => FML2eacPNH6HIGxJXnhwQUHPeOY=
Once this step is complete, I get the request Token as follows:
[oauth_token] => 6aMcbRK5wMqHgZQsdfsd
[oauth_token_secret] => ro8AJxZ67sUDoiOTk8sl4V3js0uyof1uPJVB14asdfs
[oauth_callback_confirmed] => true
I then try to authorize the given token and token secret by passing the parameters to the authorize url.It takes me to Yammer's authentication page where I have allow my app to talk to Yammer.
Yammer then gives me a 4 digit code that I have to put back into my application which then tries to acquire the permanent access token. I pass the following information to the access token URL:
[oauth_version] => 1.0
[oauth_nonce] => 52b22495ecd9eba277c1ce6b97b00fdc
[oauth_timestamp] => 1256106815
[oauth_consumer_key] => myconsumerkey
[callback_token] => 61A7
[oauth_token] => 6aMcbRK5wMqHgZQsdfsd
[oauth_token_secret] => ro8AJxZ67sUDoiOTk8sl4V3js0uyof1uPJVB14asdfs
[oauth_callback_confirmed] => true
[oauth_signature_method] => HMAC-SHA1
[oauth_signature] => V9YcMDq2rP7OiZTK1k5kb/otMzA=
Here I am supposed to receive the Oauth Permanent access token, but instead I get a Invalid Oauth signature. I dont know what I am doing wrong. I use the same signaures to sign the request. Should I sign the request using the new token and secret? I tried that as well but to no avail. I even tried implementing this in java using signpost library and got stuck at the exact same place. Help Help!!
The callback_token was something Yammer introduced in response to an OAuth security advisory earlier this year. When OAuth 1.0a was released, it was instead named oauth_verifier. However, it's not unlikely that Yammer still supports their workaround but rename it and try again to be sure.
Also, the below is information from the Yammer Development Network yesterday:
Tomorrow we will be releasing some
changes to the Yammer API to
facilitate user network switching on
API clients. Most of the change is in
the OAuth Access Tokens call which
allows you to generate pre-authorized
OAuth access tokens for a given user.
One token will be generated for each
network they are in and your clients
switch networks by sending an API
request signed with the appropriate
token for that network.
I'm assuming that Yammer OAuth libraries might need to be updated per this change. I haven't taken a look at it yet.
Edit: My python-yammer-oauth library still works despite Yammer having changed things on their side.
Edit2: Could you try using signature method PLAINTEXT instead of HMAC-SHA1? I've had problems with Yammer and HMAC-SHA1.
I tried by using PLAINTEXT.. but for this method its giving me the same "Invalid OAuth signature" error even for requesting the token.
So is it possible to generate the access token we use HMAC-SHA1 and for accessing the actual API method i.e. for posting the message.. we use PLAINTEXT?
just found the problem!
I had forgotten to add an ampersand ("&") at the end of CONSUMER_SECRET. Perhaps this is your issue as well?

Categories