System.Net.Http.HttpRequestException: '404 (Not Found)' - xamarin

I used this tutorial to create a Google Login for my App with Xamarin:
Authenticate users through Google with Xamarin.Auth
I did it step by step but at the end, After I sign in to my google account I get this error:
System.Net.Http.HttpRequestException: '404 (Not Found)'
This part of code returns the error... When it tries to get the Email from API:
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(tokenType, accessToken);
var json = await httpClient.GetStringAsync("https://www.googleapis.com/userinfo/email?alt=json");
var email = JsonConvert.DeserializeObject<Email>(json);
return email.Data.Email;
My config parameters:
Google Client Id is correct => 437067938756-....apps.googleusercontent.com
Google Scope is "email"
GoogleRedirectUrl is blahbal:/oauth2redirect => blahblah is myAndroidManifest
GoogleAuthorizeUrl is"https://accounts.google.com/o/oauth2/v2/auth";
GoogleAccessTokenUrl is "https://www.googleapis.com/oauth2/v4/token";
GoogleIsUsingNativeUI is true

I was having the same error at some point.
The issue was in incorrect certificate.
Check if you are signing your app with Dev or Prod certificate.
Or you've updated/reinstalled xamarin and your dev cert has changed.

Related

Is Single Sender Validation in Sendgrid possible without logging in?

Was just wondering if it's possible for Single Sender Validation to be completed without having to login to Sendgrid as part of the process (e.g. click-through without login). For context, sometimes the people who "own" a mail address that we want to use for sending don't have access to Sendgrid, and we'd like them to be able to validate it. I think they can't by design, but wanted to confirm.
Looking at the API documentation, it looks like you can use the token sent in the validation email to complete the validation process, but I'm not sure if there's any way to effectively make use of that to redirect the user back to a process we control. There's another post that mentions the same kind of challenge, but thought I'd ask again as there wasn't anything definitive.
Is there a simple way to have the user who receives the validation redirect back to something other than sendgrid directly?
Thanks in advance!
The only alternative to logging in is to use the SendGrid API.
First, you either request the verification using the UI, or you use the Create Verified Sender Request API to start the verification for the single sender.
Then, the verification email will be sent to the specified email address which contains the verification URL. Usually, this URL will redirect you the the actual URL containing the verification token, as mentioned in the SO post you linked.
Once you get the verification token, you can use the Verify Sender Request API, passing in the verification token, to verify the single sender.
Note: All these APIs require a SendGrid API key.
So technically, you could have an application that prompts your user for their email address to verify, then uses the SendGrid API to start the verification which sends the verification email, then ask the user to go to their email inbox and copy in their verification link, then let the user paste in the URL from which you can extract the verification token, and use the API to verify. While the user didn't have to log in, it still requires some manual work.
However, the inputting of the email address and the checking the email inbox can also be done programmatically, so this process can be 100% automated, although it takes quite a bit of programming.
Here's a C# sample:
using System.Net;
using Microsoft.AspNetCore.WebUtilities;
using SendGrid;
namespace VerifySender;
internal class Program
{
public static async Task Main(string[] args)
{
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Program>(optional: true)
.Build();
var apiKey = configuration["SendGrid:ApiKey"]
?? Environment.GetEnvironmentVariable("SENDGRID_API_KEY")
?? throw new Exception("SendGrid API Key not configured.");
var client = new SendGridClient(apiKey);
// replace this JSON with your own values
const string data = """
{
"nickname": "Orders",
"from_email": "orders#example.com",
"from_name": "Example Orders",
"reply_to": "orders#example.com",
"reply_to_name": "Example Orders",
"address": "1234 Fake St",
"address2": "PO Box 1234",
"state": "CA",
"city": "San Francisco",
"country": "USA",
"zip": "94105"
}
""";
var response = await client.RequestAsync(
method: SendGridClient.Method.POST,
urlPath: "verified_senders",
requestBody: data
);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to request sender verification. HTTP status code {response.StatusCode}.");
Console.WriteLine(await response.Body.ReadAsStringAsync());
Console.WriteLine(response.Headers.ToString());
}
Console.WriteLine("Enter verification URL:");
var verificationUrl = Console.ReadLine();
var token = await GetVerificationTokenFromUrl(verificationUrl);
response = await client.RequestAsync(
method: SendGridClient.Method.GET,
urlPath: $"verified_senders/verify/{token}"
);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Failed to verify sender. HTTP status code {response.StatusCode}.");
Console.WriteLine(await response.Body.ReadAsStringAsync());
Console.WriteLine(response.Headers.ToString());
}
}
private static async Task<string> GetVerificationTokenFromUrl(string url)
{
/*
* url could be three different types:
* 1. Click Tracking Link which responds with HTTP Found and Location header to url type 2.
* 2. URL containing the verification token:
* https://app.sendgrid.com/settings/sender_auth/senders/verify?token=[VERIFICATION_TOKEN]&etc=etc
* 3. URL prompting the user to login, but contains url 2. in the redirect_to parameter:
* https://app.sendgrid.com/login?redirect_to=[URL_TYPE_2_ENCODED]
*/
const string verificationBaseUrl = "https://app.sendgrid.com/settings/sender_auth/senders/verify";
const string loginBaseUrl = "https://app.sendgrid.com/login";
if (url.StartsWith(verificationBaseUrl))
{
var uri = new Uri(url, UriKind.Absolute);
var parameters = QueryHelpers.ParseQuery(uri.Query);
if (parameters.ContainsKey("token"))
{
return parameters["token"].ToString();
}
throw new Exception("Did not find token in verification URL.");
}
if (url.StartsWith(loginBaseUrl))
{
var uri = new Uri(url, UriKind.Absolute);
var parameters = QueryHelpers.ParseQuery(uri.Query);
if (parameters.ContainsKey("redirect_to"))
{
url = $"https://app.sendgrid.com{parameters["redirect_to"]}";
return await GetVerificationTokenFromUrl(url);
}
throw new Exception("Did not find token in verification URL.");
}
var clientHandler = new HttpClientHandler();
clientHandler.AllowAutoRedirect = false;
using var httpClient = new HttpClient(clientHandler);
var response = await httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.Found)
{
var uri = response.Headers.Location;
return await GetVerificationTokenFromUrl(uri.ToString());
}
throw new Exception("Did not find token in verification URL.");
}
}
Take note of the comments inside of GetVerificationTokenFromUrl. Since I don't trust the user to copy the URL from the email without clicking on it, I added support for three types of URL:
Click Tracking Link which responds with HTTP Found and Location header to url type 2.
URL containing the verification token: https://app.sendgrid.com/settings/sender_auth/senders/verify?token=[VERIFICATION_TOKEN]&etc=etc
URL prompting the user to login, but contains url 2. in the redirect_to parameter: https://app.sendgrid.com/login?redirect_to=[URL_TYPE_2_ENCODED]
Here's the full source code on GitHub.

Botframework | Twilio WhatsApp Adapter: how to fix "request doesn't contain a valid Twilio Signature"

I need to use Twilio as a channel for a chatbot (Bot Framework V4 NodeJS and the Twilio WhatsApp Adapter)
Messages send to Twilio via WhatsApp do reach the chatbot as a request. In the chatbot itself the validation of the request keeps failing.
const { TwilioWhatsAppAdapter } = require('#botbuildercommunity/adapter-twilio-whatsapp');
const whatsAppAdapter = new TwilioWhatsAppAdapter({
accountSid: '<Sid from Twilio console >',
authToken: '<>Auth token from Twilio console',
phoneNumber: '<phone number from Whatsapp Sandbox settings in Twilio>',
endpointUrl: 'url from Sandbox setting in Twilio'
});
The adapter is using a Twilio module. Twilio.validateRequest keeps returning false.
const signature = req.headers['x-twilio-signature'] || req.headers['X-Twilio-Signature'];
const authToken = this.settings.authToken;
const requestUrl = this.settings.endpointUrl;
const message = await this.retrieveBody(req);
return Twilio.validateRequest(authToken, signature, requestUrl, message);
I would appreciate some guidance. Otherwise I will need to figure our a way to debug this module
tnx
For the endpointUrl parameter I use is the one from my chatbot: https://mybot.azurewebsites.net/api/whatsapp/messages
for the phoneNumber parameter I tried multiple formats without succes:
whatsapp:+1XXXXXXXXXX
whatsapp:1XXXXXXXXXX
+1XXXXXXXXXX
1XXXXXXXXXX
I checked the source of the adapter and there are two possible causes.
No x-twilio-signature header. I checked the request headers and there seems to be a valid x-twilio-signature header in there.
The result of the twilio.validateRequest returns false. This method is taking authToken, signature, requestUrl and message. I checked them all and they seem fine.

javascript client for oauth2 and google-oauth library's "redirect-uri" missing case

I am working on porting oauth2client library to google-auth and oauthlib library
Right now for oauth2 authentication, I am using javascript client and using grantofflineaccess method in it.
The flow for oauth2client library was:
using the js client, we get the authorization code and pass it in oauth2client.client.credentials_from_code to get the credentials object. In the paramaeters, we pass only clientid,secret,scopes and code. There was no mention of redirect_uri.
Right now I am replacing it with flow
https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html
This means that I HAVE TO pass redirect_uri to create the flow object, following which I can use flow.fetch_token(code) to get flow.credentials.
Here is my javascript side code (angularJS) (Reference: https://developers.google.com/identity/sign-in/web/reference)
// init
gapi.load('auth2', () => {
const authScopes = {
client_id: $window.GOOGLE_API_CLIENT_ID,
scope: scopes.join(' '),
};
auth2 = gapi.auth2.init(authScopes);
// When signin button clicked
auth2.grantOfflineAccess({ redirect_uri: 'postmessage', prompt: 'consent' })
.then((response) => {
// We enter this when user signins into google account and 'ALLOWS' the scopes. We receive the code in response using which we get the access and refresh token. The below calls are being made to backend
Something.authenticateUser(response.code).then(() => {
$scope.sheetsWizard.onLoginSuccess();
The backend code: (django)
# Upon receiving the 'code', it calls oauth2client function to get credentials object
credentials = client.credentials_from_code(
GOOGLE_API_CLIENT_ID,
GOOGLE_API_CLIENT_SECRET,
scopes,
data['code']
The above was older code, How the new code looks in the backend (The frontend or angularJS remains the same)
flow = Flow.from_client_config(client_config=dictionary_with_client_credentials, scopes=scopes, redirect_uri='some_website.com')
flow.fetch_token(code = data['code'])
credentials = flow.credentials
My questions:
Is there a method in google-auth that I am missing, so I don't have to pass redirect_uri, and still get at least access_token and refresh_token, after receiving code?
in js gapi client, there is a mention of redirect_uri='postmessage', but could not find documentation related to it. What does it mean?

google ExchangeCodeForTokenAsync invalid_grant in webapi

i have implemented GoogleAuthorizationCodeFlow scenario from google api client dotnet and tutorial to get token from what my client sent to server as a code. but when i call flow.ExchangeCodeForTokenAsync , I get the following error :
{"Error:\"invalid_grant\", Description:\"\", Uri:\"\""}
I read google authorization invalid_grant and gusclass oauth 2 using google dotnet api client libraries but they didn't help me and. I think it must be very simple but I don't know why it doesn't work.
For client side , I have used Satellizer and this is my server Codes:
public bool PostExchangeAccessToken(GoogleClientAccessCode code)
{
string[] SCOPES = { "email" };
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientSecret = "******",
ClientId = "********.apps.googleusercontent.com"
},
Scopes = SCOPES
});
try
{
TokenResponse token;
token = flow.ExchangeCodeForTokenAsync("*****#gmail.com", Newtonsoft.Json.JsonConvert.SerializeObject(code), "https://localhost:44301/",
CancellationToken.None).Result;
}
catch (Exception ex)
{
throw ex;
}
return true;
}
what is the problem?
On Github I found that I must use the Token from the client and use
GoogleAuthorizationCodeFlow.Initializer()
to create my UserCredential object.
You can check your google developer console settings.(Authorized redirect URIs)
Credentials => OAuth 2.0 client IDs => Your Application Settings => Authorized redirect URIs
You must add url. ("https://localhost:44301/")
My code :
flow.ExchangeCodeForTokenAsync("me", authCode, redirectUri, CancellationToken.None).Result;
Authorized redirect URIs
For use with requests from a web server. This is the path in your application that users are redirected to after they have authenticated with Google. The path will be appended with the authorization code for access. Must have a protocol. Cannot contain URL fragments or relative paths. Cannot be a public IP address.

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