I have an application that is available on iOS, Android, and Desktop (via Electron) which all have features that depend on a Google OAuth flow.
I decided to make all of the apps redirect to an Angular web app for completing the OAuth journey which then successfully redirects back into whichever app the user came from.
Just to clarify immediately, my app is successfully verified by Google and it is set to production mode.
These are scopes which I use:
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/user.birthday.read',
'https://www.googleapis.com/auth/user.phonenumbers.read',
'https://www.googleapis.com/auth/calendar.events',
'https://www.googleapis.com/auth/contacts.readonly',
'https://www.googleapis.com/auth/contacts.other.readonly',
'https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive.appdata',
'https://www.googleapis.com/auth/youtube.force-ssl',
Below is a step-by-step breakdown of the OAuth journey:
User clicks on UI within my frontend app to connect their Google account.
Frontend calls my backend (PHP Laravel) to generate a URL where the user can authenticate.
Frontend sends the user to the URL in their default web browser.
User enters their Google account credentials and gives consent to all the permissions/scopes.
User is sent to the configured redirect URI which is an Angular web app that captures the auth code.
Angular web app sends the auth code to my backend to validate the OAuth process such as generating refresh/access tokens etc.
My backend responds to the Angular app successfuly.
Angular app finally proceeds to redirect the user back to the app which they come from such as iOS, Android, or Desktop.
User is now authenticated and the OAuth process is complete while all the Google tokens are persisted safely in my backend.
All of the above works perfectly fine across all of the apps.
Later when the user is interacting with a feature which depends on Google such as for example their Google Calendar Events, my frontend calls my backend which in turn calls Google's REST APIs using that user's persisted access token and then serves it back to the frontend app.
Important Note:
Since as you can see above that the OAuth process is all done in the Angular web app, I did not need to implement the Google OAuth SDKs on my iOS, Android, or Desktop apps.
However on the Google API Dashboard I had to generate 3 separate web OAuth clients, one for each app. This should have not been necessary since I can set multiple Redirect URIs for 1 web OAuth client but I was facing a weird bug which seemingly did not go away until I separated them.
So for example when iOS goes through the OAuth flow, the client ID for the iOS web client is used and so on.
Image of 3 web clients on Google API Dashboard
So what is the problem?
For some reason at random the OAuth credentials stop working. Sometimes it takes weeks to happen, sometimes days. I've been trying to find a pattern of what causes it and the closest clue I have is if the user performs SSO/OAuth across the different frontend apps perhaps. So for example they do it on Desktop and then later also perform an SSO login on their iOS app.
Or is it possible that Google is noticing this OAuth user originating from multiple different seemingly web origins so it's randomly revoking the tokens?
Everywhere I look online people mention that refresh tokens on a verified app in production mode should not expire unless the user's password is reset or they revoke the app manually, all of which are not the case for me.
Lastly to note, the same exact procedure is used with Microsoft and it works without this problem occurring. So it's specifically happening with Google OAuth only.
UPDATE (30/06/2022)
At some point for some reason randomly, my backend is unable to refresh the access token at server side. It throws the following error message when attempting to refresh the access token:
{'error' => 'unauthorized_client', 'error_description' => 'Unauthorized'}
When you generating access and refresh token, you have to store it safely.
The access token have a short lifetime (1 hour). You have to control if it stay valid before using it. To do it, you may use this endpoint: https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=[ACCESS_TOKEN]
If the token is valid, you will have this kind of response:
{
"azp": "15[...]2b.apps.googleusercontent.com",
"aud": "15[...]2b.apps.googleusercontent.com",
"sub": "10[...]71",
"scope": "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/business.manage",
"exp": "1658865502",
"expires_in": "3067",
"access_type": "offline"
}
Else, you will have an error:
{
"error_description": "Invalid Value"
}
You may generate a new access token from the refresh token when the expires_in value is low or when the access token became invalid.
For example, with Axios:
axios.post('https://oauth2.googleapis.com/token', {
client_id: YOUR_GOOGLE_CLIENT_ID,
client_secret: YOUR_GOOGLE_CLIENT_SECRET,
refresh_token: USER_GOOGLE_REFRESH_TOKEN,
grant_type: 'refresh_token',
});
You will get a new access token:
{
"access_token": "ya[...]63",
"expires_in": 3599,
"scope": "https://www.googleapis.com/auth/business.manage https://www.googleapis.com/auth/userinfo.profile",
"token_type": "Bearer",
"id_token": "ey[...]bA"
}
If the authentication was revoked by the user, you will have an error in response:
{
"error": "invalid_grant",
"error_description": "Token has been expired or revoked."
}
Related
I am trying to use account linking in my skill. The problem is that the documentation is not clear enough. I have the login screen, it redirects to the amazon redirect uri provided in the URL parameters, I also include the code (btw I'm using Auth Code Grant) and here comes the problem. My script gets the code and the client credentials and generates the two tokens, but what should it do with them? Does their system wait a json response or something containing the two tokens using their names as keys or should I redirect again? What do they mean in the documentation when they say the server should return the access token and the refresh token?
The token response should look like this. I believe the exires_in and refresh_token properties are optional.
{
"access_token": "...",
"refresh_token": "...",
"expires_in": 3600
"token_type": "bearer"
}
https://developer.amazon.com/docs/alexa-voice-service/authorize-companion-site.html#auth-code-grant.
There are multiple threads on this topic from the Amazon Developer Forum I tried the tutorial referenced on a home brew AVS device (RPi3) and it works with no issue (I can get the user name etc. from my Amazon account), although I still can't figure out how to access activity api json within AVS SDK.
Steps in Account Linking(Auth code grand flow):
• User will be redirect to form based login page, used to get the user credentials
• After validating the user credentials and user authorities by your backend security script, it will be redirect the request to authorize endpoint to generate the authorization code.
• Once the request reached the authorize end point, it will validate the client details.
• Once it is validated, user will be redirected to the approval page.
• Once we get the confirmation from the user, your backend security server script need to create the autorization code and need to give it back to the AWS alexa client.
• AWS client will call our access token end point to get the access token and refresh token.
• Once AWS client gets the access token and refresh token, the skill will be linked to the user.
• AWS Alexa client send the access token in every request once the user have linked the skill
Your Problem: AWS client will call your access token end point url along with Authorization code and client credentials, Your back-end script need to validate those inputs and need to create access token, refresh token and send it back to AWS client.Its a POST request from AWS client, you have to return back response to same POST request.
Basically my goal is to make a form that allows every visitor of my website to upload a video in the comment section to my OWN channel. Currently I'm using YouTube OAuth version 3 API for it. The problem is every 3600 seconds, the code from YouTube will be expired and we will be redirected to Google OAuth that asks for permission (Example: https://accounts.google.com/o/oauth2/auth?client_id=805j8tubb260venakqj8jq3f6hl9eluu.apps.googleusercontent.com). And we need to manually click the 'allow' button every time the button expires.
So is it possible to allow access once, and then we dont need to take the code again to give the upload permission to the website visitor?
You should follow KENdi's instructions on how to get the token -- he pretty much pulled it straight from the docs. But, you should only have to do that once. The trick is to save the refresh_token somewhere, and then use that to get a new access token before you want to upload a video. Here's my quick and dirty NodeJS solution:
function getAccessToken () {
const options = {
method: 'POST',
uri: 'https://accounts.google.com/o/oauth2/token',
form: {
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
refresh_token: 'YOUR_REFRESH_TOKEN',
grant_type: 'refresh_token'
}
}
return request(options)
.then(body => JSON.parse(body));
}
getAccessToken.then((token) => uploadSomeYoutubeVideo());
Try to check if you follow the steps/process here in this documentation.
1. Register your application as an installed application
2. Request an access token
3. User consent decision
In this step, the user decides whether to grant your application the ability to make API requests that are authorized as the user. Google's authorization server displays the name of your application and the Google API services that it is requesting permission to access with the user's authorization credentials. The user can then consent or refuse to grant access to your application.
4. Handle response from Google
5. Exchange authorization code for refresh and access tokens
6. Process response and store tokens
Here, Google will respond to your POST request by returning a JSON object that contains a short-lived access token and a refresh token.
{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}
NOte: Your application should store both values in a secure,
long-lived location that is accessible between different invocations
of your application. The refresh token enables your application to
obtain a new access token if the one that you have expires. As such,
if your application loses the refresh token, the user will need to
repeat the OAuth 2.0 consent flow so that your application can obtain
a new refresh token.
Access tokens periodically expire, so it need to be refreshed. When an access token expires, your application may be able to use a refresh token to obtain a new, valid access token. Server-side web applications, installed applications, and devices all obtain refresh tokens during the authorization process.
Note that tokens might stop, no longer work or
expire
if:
The user has revoked access.
The token has not been used for six months.
The user account has exceeded a certain number of token requests.
I have a 3-legged auth flow working on a web app of mine. It goes as follows:
Use clicks Connect with Google
They accept on the OAuth dialog that Google Provides
The page gets redirect to my backend's /oauth/google endpoint with a code parameter which I send to Google to get a refresh_token so I can access data (like calendar info) on their behalf
I redirect back to the web app passing my own JWT token in the URL.
Whenever the web app makes a request like api.mybackend.com/me they use the JWT token I provided
I'm trying to accomplish something similar in a mobile app. What's the acceptable way to do this? Is it pretty much the same logic?
If it helps, my backend is Ruby on Rails and I'm writing the mobile app in Swift.
Thanks!
If you're using NSURLSession to make HTTP requests, then see this for information about handling redirects.
Google also has some pre-built Google Sign-In packages for iOS and Android that you can include in your app, similar to the one in your web client. I've never used them though, so I don't how exactly they'd integrate with you app.
Alternatively you can set up an authentication endpoint in your backend that handles the whole thing, with the app only ever making one request to your server and your server handling communication with Google. So, for example, you could have the user submit a request to /oauth/mobile. The server then submits an authentication request to Google and gets an access token and a refresh token. Then you can return your own app's token from the server. Google has some documentation on Google Sign-In for server-side apps that may be relevant.
What exactly does the word "offline" mean with regard to the offline access granted by an OAuth server?
Does it mean that the resource server will return data about the user even when the user is logged out of the third-party application or when the user is logged out of the OAuth resource server such as Facebook or Google or Twitter?
Offline access is IMO a really bad name for it, and I think its a term only
Google uses its not in the RFC for OAuth as far as I remember.
What is Google offline access?
When you request offline access the Google Authentication server returns a
refresh token. Refresh tokens give your application the ability to
request data on behalf of the user when the user is not present and in front of
your application.
Example of an app needing offline access
Let's say I have a Super Awesome app that downloads your Google Analytics Data,
makes it into a nice PDF file and emails it to you every morning with your
stats. For this to work my application needs to have the ability to access
your Google Analytics data when you are not around, to give me permission to do
that. So Super Awesome app would request offline access and the
authentication server would return a refresh token. With that refresh token
Super awesome app can request a new access token whenever it wants and get your
Google Analytics data.
Example of an app not needing offline access
Let's try Less Awesome app that lets you upload files to Google Drive. Less
Awesome app doesn't need to access your Google drive account when you're not
around. It only needs to access it when you are online. So in theory it
wouldn't need offline access. But in practice it does, it still gets a refresh
token so that it won't have to ask you for permission again (this is where I
think the naming is incorrect).
Helpful quote from the OpenStack documentation:
If a refresh token is present in the authorization code exchange, then it
can be used to obtain new access tokens at any time. This is called
offline access, because the user does not have to be present at the browser
when the application obtains a new access token.
The truth about offline access
The thing is that in a lot of cases the authentication server will return the
refresh token to you no matter what: You don't have to actually ask for anything –
it gives it to you. Giving you the ability to access the users data when they
aren't around. Users don't know that you could access their data without them
being there. It's only the JavaScript library and I think the PHP library
that hide the refresh token from you, but it's there.
Example
By just posting (i.e. HTTP POST request):
https://accounts.google.com/o/oauth2/token?code={AuthCode}&
client_id={ClientId}.apps.googleusercontent.com&client_secret={ClientSecret}&
redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code
Here is the response:
{
"access_token": "ya29.1.AADtN_VSBMC2Ga2lhxsTKjVQ_ROco8VbD6h01aj4PcKHLm6qvHbNtn-_BIzXMw",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "1/J-3zPA8XR1o_cXebV9sDKn_f5MTqaFhKFxH-3PUPiJ4"
}
I now have offline access to this users data, and I never told them that I
would have it. More details be found in this short article: Google 3 legged
OAuth2 flow.
Useful reading
Using OAuth 2.0 for Web Server Applications
Understanding Refresh Tokens
By design the access tokens returned by the OAuth flow expire after a period of time (1 hour for Google access tokens), as a safety mechanism. This means that any application that wants to work with a user's data needs the user to have recently gone through the OAuth flow, aka be online. Requesting offline access provides the application a refresh token it can use to generate new access tokens, allowing it to access user data long after the data has gone through the OAuth flow, aka when they are offline.
Getting offline access is needed when your application continues to run when the user isn't present. For instance, if there is some nightly batch process, or if your application responds to external events like push notifications. However if you only access user data while the user is actively using your application then there is no need for offline access. Just send the user through the OAuth flow every time you need n access token, and if they've previously granted access to your application the authorization page will instantly close, making the process nearly invisible to the user.
For Google APIs, you can request offline access by including the parameter access_type=offline in the authorization URL you present to your users. Offline access, and hence refresh tokens, is requested automatically when using the Installed Application flow.
We have a SSO system which is used by our web applications and we are planning to also write some new mobile/desktop applications. I have been reading about using oAuth 2.0 so a user can natively login using the app (using the password grant type), retrieve an access token and access functionality using the REST web services. The problem is all functionality will not be available within the app, in some cases we have to redirect the user to the web application via a browser. I have read we shouldn't pass the access token in a URL so is there anyway to use oAuth to login to the web application automatically without the user having to submit their username and password again?
Any advice would be much appreciated.
In the mobile app, you could create a nonce (a token that should be used no more than once). Then, add this to the URL when opening a link to the Web page. This token should have some sort of identifier in it. The Web app that serves that page should track these IDs to ensure that it's not sent multiple times (thus, making it a nonce). This would be independent of the OAuth-based authentication. So, a request might look like this:
GET /anything?nonce=eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI5N1Y2ZU9WWlo1VGpPR3dWIiwiY2xpZW50X2lkIjoibXktZ29vZC1tb2JpbGUtYXBwLTIyIiwiYXVkIjoiaHR0cHM6Ly9leGFtcGxlLm9yZy9wYWdlLXRoYXQtaXMtbm90LWluLW15LW1vYmlsZS1hcHAiLCJleHAiOjE1OTg3NTI0NDB9.JoDstXRnC23PE8ZCqk-U-IzSNp_cUYa12HbajI1KGlF-OwRR46QRC_V7brcGOVdo5_Aw1RdpssNWCVFiGDeTBc8wi1lIJW-rgEGH5J1qUi8rL1T-yfy3vdLGPYzJMtHvCeyoEjgmoYOtZcpPSQScBJSRvId3Hdu3QgwcelSwljkeNJbZRCnG25HIqJfC1Cjm9vqHhvUGqtzbgVBUPnICiI8EZyGe3SpH2P4SxctLcgzWad8zJeyPFki3yfBHpqQ3mBWy0BbVdjzgD0mj323q1LWHR4kNkrH7cUJgAg4PlWahOW7Q4qcT3CBJYNtlh008ARqK7XagEQKX45vv5TfSlk2q7Zy06RnV2XwZXfLpyh-ZfURpcsxEJ3O-4NY71XxEqUtEyuisjQdZx5m95uzSrzk75F-ruQ3KYIouiAOAUDuMtFwhwjF68VdDeC4Zwt2q3BHzMBBp-8k1bAXq8e4dmHz0Jbuo9R8MJ2zSrVK6is5nNtNoJvYoXgc0WTA8MFqBj316cT_ouu-U1nTL3GR5sJ_lxorhP6xz0CqNxNY_90-JwOUB0UibUryRiXt-SUPJga36pBQ8eO8--Xupx_WU7CDIFdFvnLgJahD-4KmZcga6wCoqd-KKw3H5-jtbit06XMrKkDiWjz2g4eYhPR6xipbnqyZaaCwtYN4mAMz86ug HTTP/1.1
Host: example.org
Accept: */*
When the JWT-encoded nonce is decoded, you can see this header:
{
"alg": RS256
}
and this body:
{
"jti": "97V6eOVZZ5TjOGwV",
"client_id": "my-good-mobile-app-22",
"aud": "https://example.org/page-that-is-not-in-my-mobile-app",
"exp": 1598752440
}
Now, the API could decide if the nonce is acceptable by:
Check that it's URL is the same as the aud (audience) claim
Check that the nonce has never been seen before (using the jti, JWT ID, claim)
Check that this resource should be possible for this client to request (using the client_id claim)
Check that the nonce wasn't created too long ago and is still valid (using the exp or expires claim)
The signature of the JWT is valid. This can be done using the public key of the client.
Doing like this could lead to a spaghetti of trust, however. To avoid this, your OAuth server could instead embed a nonce into the app's ID token, or allow the client to request new ones as needed (by performing token exchange e.g.). Then, the API only has to trust the OAuth server, and doesn't need to keep track of the client's public key. If you only have a handful of clients, this won't be hard to mange though.