Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access Token Timeout #1

Closed
JpEncausse opened this issue Nov 23, 2014 · 7 comments
Closed

Access Token Timeout #1

JpEncausse opened this issue Nov 23, 2014 · 7 comments

Comments

@JpEncausse
Copy link

Hi,
I'm using refresh with Google OAuth, how do you handle token timeout ?

  • Should I ask for a accessToken refresh every time ?
  • Where can I find accessToken timeout (passport only provide accessToken, refreshToken, profile)
  • I don't want to wait for an error to perform the refresh
  • Should I assume 1 hour ?

Regards

@fiznool
Copy link
Owner

fiznool commented Nov 24, 2014

I personally don't use a recurring task to refresh my tokens, instead I just handle the error when using an expired access token and refresh the token automatically.

Let's assume you are using Mongoose and Passport to log the user in with their Google account, and you then set a session token with that user once logged in using serializeUser():

var passport = require('passport'),
    refresh = require('passport-oauth2-refresh'),
    GoogleStrategy = require('passport-google-oauth').Strategy;

passport.serializeUser(function(user, done) {
  done(null, user._id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

var strategy = new GoogleStrategy({
  clientID: GOOGLE_APP_ID,
  clientSecret: GOOGLE_APP_SECRET,
  callbackURL: "http://www.example.com/auth/google/callback"
},
function(accessToken, refreshToken, profile, done) {
  // Store the refresh token with the user profile.
  var user = {
    email: profile.emails[0],
    accessToken: accessToken,
    refreshToken: refreshToken
  };
  User.findOneAndUpdate({ email: user.email }, user, { upsert: true }, function(err, user) {
    if (err) { return done(err); }
    done(null, user);
  });
});

passport.use(strategy);
refresh.use(strategy);

When you later need to use the accessToken to request some data from the Google API, catch a 401 error from Google and refresh the token automatically, then run the request again with the (hopefully) new access token. The following example uses the Google JavaScript API bindings.

var google = require('googleapis'),
    refresh = require('passport-oauth2-refresh');

// This is an express callback.
function getGmailProfile(req, res, next) {
  var retries = 2;

  var send401Response = function() {
    return res.status(401).end();
  };

  // Get the user's credentials.
  User.findById(req.user, function(err, user) {
    if(err || !user) { return send401Response(); }

    var makeRequest = function() {
      retries--;
      if(!retries) {
        // Couldn't refresh the access token.
        return send401Response();
      }

      // Set the credentials and make the request.
      var auth = new google.auth.OAuth2;
      auth.setCredentials({
        access_token: user.accessToken,
        refresh_token: user.refreshToken
      });

      var gmail = google.gmail('v1');
      var request = gmail.users.getProfile({
        auth: auth,
        userId: 'me'
      });
      request.then(function(resp) {
        // Success! Do something with the response
        return res.json(resp);

      }, function(reason) {
        if(reason.code === 401) {
          // Access token expired.
          // Try to fetch a new one.
          refresh.requestNewAccessToken('google', user.refreshToken, function(err, accessToken) {
            if(err || !accessToken) { return send401Response(); }

            // Save the new accessToken for future use
            user.save({ accessToken: accessToken }, function() {
             // Retry the request.
             makeRequest();
            });
          });

        } else {
          // There was another error, handle it appropriately.
          return res.status(reason.code).json(reason.message);
        }
      });
    };

    // Make the initial request.
    makeRequest();
  });
}

As you can see, I have used a recursive function in order to automatically refresh the token in the case of a 401 being returned by the GMail API. The recursion is guarded with a retry counter so that any further failures don't cause an infinite loop.

@JpEncausse
Copy link
Author

Ok thanks I understood.

In my case it's a third party code that request an accessToken to do something. So I try to keep the code as simple as possible for this part.

The function that provide the accessToken keep the last refresh date and refresh the token if it is older than an hour.

@fiznool
Copy link
Owner

fiznool commented Nov 24, 2014

If you want to try and pre-empt the accessToken expiring, you can get the accessToken timeout from the returned profile object:

var expiry = profile._json.expires_in;

See the Google docs for more details on the returned fields.

However, I recommend taking the approach above, instead - you will likely need some sort of error handling in case the scheduled task does not refresh the token correctly, and by using the recursive technique, the process will be transparent to the user.

@jtabone16
Copy link

Anyone have using this with Spotify's web API?

@fiznool
Copy link
Owner

fiznool commented Aug 11, 2015

@jtabone16 I haven't used the Spotify API. Is there a particular problem that you are facing? If so it would be best to create a new issue.

@jtabone16
Copy link

@fiznool Got everything working finally haha it was an issue I found in my spotify strategy when setting providerIdentifierField. initially, it was set to 'id_str' (used twitter strategy as boilerplate). however, I needed to use 'id' for providerIdentifierField

Glad I finally found this bug since it's caused a TON of issues that I couldn't find the source of until now!

@fiznool
Copy link
Owner

fiznool commented Aug 11, 2015 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants