Google oAuth2 Access Token: "invalid_grant" - node.js

Similar question to this, except those answers are outdated and not applicable to my current situation.
Here's my webapp's signin flow:
Log in with Google (forces prompt, gives offline approval)
I successfully get a profile back with an access token and a refresh token
I do a few bits and pieces, then use that refresh token to generate an access token and download the user's contact list
This was working fine (last tested a week ago) but today it's suddenly giving me:
error: invalid_grant
I'm using Node's xoauth2 package, and this hasn't been updated since June so I can't see why this would suddenly be a problem now - unless Google has changed something on their end in the past week or so?
Sample of the code I'm using that calls the error:
// User credentials - all verified working + correct
xoauth2gen = xoauth2.createXOAuth2Generator({
user: email,
clientId: configAuth.googleAuth.clientID,
clientSecret: configAuth.googleAuth.clientSecret,
refreshToken: refresh
});
// SMTP/IMAP
xoauth2gen.getToken(function(err, token){
if(err){
return console.log("XOAUTH2 Error: " + err);
}
if(type === "full"){
cb(token);
}
});
Edit: For completeness, the scopes I'm using when generating the refresh token are:
app.get('/auth/google',
passport.authenticate('google',
{
scope : ['https://mail.google.com/',
'profile',
'email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.google.com/m8/feeds'],
accessType: 'offline',
approvalPrompt: 'force'
}
));

According to RFC 6749 invalid_grant is returned when:
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.
Check this out here

This was eventually fixed by deauthorizing my app in my Gmail account, deleting the refresh token I had stored in MongoDB and starting from scratch. Appears that it was simply a case of some permissions that I had changed, that hadn't been granted via oAuth2.

If you are running a test server using localhost, this error may occur if your system time(OS time) is out of sync.
My system clock was not correct as my CMOS battery was dead. Every time I restarted my laptop the time would not be correct. Hence I would get this error when running
`node server.js'
and then making a request from the frontend to my express js backend

Related

Error when getting token using BotFramework's OAuthPrompt and Azure Oauth 2 Generic Provider

I'm trying to add authentication to a bot for Microsoft Teams using the Azure Oauth 2 Generic Provider. It works the first time I sign in, but if I need to get the token again, bot framework raises an error.
The connection works fine when I test in Azure Portal
Azure Portal connection test
And the token is returned the first time I call OAuthPrompt and sign in, I'm able to get the user profile as expected:
OAuthPrompt token response
If I call OAuthPrompt a second time to get the token, I get the error DialogContextError: Error retrieving token: 3498ec40d9ccd8469840bd31f8b1a78c.
The code for the dialog:
this.addDialog(
new OAuthPrompt(OAUTH_PROMPT, {
connectionName,
text: "Please sign in",
title: "Sign in",
timeout: 300000,
showSignInLink: true
})
);
How I get the token:
private async getTokenStep(stepContext: WaterfallStepContext) {
return await stepContext.beginDialog(OAUTH_PROMPT);
}
Any Idea what I doing wrong?
If you are using the OAuthPrompt class provided by MS, the most obvious reason I can think of that it's generating an error retrieving the token is because your request is getting throttled. If you can find the HTTP Request in your debug it's most likely returning 400 Bad Request.
Why are you calling the OAuthPrompt the second time to generate a new token, when you already requested a token the first time? What you should actually be doing is saving the token you generated from the first request into a user state, and reusing the token until it expires. You can check if the token expires before prompting for a new token again.
Check this article for how to save user state.
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-state?view=azure-bot-service-4.0&tabs=csharp

error 401 unauthorized I see when I am successfully logged in. But after logging in, the token gets Revoked and the response is error 401 unauthorized

that error appears to me
[Unhandled promise rejection: Error: Request failed with status code 401]
that's the Ropo link
https://github.com/ahmedsawy1/Carna-Project
It contains two folders, the first for the front end (React Native) and the other for the back end (Node js)
Everything works fine regarding Authentication where I can register a user or login with a valid password and email and bring the token and everything is fine. But the problem is that when I log in.. and bring a correct token, it works and actually logs in.
This app supposedly goes to the Profile screen after logging in.
But after The successful login, it goes to the Profile screen, and here the problem appears, which is that the token gets Revoked and does not show any data for the user, and I cannot bring his data such as his name or his data to display on his personal page.
I would appreciate someone helping me here.
I think you use JWT for your authentication. JWT contains accessToken and refreshToken. both of this tokens have expired time and refreshToken has longer expire time. whenever accessToken expired, it would be usually received error code 401. By getting this error, you need to send your accessToken and your refreshToken to the refreshToken api and get the new accessToken and RefreshToken and carry on with your new accessToken

Google Auth - Always prompt for authorization, what should I do with the refresh token?

I'm currently discovering OAuth identification on a nodejs server in order to test it with queries on the api youtube.
For now, it works, I can make the requests. There are only two things I have a problem with.
First, every time I log in, Google asks for permission even though it has already been granted (and the permission is visible in the allowed applications on my account).
According to this answer
"login with google always asks user consent", this should not happen, despite the fact that I work locally.
My second question may help me answer the first. When I studied the OAuth, I learned about the existence of the refresher token, which is supposed to allow the creation of a new access token when it has expired.
I just can't figure out if it's up to me to use the refresh token or if the library does it by itself when making requests?
I based myself on:
https://developers.google.com/youtube/v3/quickstart/nodejs
Each session is associated with an OAuth2Client containing tokens and all client information
[Current situation]
Refresh token is handle by the library as Rubén shown here.
Still no answer about the prompt everytime.
Following that quickstart, when you go through authorization you are calling getToken:
oauth2Client.getToken(code, function(err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
The token object response contains an access_token and the refresh_token. The refresh_token is only returned the first time you log in (you can get new ones if token expires) and you need to store it in order to generate new access tokens.
As you can also see in the example, in authorize function you need to check if you have previously stored your tokens.
Then, to use the refresh_token you need to call:
oauth2Client.setCredentials({
refresh_token: YOUR_STORED_REFRESH_TOKEN
});
After setting credentials with refresh_token, access_token is automatically updated and you can use your oauth2client to make requests again.
I hope it's clear!
Reference: https://developers.google.com/identity/protocols/OAuth2WebServer#offline

Why is my request for a new access token not returning a new refresh token?

I am using the following code, along with my refresh token, to request a new access token:
exports.getTokenFromRefreshToken = function (user, callback) {
request.post({
url:'https://login.microsoftonline.com/12345678-1234-1234-1234-2f189712345/oauth2/token',
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token,
client_id: client_id,
client_secret: client_secret,
resource: 'https://graph.microsoft.com'
}
}, function(err, httpResponse, body) {
if (!err) {
var tokens = JSON.parse(httpResponse.body);
console.log('getTokenFromRefreshToken() tokens = ' + JSON.stringify(tokens));
callback(null, tokens);
}
})
};
The httpResponse includes everything that I get when I make the original token request (from the code), but without a new refresh token. I was under the impression that I would also receive a new refresh token. Is that not the case?
You get a new refresh token only when you are including the offline_access scope.
ref.: https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-scopes/
The offline_access scope gives your app access to resources on behalf of the user for an extended time. On the work account consent page, this scope appears as the "Access your data anytime" permission. On the personal Microsoft account consent page, it appears as the "Access your info anytime" permission. When a user approves the offline_access scope, your app can receive refresh tokens from the v2.0 token endpoint. Refresh tokens are long-lived. Your app can get new access tokens as older ones expire.
Refresh tokens aren't refreshed the same way you can get a new access token using the refresh token. When a refresh token expires, you will need to need to get the credentials and do the initial token acquisition again.
More info here: Refreshing an Access Token
It looks like it should work except you seem to be missing the redirect URI. I have a working version of this call but it includes this redirect_uri. It produces for me both a new access and refresh token.
--
http://graph.microsoft.io/en-us/docs/authorization/app_authorization
Renew expiring access token using refresh token
The redirect URL that the browser is sent to when authentication is complete. This should match the redirect_uri value used in the first request.
I had the exact same issue, caused a headache for a while until the problem was found.
Seems like you are probably logging in with a Guest MS account (previously known as Live) and thus getting a 12hr expiry refresh token with no rolling window.
You need to use a full MS account to get the refresh token back in the response body (which is a token that will last 14 days), with a rolling window of 90 days.
For as long as you use a Guest MSA you will not get a refresh token back. Your code is correctly structured as far as I can see, and I don't believe you need a redirect_uri header as stated above, they are optional fields according to doco.
For more information on the types of refresh tokens, see this blog post:
Azure tokens

Azure Error: Client side authentication flow with Google is not supported

I suddenly started to receive the following error in my app. I'm sure it has been running fine before - atleast on my machine :-)
Error: Client side authentication flow with Google is not supported.
I get this error when I try to login using a accesstoken I've received from a gapi authorize call:
// login with google using gapi
gapi.auth.authorize({ client_id: clientId, scope: scopes, immediate: noPopup },
function (authResult) {
// Pass the accesstoken into azure
client.login("google", {"access_token": authResult.access_token}).then(
function(user) {
// logged into azure...
Then I receive the error about not supported flow.
(if I change from "google" to "facebook", the error is: Error: The Facebook Graph API access token authorization request failed with HTTP status code 400 - which makes sence since it's a google accesstoken I'm passing in)
If I paste in the url directly in a browser https://kjokken.azure-mobile.net/login/google, then everything seems to be ok.
Any ideas why this is happening?
Thanks for any help
Larsi
Thank you for using Mobile Services and taking the time to report this. We actively working on adding support for this particular scenario over the next couple of weeks, which explains what you are seeing. I will update this post when we have more information.
In the interim, did you consider using MobileServiceClient.login(MobileServiceAuthenticationProvider provider, UserAuthenticationCallback callback)?
Thanks,
-Yavor

Resources