Amplify-js: react-native-fbsdk and AWS Amplify token refresh

Created on 9 Oct 2018  路  18Comments  路  Source: aws-amplify/amplify-js

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

  • Create a new React Native project.
  • Implement the react-native-fbsdk.
  • At first, you will be able to make API requests and such as expected.
  • Then, after some time, you will get "Access Denied" on all requests.

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):

  • Device: Google Pixel, reproducible on iOS simulator as well
  • OS: Android 9, iOS 12

Versions

  • aws-amplify: 1.0.2
  • react: 16.3.1
  • react-native: 0.55.4
  • react-native-fbsdk: 0.7.0

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.' });
    });
  }
Auth question

All 18 comments

@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!!

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ddemoll picture ddemoll  路  3Comments

cosmosof picture cosmosof  路  3Comments

lucasmike picture lucasmike  路  3Comments

romainquellec picture romainquellec  路  3Comments

guanzo picture guanzo  路  3Comments