Amplify-js: How to persist session over page refresh?

Created on 12 Mar 2019  路  17Comments  路  Source: aws-amplify/amplify-js

I am trying to move away from using the withAuthenticator HOC and use my own components to handle authentication to better integrate with the rest of the application. So far I have sign in, up, and out as well as verify email working (I am not using usernames). But I cannot make a signed in session persist over a page refresh and I cannot work out why.

I saw this issue and tried moving Amplify.configure to index.js before React renders anything but this has not changed anything. https://github.com/aws-amplify/amplify-js/issues/2480

* Which Category is your question related to? *
Authentication

* What AWS Services are you utilizing? *
Cognito User Pool

* Provide additional details e.g. code snippets *

Auth question

All 17 comments

Sounds like Amplify isn't being configured as the first statement in the program.

Where in your app are you configuring Amplify?

(@jordanranz helped me solve a similar issue not too long ago)

Yes, the issue that you opened is the one I found and tried to copy without success. This is my index.js, I don't know if I can configure Amplify any earlier than this can I?

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import registerServiceWorker from './registerServiceWorker';
import Amplify from 'aws-amplify';

import App from './Components/App/App';

Amplify.configure({
    Auth: {
        identityPoolId: "",
        region: 'eu-west-1',
        userPoolId: '',
        userPoolWebClientId: '',
        mandatorySignIn: true,
    },
    Analytics: {
        AWSPinpoint: {
            appId: '',
            region: 'eu-west-1',
            mandatorySignIn: false
        }
    }
});

ReactDOM.render((
    <BrowserRouter>
        <App />
    </BrowserRouter>
), document.getElementById('root'));

registerServiceWorker();

IDs etc redacted, the values are not empty strings in the app.

I think you want to configure at the top of your App.jsx. That's where our projects now run Amplify.configure(aws_exports) and persist auth across refresh. The App component itself is still rendered from index.js.

Yea as @jkeys-ecg-nmsu mentioned,

Try and move
import Amplify from 'aws-amplify'
and
Amplify.configure(...)

to the top of the./Components/App/App' file

Here is a relevant example.

Yeah, this was how I had it configured before and moved it into index just in case that would make any difference, moved it back to App and it hasn't changed anything.

import React from 'react';
import Amplify from 'aws-amplify';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ApolloProvider } from 'react-apollo'

import NavBar from '../NavBar/NavBar';

import { client } from '../../ApolloClient/ApolloClient';

Amplify.configure({
  Auth: {
      identityPoolId: "",
      region: '',
      userPoolId: '',
      userPoolWebClientId: '',
      mandatorySignIn: true,
  },
  Analytics: {
      AWSPinpoint: {
          appId: '',
          region: '',
          mandatorySignIn: false
      }
  }
});

class App extends React.Component {
  render() {
    return (
      <React.Fragment>
        <CssBaseline />
        <ApolloProvider client={client}>
          <NavBar />
        </ApolloProvider>
      </React.Fragment>
    );
  }
}

export default App;

Could you paste your index.js? I think @jordanranz is saying not to import amplify in index.js.

This is index.js now:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import registerServiceWorker from './registerServiceWorker';

import App from './Components/App/App';

ReactDOM.render((
    <BrowserRouter>
        <App />
    </BrowserRouter>
), document.getElementById('root'));

registerServiceWorker();

I wonder if wrapping App in BrowserRouter is causing the issue. Can you try adding the BrowserRouter to the actual App component instead of wrapping it in index.js?

A good idea, unfortunately it hasn't worked :(

Have you tried setting a breakpoint on ReactDOM.render() and Amplify.configure() to see if any other statements are being executed before it?

Oh, and try making your top-level Amplify.configure() only configure the Auth category. Configure your analytics afterward with Analytics.configure() (I think).

To test these ideas I first moved everything except Amplify.confgure() out of App and into a secondary App like so and removed the Analytics configuration:

import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';

import App from './Components/App/App';

ReactDOM.render((
    <App />
), document.getElementById('root'));

registerServiceWorker();
import React from 'react';
import Amplify from 'aws-amplify';

import AppWithApollo from '../AppWithApollo/AppWithApollo';

Amplify.configure({
  Auth: {
      region: '',
      userPoolId: '',
      userPoolWebClientId: '',
  }
});

class App extends React.Component {
  render() {
    return (
      <AppWithApollo />
    );
  }
}

export default App;
import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import CssBaseline from '@material-ui/core/CssBaseline';
import { ApolloProvider } from 'react-apollo'

import NavBar from '../NavBar/NavBar';

import { client } from '../../ApolloClient/ApolloClient';

class AppWithApollo extends React.Component {
    render() {
        return (
            <BrowserRouter >
                <React.Fragment>
                    <CssBaseline />
                    <ApolloProvider client={client}>
                        <NavBar />
                    </ApolloProvider>
                </React.Fragment>
            </BrowserRouter>
        );
    }
}

export default AppWithApollo;

None of this worked. Then I cleared the browser data, didn't work. Tried refreshing with the toolbar button in case CMD + R was clearing local data, didn't work.

Then put break points in and couldn't see anything obvious (though this might be inexperience so if anyone has any pointers on what to look for that would be amazing). But realised that configuring Apollo client touches Auth to get the tokens for submission to the Apollo server and that is then imported into AppWithApollo, being an import I thought that it might take place before config, a break point confirmed that it was, but moving amplify config into it didn't work (but I could login so it was still configuring properly).

Then tried it in Firefox just in case, didn't work.

Finally I added the withAuthenticator HOC to App and that worked perfectly immediately. So what do I have to do to get that behaviour without using the HOC?

@Exitialis1 Are you configuring Amplify before initializing your Apollo client? Maybe move the client initialization to your render function or a lifecycle method, and keep Amplify.configure at the top-level?

(Also, lol at this idea, but have you considered exporting your component as a CommonJS module or using babel or something so you can require your App (which will let you do it beneath a statement)?)

FWIW we use withAuthenticator for our production projects because (1) our apps sit behind Cognito, (2) it's extensible and (3) it's written and maintained by a dedicated team.

Finally I added the withAuthenticator HOC to App and that worked perfectly immediately. So what do I have to do to get that behaviour without using the HOC?

You could try not importing the client (seems like a strong candidate for the problem), but instead importing apollo-client or apollo-boost and instantiating it:

import ApolloClient from 'apollo-boost';
import aws_exports from 'aws-exports.js';
Amplify.configure(aws_exports);
const client = new ApolloClient();

@jordanranz any thoughts?

No, I moved it into AppWithApollo's Render method and it made no difference.

I did start using withAthenticator for the same reasons that you list but I didn't like the user experience of just being presented with a sign-up box and no context for what the application is or why they should sign up. If I was making an internal tool then this wouldn't be an issue but this application is going to be customer facing and I want a proper user experience with the authentication actions integrated into the rest of the UI.

Apollo client has some setup to do before it can be used, it isn't just instantiated, which is why it was in a separate file. But even moving this entirely into AppWithApollo's Render method like I said above has not changed anything. I even removed the call to Auth from the setup (it uses it to get the access token) didn't fix it so I don't think that it is Apollo Client causing the issue.

Don't respond to this, I think that I have realised what is going on but haven't had a chance to test it fully yet, I will update when I get the time.

Okay, won't do. ;)

This was all caused by me using Auth.user as a test of whether there was an active session or not. I have no idea where I get Auth.user from, it isn't listed in any of the documentation and from an educated guess it is used as a cache of the session as it is populated whenever anything touches a session (sign in and Auth.currentSession() both populate it for example). As I was using it as a test of session for UI components it gave the impression that there was no session when in fact I just hadn't retrieved the session.

Sorry and thank you for all of the help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ldgarcia picture ldgarcia  路  3Comments

karlmosenbacher picture karlmosenbacher  路  3Comments

DougWoodCDS picture DougWoodCDS  路  3Comments

cgarvis picture cgarvis  路  3Comments

epicfaace picture epicfaace  路  3Comments