Describe the bug
I am building a mobile app with React Native, react-native-fbsdk and aws-amplify. I was able to successfully login with email and facebook using Cognito. Now the issue that I'm having is this:
After a while, I get "Access Denied" when I try to make API requests. This only happens with a facebook authed user in my system. It appears the facebook token is not getting refreshed, even though the expire date is set for a few months from now. Maybe this is an issue with Cognito not refreshing the federated credentials?
Anyone got any tips for making the token refresh?
Sidenote: I am also making a companion web app (in React), and that allows facebook auth as well. However, to make the webapp work, I had to wrap my whole app in some HOCs:
import FacebookProvider, { InitFacebook } from 'react-facebook-sdk';
function App(props) {
return(
<FacebookProvider appId='my-actual-app-id'>
<InitFacebook onReady={() => {}}>
<Router />
</InitFacebook>
</FacebookProvider>
);
}
I'm wondering if there's some react-native equivalent that I'm missing...
To Reproduce
Expected behavior
I expected Amplify to see that my access token is no longer good and use my facebook refresh token to get a new access token.
Smartphone (please complete the following information):
Versions
For Additional Reference
Here is my function that logs me in with facebook, gets my tokens, and Authorizes me with a federated identity:
import { API, Auth } from 'aws-amplify';
import {
LoginManager,
AccessToken,
GraphRequest,
GraphRequestManager
} from 'react-native-fbsdk';
handleFacebookLogin() {
let self = this;
// Attempt a login using the Facebook login dialog asking for default permissions.
LoginManager.logInWithReadPermissions(['public_profile', 'email'])
.then(
function(result) {
if (result.isCancelled) {
// do nothing
} else {
AccessToken.getCurrentAccessToken()
.then(data => {
let token = data.accessToken;
let expires_at = data.expirationTime;
let user = data;
let graphParams = { parameters: {} };
let infoRequest = new GraphRequest('/me', graphParams, (error, result) => {
if (error) {
console.log('Error fetching data: ' + error.toString());
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
} else {
let params = {
body: {
user: result
}
};
Auth.federatedSignIn('facebook', { token, expires_at }, user)
.then(result => {
API.post('HangerAPI', '/v1/auth/fb', params)
.then(response => {
self.handleNavigation('/home');
})
.catch(err => {
console.log(err);
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
});
})
.catch(err => {
console.log(err);
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
});
}
}
);
infoRequest.addStringParameter('id,name,email', 'fields');
// Start the graph request.
new GraphRequestManager().addRequest(infoRequest).start();
})
.catch(err => {
console.log(err);
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
});
}
},
function(error) {
console.log('Login fail with error: ' + error);
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
}
)
.catch(err => {
console.log(err);
this.setState({ alertMessage: 'Error authenticating with Facebook. Please try again.' });
});
}
@ASteinheiser Hi as you know the aws credentials will not be successfully refreshed if the token itself is expired. Amplify could refresh facebook tokens automatically in the web environment but not in React Native. You need to refresh it yourself or pass a refreshHandler to the Auth module: https://aws-amplify.github.io/amplify-js/media/authentication_guide#enabling-federated-identities -> refreshing jwt tokens.
Also can you make sure you are passing the correct expires_at value to Amplify. It is the timestamp when the token expires.
Okay, so in order to have this work properly, I will have to use the withAuthenticator, or the Authenticator react-native components?
This poses an issue as these components seem to enforce an authentication UI. I would like to not have to use those components if possible.
Would I be able to solve this problem by just adding a check for facebook token on my Auth component that wraps each route?
No, you don't need to use any HOC component. All you need to do is to pass a handler to the Auth module so it would use that to refresh the token when needed.
Understood! That's good news. I see now how this works, according to the docs that you linked. Thank you for your help and patience!!
Maybe you can take a look at this: https://github.com/aws-amplify/amplify-js/blob/master/packages/core/src/OAuthHelper/FacebookOAuth.ts#L39
So, just to confirm... If I configure the refresh handler on the Auth for 'facebook' it should work?
Auth.configure({
refreshHandlers: {
'facebook': refreshToken
}
});
@ASteinheiser yes
@powerful23 So when exactly should the refresh token function get called by Auth? It doesn't seem to get called when I reopen my app. I'm saving the token in the redux state, and expected that on app open, the refresh func would get called since the request fails.
@ASteinheiser the function will be called when the credentials and the tokens are both expired. It doesn't matter if you reopened your app or not. As long as the jwt token cached in the AsyncStorage is not expired, you should be able to get the credentials. Can you open the debug mode to get some debug info? put window.LOG_LEVEL='DEBUG' in your code.
Gotcha, I think I'm understanding. It may take up to a day or so for that jwt to expire, and then the refresh will be called?
@ASteinheiser yeah, it happens when some function tiggers the action to get you a new credentials, and then Auth module will read the token from the cache and find it expired, then will call the refresh function
Okay, I have one more question.
My current setup does not use the refreshToken handler, and just gets the cached token from AccessToken on react-native-fbsdk. This token appears to be a long-live token, as it's valid for more than a day (seems like it should last months). The web facebook sdk seems to use a short-live token, which needs a refresh every day.
Am I correct to assume that this is the case? And if so, then I can also assume that my token will eventually expire in a few months time?
@ASteinheiser it doesn't matter if it's a long live one or not, Amplify will check its expiration time and based on that it will trigger the refresh function. When you get the token from Facebook, you should also get a time which tells you when the token will expire right?
Yeah, I understand that. The only difference between long live and not is how long the expire time is.
The token I get back expires in a few months. So I assume at that point, it will try to refresh?
@ASteinheiser yes.
@powerful23 I'm confused as to why a refresh handler needs to be written for facebook when one is shown here in https://github.com/aws-amplify/amplify-js/blob/master/packages/core/src/OAuthHelper/FacebookOAuth.ts#L39
Is it because that handler is for React instead of React Native?
@sampocs yes that one is using the web sdk of FB so can only be used in web env
Closing this issue due to no further response. Feel free to reopen if you still have this issue.