I have a problem with google api and oauth2 token.
There is an app which allows to synchronize contacts / calendar with your google account by oauth2 token.
When first time user wants to connect with his google account, he needs to grant access , then app is receiving code/token which is saved and will be used for offline synchronization later.
function getClient($app)
{
$client = new Google_Client();
$client->setAuthConfig("path_to_secret.json");
switch($app)
{
case 'contacts':
$client->addScope(Google_Service_Script::WWW_GOOGLE_COM_M8_FEEDS);
$client->addScope(Google_Service_People::USERINFO_EMAIL);
break;
case 'calendar':
$client->addScope(Google_Service_Calendar::CALENDAR);
break;
default:
throw new Exception('API Callback not defined in setup');
}
$client->setAccessType('offline'); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setRedirectUri(GOOGLE_APP_URL . $app.'/callback.php');
return $client;
}
(there are different tokens for contacts and calendar)
The synchronization script:
...
try
{
$client = getClient('calendar');
$client->setAccessToken(unserialize($accessToken));
$http = $client->authorize();
$service = new Google_Service_Calendar($client);
...
}
$accessToken is a serialized string like:
a:5:{s:12:"access_token";s:131:"******token_here********";s:10:"token_type";s:6:"Bearer";s:10:"expires_in";i:3598;s:8:"id_token";s:902:"***id_token****";s:7:"created";i:1505178047;}
This is working for first time and couple more times but after some time(hours) there is an error:
Error: {"error": { "errors": [ { "domain": "global", "reason": "authError", "message": "Invalid Credentials", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Invalid Credentials" }}
What I am doing wrong?
What is interesting that for contacts synchronization works fine all the time (access token has the same attributes as in calendar synchronization )
Ok, propably solved - refresh_token is provided for the first time only, so when I was testing it more times then I didn't get refresh token.
When I revoked access in https://myaccount.google.com/u/0/permissions and connected again then I received also refresh token. I assume now it will work properly
Related
I'm trying to create a Laravel API to interact with Gmail via the Google API client library for PHP.
However, I encounter issues with the endpoint to get mails.
So far, I could do authorization and get the access token in my DB. This endpoint return JSON of this format:
{
"user_id": 1,
"token": {
"access_token": "ya29.XXXXXXXXXXXX-K_P9Z0Rd0nU5WzSvU3TMlC0TZQRbMLkDHE1XI3j29mbIRP13dt_NGMb4d9trTECGKiwbjM45Ijk7fbhpLzU2JL7w-6w_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/gmail.readonly",
"token_type": "Bearer",
"id_token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-yK6GHPRATk64GaPcYCuIVyqTYNNvfvr8gCVuTCKr3RMtUz5J97ROZkJRN5w_AQvyJFL369MNTnHQAiqJoPIN2Wes0OAbeq1urpDRMRaAfF6Nuqun-pmewI8g5OCjkUMqekZTKlzCqRl7Xkm4qQRrwk66SxwGdC78Iy5Z_3VG1OIj681zoY18dQu9_ZqGMnwVuHmffmzNInuAmqHcQ7CLM_I_vJbWV3-UKVRF2UtjHvmUydCdo4PrEpL371i8exwPme5IK_xMcywxdfdjxm4duyv6X_ky2MCYwANNBSyBLMYh214FZPowL8choHmMIms-biJlg",
"created": 1647922921
},
"name": "gmail",
"updated_at": "2022-03-22T04:22:01.000000Z",
"created_at": "2022-03-22T04:22:01.000000Z",
"id": 10
}
My second endpoint is to get the mails by using the access token.
My implementation looks like this.
// Services
public function getEMails($user_id, $access_token){
$this->client->setAccessToken($access_token);
$service = new Gmail($this->client);
$messages = $service->users_messages->listUsersMessages($user_id);
return $messages;
}
// Controller
public function getMails(WebService $web_service, GmailServices $gmail_services){
$access_token = $web_service->token['access_token'];
$user_id = 'me';
if (isset($access_token)){
$mails = $gmail_services->getEMails($user_id, $access_token);
}
return $mails;
}
// api.php (In route middleware)
Route::get('/web-service/{mails}', [WebServiceController::class, 'getMails'])->name('web-service.getMails');
Hitting this endpoint, I get the below error.
"message": "Trying to access array offset on value of type null",
"exception": "ErrorException"
which corresponds to this line
$access_token = $web_service->token['access_token'];
I did some minor research, but I couldn't resolve it. Unfortunately, I'm not advanced in Laravel development, and I'll appreciate any help and feedback.
Furthermore, I equally want to add that my implementation is largely based on Bitfumes test-driven API development on YouTube. Here is the link to the repo https://github.com/bitfumes/laravel-test-driven-api
Thanks!
After trying the above, I was expecting to get the mails and equally work on pagination or querying via LIMIT so that the backend will not over-stress to get all mails at once.
Everything you do should be going though the Google_Client object. Your code is a little confusing to me you have two getEMails methods and neither appear to be using assigning things properly though the client.
When properly initialized it is able to handle everything for you.
$client = new Google_Client();
$client->setApplicationName('Gmail API PHP Quickstart');
$client->setScopes(Google_Service_Gmail::GMAIL_READONLY);
$client->setAuthConfig('credentials.json');
$client->setAccessType('offline');
$client->setPrompt('select_account consent');
You should be testing if the access token has expired. If it is expired then have the Google_Client request a new one.
if ($client->isAccessTokenExpired()) {
// Refresh the token if possible, else fetch a new one.
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
} else {
// request access of the user if it has not been granted.
}
// note after refresh remember to store the new refresh token.
}
For my online platform, I'm setting up a basic google sign-in button whereby users can create an account or log-in using their gmail email address.
I created my credentials: OAuth 2.0 Client IDs (got both ID and secret) and Service Accounts (now removed - not needed when asking access approval from user).
For the oauth consent screen, I've added my non-sensitive scopes:
./auth/userinfo.email
./auth/userinfo.profile
openid
I'm using my own gmail address for testing and API url used is https://www.googleapis.com/oauth2/v2/userinfo? with query on the email field.
EDIT: I've removed the service account so ignore this:
I have setup my jwt sign too with the service account email (XXX-googleconnect#genial-analyzer-341XXX...) and the (pretty damn long) generated key.
Could you please tell me how to debug the error "message":"Undefined index: jwt" when I run the google API?
EDIT: I've amended my code to reflect the removal of the service account. So no more jwt token: "jwt_bearer": "false",
<?php
$exports = <<<'JSON'
{
"name": "oauth",
"module": "oauth",
"action": "provider",
"options": {
"jwt_bearer": "false",
"service": "google",
"client_id": "307540412021-test06test06test06test06test06.apps.googleusercontent.com",
"client_secret": "XXXXX-X0RMDgT1nEgxAKrOmpcjK4sXY7Qq",
"tokenHandling": "self"
},
"meta": [
{
"name": "access_token",
"type": "text"
}
],
"outputType": "object"
}
JSON;
?>
But now I get "message":"Undefined index: false",
Disclaimer: I'm a no-coder, with basic php knowledge, pls if you could possibly use as easy to understand a language, that would be highly appreciated (and helpful for other no-coders too I bet)
Thanks in advance.
What you need to understand is that there is a big diffrence between Service account authoirzation, Oauth2, and signin.
If you run oauth2 authorization and request access of the using the email and profile scopes your going to have access to the users profile information.
if you try to run service account authentication and request the email and profile scopes your either going to get an error or your going to get the service accounts profile information. As a service account is a dummy user.
"message":"Undefined index: jwt"
Implies to me that you are using the wrong code for the wrong client.
service account client credentils
function getServiceAccountClient() {
try {
// Create and configure a new client object.
$client = new Google_Client();
$client->useApplicationDefaultCredentials();
$client->addScope([YOUR SCOPES HERE]);
return $client;
} catch (Exception $e) {
print "An error occurred: " . $e->getMessage();
}
}
Oauth2 client
function getGoogleClient() {
$client = getOauth2Client();
// Refresh the token if it's expired.
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
}
return $client;
}
/**
* Builds the Google client object.
* Documentation: https://developers.google.com/identity/protocols/OAuth2
* Scopes will need to be changed depending upon the API's being accessed.
* Example: array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
* List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
* #return A google client object.
*/
function buildClient(){
$client = new Google_Client();
$client->setAccessType("offline"); // offline access. Will result in a refresh token
$client->setIncludeGrantedScopes(true); // incremental auth
$client->setAuthConfig(__DIR__ . '/client_secrets.json');
$client->addScope([YOUR SCOPES HERE]);
$client->setRedirectUri(getRedirectUri());
return $client;
}
As you can see the code is completely different for each type of authorization.
My main issue is why are you even trying to mix service account authorization into this if you are signing users into your app?
Should you be using a Google service account?
I want to update the draft grade and assigned grade from Google Classroom using API. The following problem is seen When I test to update the draft grade and assigned grades using Try this API.
Problem:1
{
"error": {
"code": 403,
"message": "#ProjectPermissionDenied The Developer Console project is not permitted to make this request.",
"status": "PERMISSION_DENIED"
}
}
This error may be due to using an insufficient credential type.
Try using OAuth 2.0.
Localhost Code:
$client = getClient();
$service = new Google_Service_Classroom($client);
$courseId = '393351980716';
$courseWorkId = '393445838699';
$id = 'Cg0Iu5q5vHkQ657M2bkL';
$post_body = new Google_Service_Classroom_StudentSubmission(array(
'assignedGrade' => 10,
'draftGrade' => 90
));
$params = array(
'updateMask' => 'assignedGrade,draftGrade'
);
$list = $service->courses_courseWork_studentSubmissions->patch($courseId, $courseWorkId, $id, $post_body,$params);
Then when I run the above code on localhost I see problem-2:
Problem-2
Fatal error: Uncaught Google\Service\Exception: {
"error": {
"code": 403,
"message": "#ProjectPermissionDenied The Developer Console project is not pe
rmitted to make this request.",
"errors": [
{
"message": "#ProjectPermissionDenied The Developer Console project is no
t permitted to make this request.",
"domain": "global",
"reason": "forbidden"
}
],
"status": "PERMISSION_DENIED"
}
}
How to solve this problem?
According to the Classroom API documentation:
ProjectPermissionDenied indicates that the request attempted to modify a resource associated with a different Developer Console project.
Possible Action: Indicate that your application cannot make the desired request. It can only be made by the Developer Console project of the OAuth client ID that created the resource.
Therefore, if the resource you are trying to modify has been created manually for instance, this means that it is not associated with any developer project, hence the error you are receiving.
You will have to create these resources in the same project in order to be able to execute this request successfully.
Reference
Classroom API Access Errors.
For that API request your API key needs one of the following authorised scopes
Authorization Scopes
Requires one of the following OAuth scopes:
https://www.googleapis.com/auth/classroom.coursework.students
https://www.googleapis.com/auth/classroom.coursework.me
I need to start the google cloud instance and stop if my process is over.
So i tried api calls from https://cloud.google.com/compute/docs/reference/rest/v1/instances/get
Created API Key and oAuth client Id for the same and tried in postman application to test.
Used API Key in header Authorization : Bearer <api_key> and also in URL as key=<api_key>
But both methods are giving error 401 login required.
Then i found API Explorer
https://developers.google.com/apis-explorer/
There also i got same error.
What is the mistake I'm doing.
I need to implement instance start and stop through PHP code as it is background process.
PHP curl response
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
I think the easiest way to actually do this using env variable, as the google api php client library has a neat method.
require_once __DIR__ . '/vendor/autoload.php';
putenv('GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json');
$client = new Google_Client();
$client->setApplicationName('RandomNameYouNeedToInsert/0.1');
$client->addScope(array('https://www.googleapis.com/auth/compute'));
$client->useApplicationDefaultCredentials();
$service = new Google_Service_Compute($client);
// TODO: Update placeholder values.
project = 'my-project';
$zone = 'my-zone';
$instance = 'my-instance';
$response = $service->instances->start($project, $zone, $instance);
// TODO: Check if the response satisfies your request.
When I try to make a call to the Google Directory API using Server to Server authentication, I get the error message "Not Authorized to access this resource/api".
What I did:
Created an App in the Google Developers Console.
Downloaded the private key and looked up the service account name.
Activated the Admin SDK under APIs.
Downloaded the google-api-php-client.
Wrote the following code:
$serviceAccountName = 'XXXXXXXXXXX#developer.gserviceaccount.com';
$scopes = 'https://www.googleapis.com/auth/admin.directory.group';
$privateKeyFile = dirname(__FILE__).'/../certs/googleapi-privatekey.p12';
$client = new Google_Client();
$client->setApplicationName('API Project');
$client->setScopes($scopes);
$cred = new Google_Auth_AssertionCredentials($serviceAccountName, $scopes, file_get_contents($privateKeyFile));
$client->setAssertionCredentials($cred);
$client->getAuth()->refreshTokenWithAssertion();
$req = new Google_Http_Request("https://www.googleapis.com/admin/directory/v1/groups/group-id#example.com/members?maxResults=1000");
$val = $client->getAuth()->authenticatedRequest($req);
var_dump($client->getAuth()->getAccessToken());
var_dump($val->getResponseBody());
Executing that small script yields a valid access token, valid for an hour and the following error message:
{ "error": { "errors": [ { "domain": "global", "reason": "forbidden", "message": "Not Authorized to access this resource/api" } ], "code": 403, "message": "Not Authorized to access this resource/api" } }
I get the same error when I try to do the same request on the Google OAuth playground with the access key from my PHP script. Do I have to activate access to the group data for that service account somewhere in the Developers Console?
Beyond granting the service account client id access to the given scopes in your Google Apps Control Panel, you need to tell the service account to impersonate a super administrator user within your Google Apps domain:
$auth->sub = $adminEmail;
For some reason, the Admin SDK docs don't contain a PHP sample but there's sample code for instantiating a service account in the Google Drive docs.
I found by trial and error that removing "admin." from the scopes makes it work (in addition to everything said above about following these steps: https://developers.google.com/drive/web/delegation#delegate_domain-wide_authority_to_your_service_account ).
$cs = json_decode(file_get_contents(<MY SECRET PATH> . 'client_secrets.json'), true);
$cs = $cs['web'];
$cred = new Google_Auth_AssertionCredentials(
$cs['client_email'], //why do they call this "service account name" ? Misleading >:(
array(
'https://www.googleapis.com/auth/directory.user',
'https://www.googleapis.com/auth/directory.group',
'https://www.googleapis.com/auth/directory.group.member'
),
$key,
'notasecret',
'http://oauth.net/grant_type/jwt/1.0/bearer',
'<MY EMAIL IN THE DOMAIN>' //_my_ email as an user with admin rights
);