Use token received from JavaScript client to create a social connection on the server - access-token

I have a webpage using PHP/JavaScript that can successfully do the Google Signin process on the client side. I would like to then forward the obtained token to the backend which uses Spring-Social to create a social connection and be able to call on the APIs to which the app has been authorized.
I managed to do exactly this with both Facebook (OAUTH2) and Twitter (OAUTH1) both working flawlessly but for Google I always get a 401 response when trying to create the connection.
The code in the server is like this:
AccessGrant accessGrant = new AccessGrant(accessToken);
connection = ((OAuth2ConnectionFactory<?>) connectionFactory).createConnection(accessGrant);
The code in the client something like this:
function onSignIn(googleUser) {
var profile = googleUser.getBasicProfile();
console.log('ID: ' + profile.getId());
console.log('Name: ' + profile.getName());
console.log('Email: ' + profile.getEmail());
var access_token = googleUser.getAuthResponse().id_token;
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost/signin/google');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
console.log('Signed in as: ' + xhr.responseText);
console.log('access_token=' + access_token);
};
xhr.send('access_token=' + access_token);
}
Most of the latter is copied from the Google documentation on how to authenticate with a backend server found here: https://developers.google.com/identity/sign-in/web/backend-auth
I've traced the calls inside spring-social-google and they lead to a REST call to https://www.googleapis.com/oauth2/v2/userinfo which is the one that replies 401 and the following JSON:
{
"error": {
"errors": [{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}],
"code": 401,
"message": "Invalid Credentials"
}
}
I've tried to call on this same REST API myself and I also get the same response, so it does not seem to be an issue of spring-social-google not sending the token properly as I've read on some other posts.
I believe that the issue is somehow related to the fact that the JavaScript API is giving me an "id_token" and the REST API is expecting an "access_token". However, I am not finding a way to obtain the access token as something separate from the id_token and the Google documentation does send the id_token to the backend. There is an "access_token" property alongside the used "id_token" property in googleUser.getAuthResponse() but it is coming back undefined.
This doc is of course not aimed at spring-social so I am not saying it is incorrect, I am just left wondering what is the proper way to achieve this.
Is spring-social at fault by not being able to deal with the id_token?
Am I at fault not seeing some clear way to get the access_token?
Anyway, I feel like I am somehow close but still the solution seems well out of grasp.
Thank you!

Well, I found it. It seems that writing the whole thing down really tripped something in my brain...
The issue is in fact that the id_token cannot take the place of the access_token. To my taste, Google documentation could be a little more explicit about this in the example they show but they do end up explaining it in the actual client reference at https://developers.google.com/identity/sign-in/web/reference
The key is the boolean parameter to getAuthResponse() which is optional and defaults to false. From the doc:
includeAuthorizationData Optional: A boolean that specifies whether
to always return an access token and scopes. By default, the access
token and requested scopes are not returned when fetch_basic_profile
is true (the default value) and no additional scopes are requested.
By setting this to true in my JS the access_token field which was previously undefined was populated and with this token one can access the /oauth2/v2/userinfo endpoint and spring-social-google can correctly create a connection just like the other providers.
I hope this helps someone else at least.

Related

Zoho desk invalid oauth

I am testing out Zoho desk API. I have set up a dummy client in zoho console dashboard and using the self client option to generate a oauth_token. However when I try to use it, it always return an error code 401 with error code "INVALID_OAUTH".
I have checked for characters, and using it before even one minute of generation. Can somebody please help me with this?
{
url = "https://desk.zoho.in/api/v1/organizations"
payload = ""
headers = {
'Authorization': "Zoho-oauthtoken 1000.XXXXXabdbd925112
4ff.44d3eccd1e1a03bd25db1016f0f13322"
}
}
Response
{
"errorCode": "INVALID_OAUTH",
"message": "The OAuth Token you provided is invalid."
}

How come I keep getting a "Request failed with response code 401" when trying to push via Urban Airship?

I have double, triple, and quadruple checked that I have the right master key that I'm passing. My parameters are taking directly from the UA website also so it can't be that. Anyone see what I'm doing wrong here???
Parse.Cloud.define("sendPush", function(request, response) {
var Buffer = require('buffer').Buffer;
var parameters = {
"audience" : "all",
"device_types" : "all",
"notification" : {
"alert" : "Hello from Urban Airship."
}
};
var params = JSON.stringify(parameters);
Parse.Cloud.httpRequest({
url: "https://go.urbanairship.com/api/push/",
method: 'POST',
headers: {
"Content-Type" : "application/json",
"Authorization" : 'Basic ' + new Buffer('MASTER_KEY').toString('base64'),
"Accept" : "application/vnd.urbanairship+json; version=3;"
},
body: params,
success: function(httpResponse) {
response.error(httpResponse);
},
error: function(httpResponse) {
response.error('Request failed with response code ' + httpResponse.status);
}
});
});
I've also tried adding in APP_SECRET:
"Authorization" : 'Basic ' + new Buffer('APP_SECRET':'MASTER_KEY').toString('base64'),
It's not clear from your code sample if you are including the app key in your request. API requests to Urban Airship use HTTP basic authentication. The username portion is the application key and the password portion in this case is the master secret. The application secret is restricted to lower-security APIs and is for use in the distributed application. The master secret is needed for sending messages and other server API requests.
Urban Airship provides a guide for troubleshooting common API issues.
I had the same problem and tried to figure it out by Network diagnosing tools for more than two days. Because after debugging I checked that I send the right credentials to UA. After all I called the UA and ask them to check the Credentials (in my case was appKey and appToken for streaming with java-connect API) if they are still valid. They checked and approved the validation but just in case sent me a new credentials. And I could connect with the new credentials!
It is for sure a bug by UA because I tested the whole time by another test application, which was a Desktop java application and I could connect to the server (with the same appKey and appToken) and get the events, but I got 401 error in my main Application, which was a Web Application running on TomCat 8.0 . It means It worked in a same time in with the same credential for one application and did not work for another application.

Google OAuth: can't get refresh token with authorization code

I am using Google API Client for Google Analytics with OAuth 2.0
I read this to get the refresh token but it doesn't appears: https://developers.google.com/identity/protocols/OAuth2WebServer#offline
I only get this instead:
{
"access_token": "ya29.twHFi4LsiF-AITwzCpupMmie-fljyTIzD9lG8y_OYUdEGKSDL7vD8LPKIqdzRwvoQAWd",
"token_type": "Bearer",
"expires_in": 3599,
"id_token": "very long string"
}
Here is the code:
Javascript (to get the Authorization Code): that works
gapi.analytics.ready(function() {
gapi.analytics.auth.authorize({
container: 'embed-api-auth-container',
clientid: '257497260674-ji56vq885ohe9cdr1j6u0bcpr6hu7rde.apps.googleusercontent.com',
});
gapi.analytics.auth.on('success', function(response) {
var code = response.code;
$.ajax({
url: "getTokensFromCode.php",
method: "GET",
data: {
"code": code
},
success: function (tokens) {
// I can't get refresh token here, only get "access_token" and "id_token"
console.log(tokens);
}
});
});
});
PHP (to exchange Authorization Code for tokens): that doesn't work
// I get the authorization code in Javascript
$code = $_GET['code'];
$redirectURI = "postmessage";
$client = new Google_Client();
$client->setClientId($clientID);
$client->setClientSecret($clientSecret);
$client->setRedirectUri($redirectURI);
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$client->authenticate($code);
$tokens = $client->getAccessToken();
echo $tokens;
I need the refresh token in Javascript, that's why I get the authorization code in Javascript and make an Ajax request to get the refresh token.
You will only get the refresh_token the very first time that a user grants access to your app. You'll need to store the refresh_token somewhere to be able to use it afterwards. You won't get a new refresh token the next time a user logs in to your app.
FWIW: using a refresh token in a Javascript client doesn't make a lot of sense since a Javascript client can't store it in a safe (confidential) way. Since Javascript clients live in browsers and users are present in browsers, you can just redirect the browser to the authorization endpoint again and you'll get a new access token (which is also the purpose of a refresh token). The SSO session for the user at Google will make sure that the user doesn't need to login again and that the experience is seamless.
Check out this response by #curious_coder
Google API Refresh Token
He explains a way to get the Refresh Token every time :) Life saver. This post helped me get there so I figured I'd share this for any one else trying to find their refresh token. Add the last two lines of this code to your Auth process
$client = new Google_Client();
$client->setAuthConfigFile(__DIR__ . '/client_secrets.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/dashboard/oauthcallbacks');
$client->addScope(Google_Service_Analytics::ANALYTICS_READONLY);
$client->setAccessType('offline'); //To fetch refresh token(Refresh token issued only first time)
$client->setApprovalPrompt('force'); //Adding this will force for refresh token
Also I used var_dump($tokens) to get the contents instead of echo. Dont remember if it makes a difference in PHP but $tokens will be an array of objects.

YouTube Retrieve a Refresh Token?

I am sending a delete request to the youtube api but I am receiving a 401 error (unauthorized). I'm not sure why. My key is set properly, I am able to access the analytics of the youtube channel. This is my code that fires on a button click
jQuery.ajax({
type: 'DELETE',
// must set api key
url: 'https://www.googleapis.com/youtube/v3/videos?id='+ thisUniqueID + '&key={<?php echo $oAuth2Key; ?>}',
});
I've used alert to check that my auth key is set properly (shown below).
alert('<?php echo $oAuth2Key; ?>');
and I can see in the returned address with the error that the url is proper. What could be the issue?
It looks like I need a refresh token. This is straight out of the docs: The API will return an HTTP 401 response code (Unauthorized) if you submit a request to access a protected resource with an expired access token. The following section explains how to refresh an access token.
Is there an easy way to retrieve a refresh token at the same time that I send a delete request? If not is there an easy way to retrieve one with out the need for the client id/client secret etc.
I somehow have gotten a key for analytics, but when I go to delete a video the key is not valid.
I would suggest you to use Data API v3 instead.
Yes, you can do AJAX calls. Here's the videos->delete call.
DELETE https://www.googleapis.com/youtube/v3/videos?id=VIDEO_ID&key={YOUR_API_KEY}
You find the documentation for using authorization at:
https://developers.google.com/youtube/v3/guides/authentication
You use the API key for access to public data !
Since you want to delete a video, you must use the access_token. An access_token is valid for a short time (1 hour). You can get a new one by using your refresh_token to request another one.
Store a refresh_token since it is valid until it gets revoked.
BTW.
Maybe use client.js, to handle the authorization for your requests ?
For JS, by adding:
<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
The general documentation is at:
https://developers.google.com/api-client-library/javascript/start/start-js
An code example for YouTube is at:
https://developers.google.com/youtube/v3/code_samples/javascript
For reference of the video delete method see:
https://developers.google.com/youtube/v3/docs/#videos
The listed methods are: insert, list, delete, update , rate and getRating.
The delete method might be (This is NOT tested with a valid videoID):
var requestOptions = {
id: '012345678901', // replace VIDEOID
part: 'id'
};
var request = gapi.client.youtube.videos.delete (requestOptions);
request.execute(function(response) {
console.log("RESPONSE: " + response);
});
The response using a non-existing videoId is:
[
{
"error": {
"code": -32500,
"message": "Video not found",
"data": [
{
"domain": "youtube.video",
"reason": "videoNotFound",
"message": "Video not found",
"locationType": "parameter",
"location": "id"
}
]
},
"id": "gapiRpc"
}
]

invalid_grant trying to get oAuth token from google

I keep getting an invalid_grant error on trying to get an oAuth token from Google to connect to their contacts api. All the information is correct and I have tripple checked this so kind of stumped.
Does anyone know what may be causing this issue? I have tried setting up a different client id for it but I get the same result, I have tried connecting many different ways including trying the force authentication, but still the same result.
Although this is an old question, it seems like many still encounter it - we spent days on end tracking this down ourselves.
In the OAuth2 spec, "invalid_grant" is sort of a catch-all for all errors related to invalid/expired/revoked tokens (auth grant or refresh token).
For us, the problem was two-fold:
User has actively revoked access to our app
Makes sense, but get this: 12 hours after revocation, Google stops sending the error message in their response:
“error_description” : “Token has been revoked.”
It's rather misleading because you'll assume that the error message is there at all times which is not the case. You can check whether your app still has access at the apps permission page.
User has reset/recovered their Google password
In December 2015, Google changed their default behaviour so that password resets for non-Google Apps users would automatically revoke all the user's apps refresh tokens. On revocation, the error message follows the same rule as the case before, so you'll only get the "error_description" in the first 12 hours. There doesn't seem to be any way of knowing whether the user manually revoked access (intentful) or it happened because of a password reset (side-effect).
Apart from those, there's a myriad of other potential causes that could trigger the error:
Server clock/time is out of sync
Not authorized for offline access
Throttled by Google
Using expired refresh tokens
User has been inactive for 6 months
Use service worker email instead of client ID
Too many access tokens in short time
Client SDK might be outdated
Incorrect/incomplete refresh token
I've written a short article summarizing each item with some debugging guidance to help find the culprit.
I ran into this same problem despite specifying the "offline" access_type in my request as per bonkydog's answer. Long story short I found that the solution described here worked for me:
https://groups.google.com/forum/#!topic/google-analytics-data-export-api/4uNaJtquxCs
In essence, when you add an OAuth2 Client in your Google API's console Google will give you a "Client ID" and an "Email address" (assuming you select "webapp" as your client type). And despite Google's misleading naming conventions, they expect you to send the "Email address" as the value of the client_id parameter when you access their OAuth2 API's.
This applies when calling both of these URL's:
https://accounts.google.com/o/oauth2/auth
https://accounts.google.com/o/oauth2/token
Note that the call to the first URL will succeed if you call it with your "Client ID" instead of your "Email address". However using the code returned from that request will not work when attempting to get a bearer token from the second URL. Instead you will get an 'Error 400' and an "invalid_grant" message.
I ran into this problem when I didn't explicitly request "offline" access when sending the user to the OAuth "Do you want to give this app permission to touch your stuff?" page.
Make sure you specify access_type=offline in your request.
Details here: https://developers.google.com/accounts/docs/OAuth2WebServer#offline
(Also: I think Google added this restriction in late 2011. If you have old tokens from before then, you'll need to send your users to the permission page to authorize offline use.)
If you're testing this out in postman / insomnia and are just trying to get it working, hint: the server auth code (code parameter) is only good once. Meaning if you stuff up any of the other parameters in the request and get back a 400, you'll need to use a new server auth code or you'll just get another 400.
I encountered the same problem. For me, I fixed this by using Email Address (the string that ends with ...#developer.gserviceaccount.com) instead of Client ID for client_id parameter value. The naming set by Google is confusing here.
We tried so many things, and in the end the issue was that the client had turned
"Less Secure App Access" off in their Google Account settings.
To turn this on:
Go to https://myaccount.google.com/ and manage account
Go to the Security tab
Turn Less secure app access on
I hope this saves someone some time!
My issue was that I used this URL:
https://accounts.google.com/o/oauth2/token
When I should have used this URL:
https://www.googleapis.com/oauth2/v4/token
This was testing a service account which wanted offline access to the Storage engine.
This is a silly answer, but the problem for me was that I failed to realize I already had been issued an active oAuth token for my google user which I failed to store. The solution in this case is to go to the api console and reset the client secret.
There are numerous other answers on SO to this effect for example
Reset Client Secret OAuth2 - Do clients need to re-grant access?
Using a Android clientId (no client_secret) I was getting the following error response:
{
"error": "invalid_grant",
"error_description": "Missing code verifier."
}
I cannot find any documentation for the field 'code_verifier' but I discovered if you set it to equal values in both the authorization and token requests it will remove this error. I'm not sure what the intended value should be or if it should be secure. It has some minimum length (16? characters) but I found setting to null also works.
I am using AppAuth for the authorization request in my Android client which has a setCodeVerifier() function.
AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
serviceConfiguration,
provider.getClientId(),
ResponseTypeValues.CODE,
provider.getRedirectUri()
)
.setScope(provider.getScope())
.setCodeVerifier(null)
.build();
Here is an example token request in node:
request.post(
'https://www.googleapis.com/oauth2/v4/token',
{ form: {
'code': '4/xxxxxxxxxxxxxxxxxxxx',
'code_verifier': null,
'client_id': 'xxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
'client_secret': null,
'redirect_uri': 'com.domain.app:/oauth2redirect',
'grant_type': 'authorization_code'
} },
function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log('Success!');
} else {
console.log(response.statusCode + ' ' + error);
}
console.log(body);
}
);
I tested and this works with both https://www.googleapis.com/oauth2/v4/token and https://accounts.google.com/o/oauth2/token.
If you are using GoogleAuthorizationCodeTokenRequest instead:
final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
TRANSPORT,
JSON_FACTORY,
getClientId(),
getClientSecret(),
code,
redirectUrl
);
req.set("code_verifier", null);
GoogleTokenResponse response = req.execute();
There are two major reasons for invalid_grant error which you have to take care prior to the POST request for Refresh Token and Access Token.
Request header must contain "content-type: application/x-www-form-urlencoded"
Your request payload should be url encoded Form Data, don't send as json object.
RFC 6749 OAuth 2.0 defined invalid_grant as:
The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
I found another good article, here you will find many other reasons for this error.
https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35
Solved by removing all Authorized redirect URIs in Google console for the project.
I use server side flow when you use 'postmessage' as redirect URI
I had the same error message 'invalid_grant' and it was because the
authResult['code']
send from client side javascript was not received correctly on the server.
Try to output it back from the server to see if it is correct and not an empty string.
Try change your url for requst to
https://www.googleapis.com/oauth2/v4/token
You might have to remove a stale/invalid OAuth response.
Credit: node.js google oauth2 sample stopped working invalid_grant
Note: An OAuth response will also become invalid if the password used in the initial authorization has been changed.
If in a bash environment, you can use the following to remove the stale response:
rm /Users/<username>/.credentials/<authorization.json>
The code you obtain in the URL after user consent has a very short expiry. Please obtain the code again and attempt to get access token within seconds (you have to hurry) and it should work. I can't find out the expiry period of code but it's literally very short.
In my case it was a callback URL that was different from the original request.
So, callback URL should be the same for auth request and code exchange.
if you are using scribe library, for example to set up the offline mode, like bonkydog suggested.
here is the code:
OAuthService service = new ServiceBuilder().provider(Google2Api.class).apiKey(clientId).apiSecret(apiSecret)
.callback(callbackUrl).scope(SCOPE).offline(true)
.build();
https://github.com/codolutions/scribe-java/
I my case I just didn't read the documentation properly because I was trying to do const { tokens } = await oauth2Client.getToken(accessToken);
every time to get an authorized client instance but for the subsequent requests you only need to include the refresh_token you store after the first user auth.
oauth2Client.setCredentials({
refresh_token: `STORED_REFRESH_TOKEN`
});
This can happen if your redirect_url is not the same as the one you have when creating the token on Google Gloud. So make sure it's correct
in this site
console.developers.google.com
this console board select your project input the oath url.
the oauth callback url will redirect when the oauth success
After considering and trying all of the other ways here, here's how I solved the issue in nodejs with the googleapis module in conjunction with the request module, which I used to fetch the tokens instead of the provided getToken() method:
const request = require('request');
//SETUP GOOGLE AUTH
var google = require('googleapis');
const oAuthConfigs = rootRequire('config/oAuthConfig')
const googleOAuthConfigs = oAuthConfigs.google
//for google OAuth: https://github.com/google/google-api-nodejs-client
var OAuth2 = google.auth.OAuth2;
var googleOAuth2Client = new OAuth2(
process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId,
process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret,
process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl);
/* generate a url that asks permissions for Google+ and Google Calendar scopes
https://developers.google.com/identity/protocols/googlescopes#monitoringv3*/
var googleOAuth2ClientScopes = [
'https://www.googleapis.com/auth/plus.me',
'https://www.googleapis.com/auth/userinfo.email'
];
var googleOAuth2ClientRedirectURL = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl;
var googleOAuth2ClientAuthUrl = googleOAuth2Client.generateAuthUrl({
access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
scope: googleOAuth2ClientScopes // If you only need one scope you can pass it as string
});
//AFTER SETUP, THE FOLLOWING IS FOR OBTAINING TOKENS FROM THE AUTHCODE
const ci = process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId
const cs = process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret
const ru = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl
var oauth2Client = new OAuth2(ci, cs, ru);
var hostUrl = "https://www.googleapis.com";
hostUrl += '/oauth2/v4/token?code=' + authCode + '&client_id=' + ci + '&client_secret=' + cs + '&redirect_uri=' + ru + '&grant_type=authorization_code',
request.post({url: hostUrl}, function optionalCallback(err, httpResponse, data) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if(!err) {
//SUCCESS! We got the tokens
const tokens = JSON.parse(data)
oauth2Client.setCredentials(tokens);
//AUTHENTICATED PROCEED AS DESIRED.
googlePlus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
// handle err and response
if(!err) {
res.status(200).json(response);
} else {
console.error("/google/exchange 1", err.message);
handleError(res, err.message, "Failed to retrieve google person");
}
});
} else {
console.log("/google/exchange 2", err.message);
handleError(res, err.message, "Failed to get access tokens", err.code);
}
});
I simply use request to make the api request via HTTP as described here:
https://developers.google.com/identity/protocols/OAuth2WebServer#offline
POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded
code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code
For future folks... I read many articles and blogs but had luck with solution below...
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientId,
clientSecret,
authCode,
"") //Redirect Url
.setScopes(scopes)
.setGrantType("authorization_code")
.execute();
This blog depicts different cases in which "invalid_grant" error comes.
Enjoy!!!
for me I had to make sure that the redirect_uri is an exact match to the one in the developer console Authorised redirect URIs, that fixed it for me, I was able to debug and know what exactly was the issue after switching from
https://accounts.google.com/o/oauth2/token to https://www.googleapis.com/oauth2/v4/token
I got a proper error:
{"error": "redirect_uri_mismatch", "error_description": "Bad Request"}
I had this problem after enabling a new service API on the Google console and trying to use the previously made credentials.
To fix the problem, I had to go back to the credential page, clicking on the credential name, and clicking "Save" again. After that, I could authenticate just fine.
In my case, the issue was in my code. Mistakenly I've tried to initiate client 2 times with the same tokens. If none of the answers above helped make sure you do not generate 2 instances of the client.
My code before the fix:
def gc_service
oauth_client = Signet::OAuth2::Client.new(client_options)
oauth_client.code = params[:code]
response = oauth_client.fetch_access_token!
session[:authorization] = response
oauth_client.update!(session[:authorization])
gc_service = Google::Apis::CalendarV3::CalendarService.new
gc_service.authorization = oauth_client
gc_service
end
primary_calendar_id = gc_service.list_calendar_lists.items.select(&:primary).first.id
gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)
as soon as I change it to (use only one instance):
#gc_service = gc_service
primary_calendar_id = #gc_service.list_calendar_lists.items.select(&:primary).first.id
#gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)
it fixed my issues with grant type.
For me the issues was I had multiple clients in my project and I am pretty sure this is perfectly alright, but I deleted all the client for that project and created a new one and all started working for me ( Got this idea fro WP_SMTP plugin help support forum) I am not able to find out that link for reference
If you are sanitizing user input (For example, $_GET["code"] in php) Make sure you don't accidentally replace something in the code.
The regex I am using is now /[^A-Za-z0-9\/-]/
Look at this https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988
First you need an access_token:
$code = $_GET['code'];
$clientid = "xxxxxxx.apps.googleusercontent.com";
$clientsecret = "xxxxxxxxxxxxxxxxxxxxx";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&code=".urlencode($code)."&grant_type=authorization_code&redirect_uri=". urlencode("https://yourdomain.com"));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);
$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$refresh_token = $server_output->refresh_token;
$expires_in = $server_output->expires_in;
Safe the Access Token and the Refresh Token and the expire_in, in a Database. The Access Token expires after $expires_in seconds. Than you need to grab a new Access Token (and safe it in the Database) with the following Request:
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&refresh_token=".urlencode($refresh_token)."&grant_type=refresh_token");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);
$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$expires_in = $server_output->expires_in;
Bear in Mind to add the redirect_uri Domain to your Domains in your Google Console: https://console.cloud.google.com/apis/credentials in the Tab "OAuth 2.0-Client-IDs". There you find also your Client-ID and Client-Secret.
There is a undocumented timeout between when you first redirect the user to the google authentication page (and get back a code), and when you take the returned code and post it to the token url. It works fine for me with the actual google supplied client_id as opposed to an "undocumented email address". I just needed to start the process again.
For me, this was caused by subsequent getToken calls with the same code.
Namely, in NestJS my callback endpoint was decorated with #UseGuards(AuthGuard('google')) and I tried to call getToken in the callback.

Resources