What I am doing:
I am integrating Google Picker on my page. This will allow users to select files from their Google Drive to be used in the web app. In the app, people in a group share a common google drive (i.e. they all can select files from account example#email.com) which was created by group admin by his email address. When the admin signs-up for the account we do OAuth and get access_token with refresh_token against our app on google (with offline access enabled). I plan to use the access_token and refresh-token of the admin, on other group user's account when they try to use picker to select files.
What I have done:
I have integrated the Google Picker successfully in my app using the basic code provided in docs. Then to achieve what I wanted, I removed following code from the example code:
gapi.load('auth', {'callback': onAuthApiLoad});
and
function onAuthApiLoad() {
window.gapi.auth.authorize(
{
'client_id': clientId,
'scope': scope,
'immediate': false
},
handleAuthResult);
}
and
function handleAuthResult(authResult) {
if (authResult && !authResult.error) {
oauthToken = authResult.access_token;
createPicker();
}
}
and instead of .setOAuthToken(oauthToken) I pass refreshed access_token directly as string (I get that from my server with an ajax call).
.setOAuthToken("<access_token>")
But every time I call picker.setVisible(true); I get a screen in an iframe saying In order to select an item from your online storage, please sign in.
Problem:
Try to add sign in listener. Listeners provide a way to automatically respond to changes in the current user's Sign-In session. For example, after your startup method initializes the Google Sign-In auth2 object, you can set up listeners to respond to events like auth2.isSignedIn state changes, or changes in auth2.currentUser.
Validating the token might be a possibility before using the token each time but that might add a lot of extra overhead for a rare use-case each time we load the picker and when calling the API endpoints with a token after the re-authentication issue, there was no key about the token being invalid. You can validate a token by making a web service request to an endpoint on the Google Authorization Server and performing a string match on the results of that web service request.
Related
I want to do something very similar to this tutorial, in which I'm getting the authCode from web client and sending that authCode to a Java BE app to get credentials of an user and then, using the credential to gain access to google sheet api to create a spreadsheet on user's drive.
According to google-api-java-client/oauth2 doc:
GoogleCredential takes care of automatically "refreshing" the token,
which simply means getting a new access token.
Would I still be able to take advantage of the above statement, in which GoogleCredential automatically refreshes the token if I'm authenticating and asking for permission on the client web app - aka, I call the grant offline request on web app and then, getting the actual GoogleCredential in a Java BE app (using the authCode)? If so, how does that work? Why would others suggest to store the refreshToken in a db?
If I do decide to store in a db, would storing the refreshToken with the key as my app's unique identifier for a user be OK (instead of using the suggested sub identifier)? Is there a limit on the amount of time I can call the token to get a new accessToken per user? Even if an accessToken hasn't expired, is it better to just get a new accessToken for every new request (seems more secure)?
I'm having some problems with the Alexa account linking authorization.
These are the steps I followed:
I got the credentials (client id, client secret...) from the Google Cloud Console
Setup on the Alexa Developer Console, using 'Auth Code Grant' as authorization grant type
Activated the skill on my Alexa application and successfully logged in with my Google account
Now I got the access token in the request, in handler_input.request_envelope.context.system.user.access_token
The problem is that the access token expires after one hour and Alexa does not manage the refreshment of the token.
What should I do to avoid having to ask my users to login every time after one hour? Should I use Implicit grant as authorization type? Should I get a refresh token somehow?
Additional info: it's a custom skill that connects to an AWS Lambda using Python3
While #pinoyyid's answer was correct, it didn't provide a solution so I'm posting one for future reference.
The problem was indeed that Amazon servers did not receive a refresh token from Google, thus making it impossible to refresh the access token after its expiration time of one hour.
Following this link and other Amazon forum posts, I got to a working solution.
Amazon Alexa developer console 'Account Linking' configuration:
Authorization grant type: Auth Code Grant
Authorization URI: https://accounts.google.com/o/oauth2/v2/auth?access_type=offline (even though the one from the google credentials was not v2, it shouldn't make a difference)
The access type is very important because, as documentation goes:
Set the [access_type] value to offline if your application needs to refresh access tokens when the user is not present at the browser. [...] This value instructs the Google authorization server to return a refresh token and an access token the first time that your application exchanges an authorization code for tokens.
Access Token URI: https://accounts.google.com/o/oauth2/token
Client ID & Secret: downloaded on Google Cloud Platform
Client Authentication Scheme: HTTP Basic
Domain List: google.com and googleapis.com
Default access Token Expiration Time: left empty
Now, after doing this and saving the configuration, be aware that you might not notice the change, as, from here:
When Alexa uses the refresh token to retrieve a new access token for an existing user, Alexa uses the access token URI that was configured at the time the user linked their account. Therefore, if you change the access token URI later, users who linked their accounts before continue to use the old URI for retrieving updated tokens. The users must unlink and re-link accounts to switch to the new access token URI.
So, in order to complete the procedure:
Deactivate your skill
Go to the Google third party applications that have access to your data and remove your Google Project associated
Reactivate your skill and login again (if done correctly it should ask you the permissions for the scope you specified in the Alexa developer console again
Done! after one hour you should re-try and it should have a renewed access token
Additional Info
I found that many suggested to retrieve the refresh token, I don't believe this is possible because, even if Google sends it, it's Amazon that stores it and uses it to refresh the access token.
EDIT:
This works fine for developing and testing but I discovered here that for publication purposes you must own the landing page that you redirect your users to. For me it was just necessary to create a simple HTML page hosted in a public S3 bucket that would redirect the request to the Authorization URI I wrote before, while the Access Token URI must remain the Google one.
Have you read https://developer.amazon.com/docs/account-linking/configure-authorization-code-grant.html ?
My guess is that the Refresh Token is missing because you have already auithorised the app. The RT is only issued once. Try going into https://myaccount.google.com/permissions?utm_source=google-account&utm_medium=web to revoke the permission and try again.
I've written an online converter and integrated Auth0 to my website. What I'm trying to achieve is to auto-upload the converted file to the Google Drive of a logged in user. I set up Google oauth in Auth0 and everything seemed to work fine.
The problem is, Google's access_token expires after 60min and I don't have a refresh_token. Therefore, the user needs to log in via the Google Login-page again. That is not, what I want, because the user is in fact logged in way longer than just 60min on my site, but Google refuses API-calls (because the Google token expired).
I know I can request a refresh_token by setting access_type=offline but this will add the permission Have offline access. I don't want that, I just want to upload data to the user's Drive, if he clicked the convert button on my page. I don't want to ask the users for permissions I don't need. If I (as a user) would link my Google account on a similar page and the tool asks for offline access I wouldn't approve, to be honest - the permission sounds like the tool creator can do whatever he wants with your account whenever he wants... There are many tools out there that have write access to a user's Drive without asking for offline access and with one single login until the user revokes the permission. How is that done?
Is there a way to make Google API calls without asking for offline access and without forcing the user to approve the app (that is already approved by him) again and again every 60min?
Thanks in advance,
phlo
Is there a way to make Google API calls without asking for offline access and without forcing the user to approve the app (that is already approved by him) again and again every 60min?
Yes there are ways, but it depends on the specifics of your use case. For example, is your code in Java/php/etc running on a server, or is it JavaScript running in the browser?
Running your auth in the browser is probably the simplest solution as the Google library (https://developers.google.com/api-client-library/javascript/features/authentication) does all the work for you.
By asking for offline access you are requesting a refresh token. Google is going to tell the user that you are requesting offline access. You can request something without telling the user what they are authorizing.
No there is no way to request a refresh token without displaying that message. Nor is there a way for you to change the message it's a standard Google thing.
I found the solution!
Prerequirements
Enable Use Auth0 instead of the IdP to do Single Sign On in your client's Dashboard
Create a new Angular-route to handle the silent login callback (e.g. /sso)
Add
$rootScope.$on("$locationChangeStart", function() {
if ($location.path().indexOf("sso") == -1) {
authService.relogin(); //this is your own service
}
});
to your run-function and set the callbackURL in angularAuth0Provider.init() to your new Angular-route (<YOUR_DOMAIN>/sso). Add this URL to your accepted callbacks in the Auth0 dashboard - this won't end in an infinite loop, because the locationChangeStart-event won't call authService.relogin() for this route
Add $window.close(); to the controller of the Angular-route (/sso) to auto-close the popup
Authenticate the user via Auth0 and save the timestamp and the Auth0-access_token somewhere
On reload:
Check, if the Auth0-token is still valid in authService.relogin(). If not, the user has to login again either way. If the token is valid and the Google token is about to expire (check this with the saved timestamp to prevent unnecessary API calls) check for SSO-data and login silently, if present
/* ... */
if (validToken && googleExpired) {
angularAuth0.getSSOData(function (err, data) {
var lastUsedConnection = data.lastUsedConnection;
var connectionName = (_.isUndefined(lastUsedConnection) ? undefined : lastUsedConnection.name);
var isGoogle = (_.isUndefined(connectionName) ? false : connectionName == "google-oauth2");
if (!err && data.sso && isGoogle) {
authManager.authenticate();
localStorage.setItem("last-relogin", new Date().getTime());
angularAuth0.signin({
popup: true,
connection: data.lastUsedConnection.name
});
}
});
}
Now you will find a fresh Google access_token for this user (without asking for offline access)
Resources:
https://auth0.com/docs/quickstart/spa/angularjs/03-session-handling
https://auth0.com/docs/quickstart/spa/angularjs/11-sso
I am making an ajax controlled website, in which I use the Javascript SDK for Google to authenticate my users, and gain access to their google contacts. What I intend to do, is the following:
Authenticate the user in the browser, withour redirecting and ask for access to their Contact list, and access to manage their Contacts.
Store the user id in my database if he/she granted me the access, together with a refresh token, which if I am not mistaking, I can only get via server side.
Sometime later, if the user wants to see their google contacts via my website, send an ajax request to my server, which ASKS for an access_token from the user, retrieves the data and shows it to the user, or stores it in my own database if the user asks to.
I've managed to complete the first step from these three, I can authenticate a user, and get access to a single access token which is valid for 3600 hours, but I can't figure out how to authenticate a user server side, without redirecting him anywhere. I tried using the Google PHP SDK too, but can't seem to figure out how to do this. I am certain that this is possible somehow, because it is stated in the Google PHP SDK guide:
If we have authenticated on an Android or Javascript web client, we may have aquired a code via a different means. In this case, we just need to exchange it. If it was retrieved via the gapi Javascript client, we need to set the redirect URI postmessage.
$client->setRedirectUri($redirect_uri);
The only problem is I don't understand how to do this. What is $redirect_uri? I don't have a redirect url, becase when I implemented the Javascript SDK, there it said, that I don't have to use a redirect uri, because Javascript authentification is done in the same window, without redirects(just as I want it).
How could I proceed to solve the second and the third step mentioned above? Where could I find a non-hacky or not-very-much-hacky tutorial, to achieve my desired result?
EDIT:
What I basically want to achieve is the following things:
ask for permission to access Google Contacts from the user, WITHOUT redirecting him from my site(via a popup window)
Achieved this with the Javascript SDK
get an access token for this permission, and a refresh token, and STORE these in a database
Javascript SDK only grants an access token, and I don't want to pass this via an ajax call, because I feel this is unsecure
with the refresh token, generate access tokens server side for the user, and process data, and send the data back.
Here is how my PHP file looks at the moment:
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setScopes('https://www.googleapis.com/auth/plus.me');
$client->setRedirectUri($PHP_SELF);
$client->setState('offline');
$authUrl = $client->createAuthUrl();
if (isset($_GET['code'])) {
$client->authenticate($_GET['code']);
$_SESSION['access_token'] = $client->getAccessToken();
$result=$client->getAccessToken();
} else {
header("Location: ".$authUrl);
exit;
}
To achieve what I want, I should get the $_GET['code'] parameter somehow through the Javascript SDK, but I don't know how:|
After a few days of headache I figured this one out too... thanks Google for nothing, your documentation SUCKS.
$client->setRedirectUri($redirect_uri);
The $redirect_uri parameter should be a string: "postmessage", and when authenticating via javascript, you should ask for a CODE instead of a TOKEN, which you then send to your server side script, to authenticate and exchange for a token.
gapi.auth.authorize({client_id: googleApi.clientId, scope: googleApi.scopes, response_type: 'code', immediate: true}, g_handleAuthResult);
Trying to organize this question into something clear. We are integrating Google for Work into our application, to use login, Google+, and eventually Contacts, Calendar, etc. As is recommended by Google and everything I have read, we are going to use incremental access, only adding scopes when they are needed. We are a PHP shop.
But, we will also be needing offline access, as our Contacts (and eventually Calendar) access will be synchronizing with our internal database.
We currently capture the Access and Refresh Tokens when doing the initial link, and store them locally, so that we can re-authorize at any time by using the Refresh token whenever the Access token expires. This is working correctly.
Questions:
a) when adding the incremental scopes for Contacts, the documentation says we need to call the gapi.auth.signIn() function in the page javascript with the new scopes. This is working on the page where we are allowing folks to manage settings. In the original login function callback, I save the Access Token and scopes with an Ajax call that uses the access code passed into the callback, and calls the Google_Client authenticate() function to get the access code and scopes... but at that point, the information I get back does not have the new scopes. Why? Do I have to re-extend the scopes every time the page is drawn?
b) since we are going to have a batch process do the contact synchronization, do I need to get an entirely different access token with access_type=offline, or can I use the current access token (properly extended with the new scopes). Can an off-line access token be used for on-line access as well as off-line? Or vice-versa?
For your questions:
a) have you used the parameter "include_granted_scopes"? as mentioned here:
https://developers.google.com/accounts/docs/OAuth2WebServer#incrementalAuth
b) When you request an offline access token, the response contains the access token and refresh token. so you can refresh the access token after it expires without having the user grant the permissions again.
online access token and offline access token work for the same.
the difference between both its the capability to refresh the access token when it expires without involving the user. Which is the functionality for the offline type.
The online access token doesn't mean that it works for your client-side authentication (done in the browser) and the offline works for the server-side.
You mentioned that you can get an access token, refresh token and authorization code from the client-side of your app. You could send that information to your server and make api calls from there, although this is not a good practice.
I would suggest that you do the OAuth Flow in the server side and from there manage the users information and API calls.
Here you can find the documentation on both Web server applications and Client Side applications.
Hope it's clearer.