4

I write an example about google api using. Google NodeJS Client library. I have followed the instruction set access_type : 'offline', however the object return doesn't contains refresh_token.

My Code:

var http = require('http');
var express = require('express');
var Session = require('express-session');
var google = require('googleapis');
var plus = google.plus('v1');
var OAuth2 = google.auth.OAuth2;
const ClientId = "251872680446-rvkcvm5mjn1ps32iabf4i2611hcg086e.apps.googleusercontent.com";
const ClientSecret = "F1qG9fFS-QwcrEfZbT8VmUnx";
const RedirectionUrl = "http://localhost:8081/oauthCallback";

var app = express();
app.use(Session({
    secret: 'raysources-secret-19890913007',
    resave: true,
    saveUninitialized: true
}));

function getOAuthClient () {
    return new OAuth2(ClientId ,  ClientSecret, RedirectionUrl);
}

function getAuthUrl () {
    var oauth2Client = getOAuthClient();
    // generate a url that asks permissions for Google+ and Google Calendar scopes
    var scopes = [
      'https://www.googleapis.com/auth/plus.me'
    ];

    var url = oauth2Client.generateAuthUrl({
        access_type: 'offline',
        scope: scopes // If you only need one scope you can pass it as string
    });

    return url;
}

app.use("/oauthCallback", function (req, res) {
    var oauth2Client = getOAuthClient();
    var session = req.session;
    var code = req.query.code;
    oauth2Client.getToken(code, function(err, tokens) {
        console.log("tokens : ", tokens); 
          // Now tokens contains an access_token and an optional refresh_token. Save them.
          if(!err) {
            oauth2Client.setCredentials(tokens);
            session["tokens"]=tokens;
            res.send(`
                <html>
                <body>
                    <h3>Login successful!!</h3>
                    <a href="/details">Go to details page</a>
                <body>
                <html>
            `);
          }
          else{
            res.send(`
                <html>
                <body>
                    <h3>Login failed!!</h3>
                </body>
                </html>
            `);
          }
    });
});

app.use("/details", function (req, res) {
    var oauth2Client = getOAuthClient();
    oauth2Client.setCredentials(req.session["tokens"]);

    var p = new Promise(function (resolve, reject) {
        plus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
            console.log("response : " , response);
            resolve(response || err);
        });
    }).then(function (data) {
        res.send(`<html><body>
            <img src=${data.image.url} />
            <h3>Hello ${data.displayName}</h3>
            </body>
            </html>
        `);
    })
});

app.use("/", function (req, res) {
    var url = getAuthUrl();
    res.send(`
        <html>
        <body>
<h1>Authentication using google oAuth</h1>
        <a href=${url}>Login</a>
        </body>
        </html>
    `)
});


var port = 8081;
var server = http.createServer(app);
server.listen(port);
server.on('listening', function () {
    console.log(`listening to ${port}`);
});
Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
hung.dev
  • 179
  • 1
  • 1
  • 8
  • what do you mean ? – hung.dev Mar 01 '17 at 03:44
  • I don't understand. Actually I use this piece of code http://voidcanvas.com/googles-oauth-api-node-js/ . Can you help me fix that ? – hung.dev Mar 01 '17 at 03:48
  • There is no error. Follow https://github.com/google/google-api-nodejs-client/ when use set access_type: 'offline' in generateAuthUrl the token return will contain refresh_token, but i set and it not work. – hung.dev Mar 01 '17 at 03:53
  • You can check line 30 on http://voidcanvas.com/googles-oauth-api-node-js/ – hung.dev Mar 01 '17 at 04:07
  • no, it not my code. Can you fix that ? – hung.dev Mar 01 '17 at 04:12
  • I want return object contain refresh_token. Can you do that ? – hung.dev Mar 01 '17 at 04:14
  • Until you post the code you are having an issue with, you will be hard pressed getting any answers - check line 37 – Jaromanda X Mar 01 '17 at 04:18
  • Hey, I update question with my code. Can you check it again ? – hung.dev Mar 01 '17 at 04:22
  • Wow - that only took 40 minutes! I'm bored now, hopefully someone else will give you help ... P.S. line 37 looks OK – Jaromanda X Mar 01 '17 at 04:23
  • Line 43 says `Now tokens contains an access_token and an optional refresh_token. Save them.` ... it's optional! Maybe that's why you aren't getting it – Jaromanda X Mar 01 '17 at 04:24
  • // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', . When you set access_type to offline. return object will contain refresh_token. The guided said that – hung.dev Mar 01 '17 at 04:31
  • @hung.dev please be aware that Stack is NOT a coding service we are not going to fix your code. We are here to help advice you on how YOU can fix YOUR code. – Linda Lawton - DaImTo Mar 01 '17 at 07:36

3 Answers3

5

The refresh token is only sent once the first time user login to your application after approving the scopes you have specified.

Edit 08/2018 : Using approval_prompt:'force' no longer works, you need to use prompt:'consent' (check @Alexander's answer)

If you want to get the refresh token each time user login (even if user has already login before and approved the scopes), you have to specify prompt:'consent' in Oauth2Client configuration :

var url = oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: scopes,
    prompt : 'consent'
});

Note that this will require user to accept the specified scope each time he/she will click on your link to authenticate :

enter image description here

You can also disable manually the application in your account permission settings, this will revoke the application and the user will have to accept the scopes again that will trigger the refresh_token to be sent the next time you authenticate :

enter image description here

FYI, if you need to use access_token offline, you have to store the refresh_token server side, and refresh the access_token with the stored refresh_token when you receive status 401 from Google API. So, if you store refresh_token as you should, there is actually no need to use prompt:'consent' and force user to approve the scopes each time he/she connects to your application.

Bertrand Martel
  • 42,756
  • 16
  • 135
  • 159
1

According the documentation the refresh_token is only returned on the first authorization.

You can remove the permissions manually on: https://myaccount.google.com/permissions

Also you can force the user to see the consent screen again by passing &prompt=consent in the authorization URL, just add this parameter:

var url = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: scopes,
  prompt: 'consent'
});

It worked just fine for me :)

Alexander
  • 7,484
  • 4
  • 51
  • 65
0

In some cases (Web clients mostly) the refresh token is only sent the first time the user is authenticated.

If you go to apps connected to your account remove the app in question. Then try and authenticated again. Check and there should now be a refresh token.

Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449