17

I have been trying to implement an app that would need user to grant access to Google Analytics. I have been following this tutorial:

https://developers.google.com/analytics/solutions/articles/hello-analytics-api

And at some other places there is code for AngularJs which uses the same functionL

https://gist.github.com/jakemmarsh/5809963

My problem is, that the auth works pretty well, but it does not return a refresh_token. It never returns a refresh_token. I have tried all the possible available on the web. 1. The first time, 2. Using prompt=force etc etc.. But nothing seems to return the refresh_token. I guess that part is skipped by the client or something.

I need to know how can I get the refresh_token when the user grants access for the first time so that I can save it.

Sambhav Sharma
  • 5,741
  • 9
  • 53
  • 95

4 Answers4

30

It does not return a refresh token as designed. The tutorial and the code you mentioned are using Google APIs Client Library for JavaScript. This library uses the OAuth 2.0 client-side flow for making requests that require authorization.

As The OAuth 2.0 Authorization Framework says:

The implicit grant type is used to obtain access tokens (it does not support the issuance of refresh tokens) and is optimized for public clients known to operate a particular redirection URI. These clients are typically implemented in a browser using a scripting language such as JavaScript.

In fact, The authorization code flow is the only one which issue refresh token, and Google supports this flow in these Scenarios: Web server applications, Installed applications, and Applications on limited-input devices, but not Client-side (JavaScript) applications or Service accounts. Get more details from here.

So you'll not get refresh token in this way.

Community
  • 1
  • 1
Owen Cao
  • 7,955
  • 2
  • 27
  • 35
13

Not sure if this is the right way, but I'm able to get a refresh token using the following code:

window.gapi.client.init({
  apiKey: this.GOOGLE.API_KEY,
  clientId: this.GOOGLE.CLIENT_ID,
  discoveryDocs: DISCOVERY_DOCS,
  scope: SCOPES
}).then(()=>{
  const authInstance = window.gapi.auth2.getAuthInstance();
  authInstance.grantOfflineAccess()
   .then((res) => {
      console.log(res);
      this.data.refreshToken = res.code;
   });
});
hugo der hungrige
  • 12,382
  • 9
  • 57
  • 84
  • 1
    .grantOfflineAccess() method returns code which we can use to get accessToken and refreshToken using token API. – krishna_5c3 Jun 15 '18 at 18:55
  • and for some reason when calling fetch, XHR, or `gapi.client.request` against the token endpoint gives an response of `{"error":"redirect_uri_mismatch", "error_description":"Bad Request"}` – Chris Rutherford Jan 16 '19 at 17:10
  • @krishna_5c3 How can we get access token and refresh token from the response_code return by grantOfflineAccess() method? possible for you to share the token api link? – chetan mekha Feb 22 '19 at 11:49
  • @chetanmekha, after we get **code** we are making server to server call to get **access_token** and **refresh_token**. Here is the sample code. `final PostMethod post = new PostMethod("https://www.googleapis.com/oauth2/v4/token"); post.addRequestHeader("content-type", "application/x-www-form-urlencoded"); post.addParameter("code", code); post.addParameter("client_id", client_id); post.addParameter("client_secret", client_secret); post.addParameter("redirect_uri", redirectUrl); post.addParameter("grant_type", "authorization_code");` – krishna_5c3 Mar 11 '19 at 15:51
2

TLDR; Set query params to response_type='code' and access_type='offline'.

I got around this by doing the "server" flow authorization. Basically you do the same thing as the "implicit" flow, by redirecting the user, but change the response_type from token --> code (also set access_type=offline). This gives you an authorization code, which you can then use to POST to the Google OAuth service documented in the Exchange code for access token and ID token section.

Matt Goo
  • 1,118
  • 1
  • 11
  • 12
  • Yeah, surely we can do it that way, but you have to reveal your CLIENT_SECRET in your JS source, which is obviously not favorable. – goodhyun Feb 20 '20 at 11:51
  • 3
    I get around that by sending it through the backend. The backend has the client secret/id. That is hidden in a KMS. – Matt Goo Feb 26 '20 at 06:16
0

I go then authentication token using a package. You can get this token following other ways also. Install a npm package for your google login. Check this npm package.

Add scope and flow in the GoogleLogin function to get the authentication code.

import { GoogleLogin } from '@react-oauth/google';

<GoogleLogin
  scope: 'https://mail.google.com/',
  flow: 'auth-code',
  onSuccess={credentialResponse => {
    console.log(credentialResponse);
  }}
  onError={() => {
    console.log('Login Failed');
  }}
/>;

Success response will look like this:

{
  authuser: "0",
  code: "********KAjJZmv4xLvbAIHeySg",
  hd: "myalice.ai",
  prompt: "consent",
}

Here, we need the code part only.

Generating refresh and access token:

Send the code you got from the response inside the payload.

let payload = {
  grant_type: 'authorization_code',
  code: "********KAjJZmv4xLvbAIHeySg",
  client_id: '******.googleusercontent.com',
  client_secret: 'GOCS*******m5Qzg',
  redirect_uri: 'http://localhost:3000',
};

axios
  .post(`https://oauth2.googleapis.com/token`, payload, {
    headers: {
      'Content-Type': 'application/json;',
    },
  })
  .then((res: any) => {
    return res.data;
  })
  .then((response: any) => {
    console.log('refresh token: ', response);
  })
  .catch((err) => console.log('err: ', err));

You will get a response like this:

{
    access_token: "********KAjJZmv4xLvbAIHey",
    expires_in: 3599,
    id_token: "***************VeM7cfmgbvVIg",
    refresh_token: "***************VeM7cfmgbvVIg",
    scope: "https://www.googleapis.com/auth/gmail.readonly openid 
            .....
            .....
            https://mail.google.com/",
    token_type: "Bearer",
}

Save your refresh and access token. Now you can use refresh token to generate new access token! Here is an article on how you can integrate this on your react application. You can check this out for more details.

Sayad Ahmed Shaurov
  • 499
  • 1
  • 7
  • 10