Google Identity: what is the recommended way to get a new access token after it expires - google-identity

I am getting an access token like this:
private async _promptForToken(scopes: string[], prompt: "none" | "consent"): Promise<string> {
const that = this
return new Promise(resolve => {
const tokenClient = google.accounts.oauth2.initTokenClient({
client_id: this.clientId,
scope: scopes.join(' '),
callback: function (tokenResponse) {
that._storeTokenResponse(tokenResponse)
resolve(tokenResponse.access_token)
}
})
tokenClient.requestAccessToken({prompt})
})
}
I storing the token in local storage. If I leave the browser for an hour, so the access token expires, and I come back to my app and click a button that requires a new access token, I am requesting the new token using this code:
this._promptForToken(scopes, 'none')
In other words, I am asking for the same access permissions, but without consent. When I do that I get back a response like this:
{error_subtype: "access_denied", error: "interaction_required"}
Which I can't find documented anywhere, but that's another issue.
If instead, I ask for a new access token using consent i.e.
this._promptForToken(scopes, 'consent')
The Google dialog box for permissions pops up for a second, then disappears, which is horrible UX. And this will happen every time an access token expires. Horrible I say!
What is the recommended way to request a new access token?
Context: browser only, so implicit flow only, I do not want to have to maintain refresh tokens in the backend.

Related

How to obtain a Google oauth2 refresh token?

The following code uses the Google oauth2 mechanism to sign in a user. We need to process updates to the user's calendar while the user is offline, so we ultimately need the 'refresh token'. Does the result from grantOfflineAccess() return the refresh token (below, I can see that response.code holds a value that might be the refresh token)?
How can I get a refresh token that can be used (server side) to create new access keys for offline access to a user's Google calendar?
<script type="text/javascript">
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function initClient() {
gapi.client.init({
apiKey: 'MY_API_KEY',
clientId: 'MY_CLIENT_ID.apps.googleusercontent.com',
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'],
scope: 'https://www.googleapis.com/auth/calendar'
}).then(function () {
var GoogleAuth = gapi.auth2.getAuthInstance();
GoogleAuth.signIn();
GoogleAuth.grantOfflineAccess().then(function (response) {
var refresh_token = response.code;
});
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
There is a reason why you are having a problem getting a refresh token out of JavaScript. That reason being that it's not possible.
JavaScript is a client side programming language, for it to work you would have to have your client id and client secret embedded in the code along with the refresh token. This would be visible to anyone who did a view source on the web page.
I think you realize why that's probably a bad idea. The main issue is that gapi won't return it the library just doesn't have that ability (not that I have tried in raw JavaScript to see if the OAuth server would return it if I asked nicely).
You will need to switch to some server side language. I have heard that this can be done with Node.js, but haven't tried myself. And Java, PHP, Python are all valid options too.
Based from this post, you should include the specific scopes in your requests. Your client configuration should have $client->setAccessType("offline"); and $client->setApprovalPrompt("force");.
After allowing access, you will be returned an access code that you can exchange for an access token. The access token returned is the one you need to save in a database. Later on, if the user needs to use the calendar service, you simply use the access token you already saved.
Here's a sample code:
/*
* #$accessToken - json encoded array (access token saved to database)
*/
$client = new Google_Client();
$client->setAuthConfig("client_secret.json");
$client->addScope("https://www.googleapis.com/auth/calendar");
$_SESSION["access_token"] = json_decode($accessToken, true);
$client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($client);
//REST OF THE PROCESS HERE

Caching Google JWT

I have a mobile app communicates with a server. For authentication in the mobile app, I'm using sign in with google. The sign in returns an accessToken which I send to my server and verify using google-auth-library as suggested here: https://developers.google.com/identity/sign-in/web/backend-auth
import GoogleAuth from 'google-auth-library'
const auth = new GoogleAuth()
const client = new auth.OAuth2(MyClientId, '', '')
apiRoutes.use((req, res, next) => {
// get the token from the request
const token = req.token
if (token) {
// verify secret with google
client.verifyIdToken(token, MyClientId, (err, payload) =>
// proceed with the user authenticated
...
Is it necessary to make this call with every request that the user makes? Would it be good practice to do some sort of caching? Or to have my own implementation of JWT on my server that includes the google payload?
No, the server should usually creates an account for the user once it validates the access token, saving the Google ID in the database along other user details (ID, email, name etc), and then returns an access token to the mobile application.
Once the latter (usually stored locally) expires, it can be refreshed without prompting the user for permission.

Youtube oAuth promts authorization window every time I make request

Here is my work flow for getting access token and refresh token for youtube api. Im generating authorization url with parameters
access_type=offline, response_type=code, redirect_uri=uri, scope=scopes, state=state, client_id=id
from authorization url I´m receiving authentication code, then I´m generating another url to get access_token and refresh_token using code from authorization url with these parameters
code: code, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: serviceCallback, state: state.callback, grant_type: "authorization_code"
As far as I know user should complete this process only once and then it should be automatic. My problem is that I´m always have to complete authorization and I´m getting always new access_token and refresh_token without forcing it on request.
here is code part where I´m getting authentication url
getAuthUrl: function(scopes, applicationCallback, serviceCallback, siteId,
selectChannel, websiteUrl) {
var requestedClientId = CLIENT_ID;
var scopess =
"https://www.googleapis.com/auth/yt-analytics.readonly https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/userinfo.email " +
scopes.replace(",", " ");
return "https://accounts.google.com/o/oauth2/auth?" +
"access_type=offline" +
"&response_type=code" +
/*"&approval_prompt=auto" +*/
"&redirect_uri=" + serviceCallback +
"&scope=" + scopes +
"&state=" + JSON.stringify({
service: NAME,
callback: applicationCallback,
scopes: scopes,
siteId: siteId,
selectChannel: selectChannel,
websiteUrl: websiteUrl
}) +
"&client_id=" + requestedClientId;
},
From there Im getting back code and using that code, clientID and clientSecret to get access token and refresh token
getAuthTokens: function(code, state, res, serviceCallback) {
// Google oAuth endpoint
var endpoint = "https://www.googleapis.com/oauth2/v4/token";
const scopes = state.scopes.split(" ");
// Setup request data
var data = {
code: code,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
redirect_uri: serviceCallback,
state: state.callback,
grant_type: "authorization_code"
};
request.post(endpoint).send(data).type('form').set('Accept',
'application/json').end(function(err, oAuthResponse) {});
},
I was using wrong endpoint url I changed it to different one to one provided by youtube api documentation and removed state parameter from data variable but still doesnt fix the problem
new endpoint url
var endpoint = "https://accounts.google.com/o/oauth2/token";
I´m really confused right now because I´m not forcing authorization and on google apps section there is my app already authorized and it does not update authorization that means it gives permission only first time and after that when I´m pressing allow it doesn´t do anything. OAuth should check if I have refresh token or not, so my conclusion is that I don´t fully understand how it should work or I´m somehow testing everything on debug or test mode where authorization prompt is automatically forced.
I would be really thankful for any kind of help because I feel like I tried everything.
The issue is that the access token that you are using has expired before the next time you use as you have not updated the access token manually using the refresh token.
You need to use the refresh token to update the access token if [ (time you last updated the access token) + (the expiry time) ] has already surpassed.
The concept of refresh tokens is that if an access token is compromised, as it is short-lived, the attacker has a limited time period in which it can be used. Refresh tokens, if compromised, are useless because the attacker requires the client id and client secret in addition to the refresh token in order to gain an access token.
The YouTube API documentation demonstrates the procedure here
By default, the expiry time is around 3 seconds.
This will surely, work in your case.
Adding the following parameter to your authentication object may help...depending on your requirements:
prompt: 'none'
This would mean no consent is gained or needed, after an initial authorization to use the app.
Go to the my accounts settings of google for this account---> go to connected apps and sites ----> manage apps:
Over there can you see the permissions for youtube like this:

How can I refresh a google plus bearer token (javascript)?

I'm using the google HTML sign-in button in my single page (javascript) application to obtain an authorization object from users with Google logins. This is detailed here: https://developers.google.com/+/web/signin/add-button.
I successfully receive back a token such as shown below. Since this token expires in 1 hour, I need to refresh the token every 30 minutes or so, until the user choses to log out. I am attempting this by calling:
gapi.auth.authorize({client_id: "90... ...92.apps.googleusercontent.com", scope: "profile email", immediate: true}, function() { console.log( arguments ); } );
but with no luck. I receive the same token back until it expires, after which I get back the empty (not signed in) token. How can I preserve / refresh the bearer token without the user having to continually log in again?
{
_aa: "1"
access_token: "ya29.1.AA... ...BByHpg"
authuser: "0"
client_id: "90... ...92.apps.googleusercontent.com"
code: "4/Nyj-4sVVcekiDnIgMFh14U7-QdRm.svPMQSODiXMbYKs_1NgQtmX9F90miwI"
cookie_policy: "single_host_origin",
expires_at: "1398341363",
expires_in: "3600",
g_user_cookie_policy: undefined,
id_token: "eyJhbGciOiJ... ...0Es1LI"
issued_at: "1398337763",
num_sessions: "2",
prompt: "none",
response_type: "code token id_token gsession",
scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
session_state: "b92d67080... ...73ae",
state: "",
status: {
google_logged_in: true,
method: "AUTO",
signed_in: true
},
token_type: "Bearer"
}
Using the client side flow (ie Java Script) you can only receive short-lived (~1 hour) access_token. If you want to be able to refresh it, you need a refresh_token which can only be obtained using the server side flow.
You can find more information here.
Basically,it works like this :
The user connects to your Website and clicks on the "Sign-in button"
You receive an access_token and a code in JavaScript
You send this code to a PHP Script on your web server
The script makes a request to Google Servers and exchanges your code for an
access_token(which should be identical to the one you just received in JavaScript) and a refresh_token
You need to store this refresh_token somewhere (in a data base for
example) because it will only be issued once (when the users grants
permission)
When one of your access_token is about to expire, you can use your
refresh_token to get another valid access_token
As well as setting a timer, you should check that your token is still valid before making the API call. Now that the client library returns promises, and promises are chainable, you can do it really elegantly.
See my gist here.

can't get access token using refresh token

I wrote desktop application on java, which have access to the Google drive. (it just uploads and downloads files).
At the moment access type is online. when I need to access files/folders to the drive, I
redirect he browser to a Google URL and get access code:
String code = "code that was returned from brouser"
GoogleTokenResponse response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
GoogleCredential credential = new GoogleCredential().setFromTokenResponse(response);
everything works well! but I need to have that redirection only first time.
When I google, in the Google Drive API documentation I found that I can get refresh token via browser redirection and save it on DB for instance. (In the other word, I can use offline access).
And every time when I need to read data from google drive, I get access token using refresh token without redirection. is not it?
so I get refresh token like that:
https://accounts.google.com/o/oauth2/auth?access_type=offline&client_id=695230079990.apps.googleusercontent.com&scope=https://www.googleapis.com/auth/drive&response_type=code&redirect_uri=https://localhost
question 1
I get code, from the browser redirecting. it's refresh token, is not it?
now, I need to get access token using that refresh token.
$.ajax({
type: "POST",
url: 'https://accounts.google.com/o/oauth2/token',
data: {
client_id: "695230079990.apps.googleusercontent.com",
client_secret: 'OWasYmp7YQ...4GJaPjP902R',
refresh_toke: '4/hBr......................xwJCgQI',
grant_type: 'refresh_token'
},
success: function(response) {
alert(response);
}
});
but I have error 400;
question 2) when I try to change redirect url I have that error: *
Invalid parameter value for redirect_uri: Non-public domains not allowed: https://sampl.ecom
so, must I create web applications Client ID , instead of installed application from google APIs console? Can't I change Redirect URI in installed application? I'm confused, I don't know, which should I use.
1) when you try to have offline access, you get authorization code which may be redeemed for an access token and a refresh token.
For isntance:
https://accounts.google.com/o/oauth2/auth?access_type=offline
&approval_prompt=auto
&client_id=[your id]
&redirect_uri=[url]
&response_type=code
&scope=[access scopes]
&state=/profile
after you get authorization code, you cat get refresh token.
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
See the section on Implementing Server-side Authorization tokens for more information.
and after you get refresh token , you must save it. see that sample for mor information.
2) If you don't have installed application, you should create web applications to change redirecting URL.

Resources