react-google-login
react-google-login copied to clipboard
Access Token expires after an hour
I assumed libraries like this take care of this sort of thing automagically. Is there a built-in way to handle this?
I assumed setting the accessType prop to "offline" would take care of this, but it didn't seem to work. According to this comment that's what needs to be done https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token#comment56214632_10857806
I guess I have to use the responseType of "code" according to the docs which state that the accessType "Can be either 'online' or 'offline'. Use offline with responseType 'code' to retrieve a refresh token", but I'll have wait an hour to see if it actually works -__-
Having accessType set to "offline" and responseType set to "code" did not work. How do we get the access token refreshed automatically?
I encountered the same issue and honestly I'm not going to say I've got an elegant solution, but I do have a workable solution. I've edited it down to something understandable below. I can guarantee that the following won't work without some edits and typo fixes.
render() {
....
return (
<div>
<MyComponentThatNeedsAnAuthToken
tokenGetter={this.tokenGetter}
googleUser={this.state.googleUser}
/>
<GoogleLogin
...
onSuccess={this.loginSuccess}
/>
</div>)
}
loginSuccess = (googleUser) => {
this.setState({
googleUser: googleUser,
});
this.accessToken = googleUser.tokenObj.access_token
this.setRefreshTimeout(googleUser.tokenObj.expires_at);
}
// Keeping the token out of "state" means that the automatic refresh of an
// access token will not cause the entire application's component to re-render.
tokenGetter = () => {
return this.accessToken;
}
setRefreshTimeout = (expiresAt) => {
// Either 5 minutes before the deadline, or 5 minutes from now. This
// should prevent thrashing while keeping the credentials fresh.
const oneMin = 60 * 1000;
var refreshDeadline = Math.max(
5*oneMin,
expiresAt - Date.now() - (5*oneMin));
console.log("Refreshing credentials in "
+ Math.floor(refreshDeadline/oneMin).toString()
+ " minutes");
setTimeout(this.reloadAuthToken, refreshDeadline);
}
reloadAuthToken = () => {
this.state.googleUser.reloadAuthResponse().then(
// success handler.
(authResponse) => {
// The GoogleUser is mutated in-place, this callback updates component state.
this.accessToken = authResponse.access_token;
this.setRefreshTimeout(authResponse.expires_at);
},
// fail handler.
(failResponse) => {
this.accessToken = "";
console.log("Could not refresh token");
console.log(failResponse);
}
);
}
Thanks for posting your code. That looks like a good solution. It will be tricky to implement for me though since I have a login app that redirects to my main app.
Since I redirect to another app and don't have that reloadAuthToken available to me anymore, I just have them login again if their token expires. It can be quite painless if you have few options
const storeToken = (response, props) => {
// store accessToken
// retry action
functionInvolveingGoogleAPI(props)
}
const relogin = (props) => {
const { gapi, user } = props
gapi.auth2.init({
client_id: 'myclientid'
})
const auth2 = gapi.auth2.getAuthInstance()
auth2.signIn({
promp: 'none'
, login_hint: user.email
, scope: "all of the scopes that you need"
})
.then(res => storeToken(res, props), err => console.error(err))
}
const functionInvolvingGoogleAPI = async function (props) {
const response = await callGoogleAPI()
if (response.error && response.error.status === 'UNAUTHENTICATED') {
relogin(props)
} else {
// do stuff with response
}
}
I haven't tested this much so it may need some tweeking
Thanks for the posted code..but the issue is that it does not work. I have to send a code to my backend for verification but the code generated is not of offline use even when i set accessType "offline" and responseType "code"
I was mistaken. The code was 100% as expected. I found this under step 6 of https://developers.google.com/identity/sign-in/web/server-side-flow
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. You must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token. This flow provides increased security over your standard OAuth 2.0 flow.
In order to get the refresh token for subsequent calls you need to set the prompt
prop to consent
like so:
<GoogleLogin
...
responseType="code"
prompt="consent"
...
/>
does this mean we need to change the button props after the first signin?
it is a bit confusing because the refresh can only be rerrieved if the value in the form is changed so it looks like we have to connect twice.
So, after tinkering around how to get the reloadAuthResponse() method after redirecting/refreshing to another page. You can still access this method by calling the window object like: window.gapi.auth2.getAuthInstance().j8.currentUser.Ab.reloadAuthResponse()
This should return you a new access_token and use that token to make requests to google API
@galoncyryll where does window.gapi come from? i tried to look into that object but it's undefined.
Thanks @eap your solution works for me and I don't know why they don't mention it here
@chitgoks include this in your index.html
<script src="https://apis.google.com/js/client.js"></script>
for window.gapi
@chitgoks I have something to this effect
window.gapi.load('auth2', () => {
window.gapi.auth2.getAuthInstance().then((auth2) => {
const user = auth2.currentUser.get();
if (!user) {
return;
}
user.reloadAuthResponse().then(onGoogleLoginSuccess);
}
i got it to work. i placed mine in an interceptor and only reload the access token if its say, 55 minutes near expiration.
then i call init first so auth wont be null. rather than loading the auth object everytime.
According to README, The response in onSuccess
handler is also a GoogleUser Object, which provides the methods as calling gapi
.
Here is my code to refreshing token automatically.
const onLoginSuccess = res => {
if (res) {
// Sometime `res.accessToken` is undefined
// saveUserToken(res.getAuthResponse(true).access_token); <-- save token
refreshTokenSetup(res);
}
};
/**
* The setup for refreshing token automatically
*
* @param res GoogleLoginResponse
*/
const refreshTokenSetup = res => {
// Timing to renew access token
let refreshTiming = (res.tokenObj.expires_in || 3600 - 5 * 60) * 1000;
const refreshToken = async () => {
const newAuthRes = await res.reloadAuthResponse();
refreshTiming = (newAuthRes.expires_in || 3600 - 5 * 60) * 1000;
// saveUserToken(newAuthRes.access_token); <-- save new token
// Setup the other timer after the first one
setTimeout(refreshToken, refreshTiming);
};
// Setup first refresh timer
setTimeout(refreshToken, refreshTiming);
};
let refreshTiming = (res.tokenObj.expires_in || 3600 - 5 * 60) * 1000;
I was banging my head from last 2 days for a way to get refresh token inorder to refresh my access_token once it is expired. This was all that I needed. Thanks a ton! No need of refresh token to refresh access token with this method.✨
According to README, The response in
onSuccess
handler is also a GoogleUser Object, which provides the methods as callinggapi
.Here is my code to refreshing token automatically.
const onLoginSuccess = res => { if (res) { // Sometime `res.accessToken` is undefined // saveUserToken(res.getAuthResponse(true).access_token); <-- save token refreshTokenSetup(res); } }; /** * The setup for refreshing token automatically * * @param res GoogleLoginResponse */ const refreshTokenSetup = res => { // Timing to renew access token let refreshTiming = (res.tokenObj.expires_in || 3600 - 5 * 60) * 1000; const refreshToken = async () => { const newAuthRes = await res.reloadAuthResponse(); refreshTiming = (newAuthRes.expires_in || 3600 - 5 * 60) * 1000; // saveUserToken(newAuthRes.access_token); <-- save new token // Setup the other timer after the first one setTimeout(refreshToken, refreshTiming); }; // Setup first refresh timer setTimeout(refreshToken, refreshTiming); };
Does this solution work when the page is refreshed? The refreshTokenSetup seems to be called on loginSuccess only. Is refreshing token based on expires_in enough for handling page refresh or expires_at needs to be considered?
Can confirm that this solution does not work when the page is refreshed. It helps a lot thought in the case user doesn't refresh the page
@smarajitdasgupta @maksimf The only thing I do to make it work when refresh the page is trying to trigger the login logic first.
- I have a
<Dashboard>
component, which is something as following. Any other valid routes will go to/dashboard
first.
// Dashboard.js
const Dashboard = (isLoggedIn) => { // `isLoggedIn` with default value `false` is the state in Redux
// ...code
return isLoggedIn ? Layout : <Redirect to="/login" />;
}
- Set
isSignedIn
totrue
in Google Login, which leads users to login automatically if their access token are still valid. And it also call the callbackonLoginSuccess
when the auto logic is finished.
// Login.js
const onLoginSuccess = res => {
if (res) {
// the action to change `isLoggedIn` state.
userLogin(); // <--- this line is the only difference
// Sometime `res.accessToken` is undefined
// saveUserToken(res.getAuthResponse(true).access_token); <-- save token
refreshTokenSetup(res);
}
};
From what I can see in the code above, refresh token logic won't be triggered unless isLoggedIn
is false
. Correct me if I'm wrong here. So, we could end up with the situation when user has refreshed the page, we did not refresh the token (isLoggedIn
is true
) and the token just expires in, say 10 minutes.
Is this a possibility or am I not getting it?
@maksimf The default value of isLoggedIn
is false
. I have updated the comment above.
So is there any way to refresh token even after page refresh?
According to README, The response in
onSuccess
handler is also a GoogleUser Object, which provides the methods as callinggapi
.Here is my code to refreshing token automatically.
const onLoginSuccess = res => { if (res) { // Sometime `res.accessToken` is undefined // saveUserToken(res.getAuthResponse(true).access_token); <-- save token refreshTokenSetup(res); } }; /** * The setup for refreshing token automatically * * @param res GoogleLoginResponse */ const refreshTokenSetup = res => { // Timing to renew access token let refreshTiming = (res.tokenObj.expires_in || 3600 - 5 * 60) * 1000; const refreshToken = async () => { const newAuthRes = await res.reloadAuthResponse(); refreshTiming = (newAuthRes.expires_in || 3600 - 5 * 60) * 1000; // saveUserToken(newAuthRes.access_token); <-- save new token // Setup the other timer after the first one setTimeout(refreshToken, refreshTiming); }; // Setup first refresh timer setTimeout(refreshToken, refreshTiming); };
isSignedIn={true}
automatically refreshes the token even after page refresh.Using ``isSignedIn={true}
with above code I was able to auto refresh and save the token as isSignedIn={true}
calls the onSuccess
callback on reload.
In my private route I am using this function
function _isLoggedIn() {
let user = _getUserDetails(); //fetch data from localStorage
if (user && (user.accessToken || user.access_token)&&((_getUserDetails().tokenObj.expires_at-new Date().getTime())/60000>5)) return true;
return false;
}
Since isSignedIn={true}
the google login prompt automatically closes and the token is refreshed and then stored in localStorage
On my machine setTimeout doesn't reliably fire after coming back from a suspend
Is this a reliable solution? Does it work after coming back from a suspend?