Amplify-js: AWS Amplify with isomorphic rendering

Created on 21 Mar 2018  路  18Comments  路  Source: aws-amplify/amplify-js

I'm currently integrating Amplify/Cognito into a server side rendered Angular 5 application. Obviously the server side rendered application has no access to local storage so I've configured Ampify to use cookie storage instead

Auth: {
  ...
      cookieStorage: {
        domain: 'localhost',
        expires: 365,
        path: '/',
        secure: false
      },
  ...
}

But when the server rendered application is loaded it shows these two errors when it tries to retrieve the cookies

[WARN] 51:02.95 Cache - getItem failed! TypeError: Cannot read property 'getItem' of null
[WARN] 51:02.140 Cache - getItem failed! TypeError: Cannot read property 'getItem' of null

I'll hazard a guess and say that's because it's using document.cookie and that doesn't exist in the node environment. Are there any suggestions on how I can get the server rendered application to read the cookies from Amplify?

Most helpful comment

Nah, it's looking for localStorage.

If you grep the aws-amplify code in node_modules, you'll see this:

 function BrowserStorageCache(config) {
        var _this = this;
        var cacheConfig = config ? Object.assign({}, Utils_1.defaultConfig, config) : Utils_1.defaultConfig;
        _this = _super.call(this, cacheConfig) || this;
        _this.config.storage = cacheConfig.storage;
        logger.debug('Using AsyncStorageCache');
        return _this;
    }

(Looks like it was transpiled to ES5 from TypeScript)

So it can take a localStorage polyfill in the config as storage.

You could use a polyfill from npm, or just use the ghetto polyfill from the AWS App Sync docs:

https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html

Like this:

const storage = {
    store: {},
    getItem(key) {
        return this.store[key]
    },
    setItem(key, value) {
        this.store[key] = value
    },
    removeItem(key) {
        delete this.store[key]
    }
}

config = { storage, ...config }

All 18 comments

Forgive me if this comes across as a bit of a stupid question but how would you tell Amplify to use memory storage when you configure it?

Looking at the code, if you pass in cookieStorage in your config you then set userPoolData.Storage

https://github.com/aws/aws-amplify/blob/b439e08f927305ffa1b030c123165f0ee82c55f7/packages/aws-amplify/src/Auth/Auth.ts#L96-L98

but I can't see how else you can pass any other storage via Amplify.configure

I'm getting the same error while running unit tests for components that include aws-amplify.
Looking at Amplify.configure options I can't find where such memory storage would go as a setting.

https://github.com/aws/aws-amplify/blob/1dba7093af821d2c9d152c32b715d45cb90fe0ff/packages/aws-amplify/lib/Auth/types/Auth.d.ts#L13-L20

Don't have full SSR yet, but I was able to check if there user is authenticated by looking at the cookie for CognitoIdentityServiceProvider.${userPoolWebClientId}.LastAuthUser and then the JWT which is also in the cookie.

Can anyone explain if it's even possible to use Amplify server-side?

I presume you'd require the module...

const amplify = require('aws-amplify');

I then tried the following...

const Amplify = amplify.default;

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

Amplify.Auth.signIn('foo', 'bar')
  .then(data => console.log(data))
  .catch(err => console.log(err));

This ^^ errors when run via node.

I think Amplify ultimately wouldn't work because it uses client-side APIs like fetch. I've tried installing packages to support fetch in node (just as an example) but I think it's too dependent on client-side implementations 馃

Note: I don't know JS (or Node) that well at all - so there might be other ways.

@Integralist I think it can be used on server-side, we were using it to handle user signin/signup with it. the only thing we need to do is add this on top of the entry js file

import fetch from 'node-fetch';
global.fetch = global.fetch || fetch;

It will warn you about getItem, but that's fine.

@crysislinux that didn't work for me.

I get the error...

import fetch from 'node-fetch';
^^^^^^

SyntaxError: Unexpected token import
    at new Script (vm.js:51:7)
    at createScript (vm.js:136:10)
    at Object.runInThisContext (vm.js:197:10)
    at Module._compile (internal/modules/cjs/loader.js:618:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:665:10)
    at Module.load (internal/modules/cjs/loader.js:566:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:506:12)
    at Function.Module._load (internal/modules/cjs/loader.js:498:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:695:10)
    at startup (internal/bootstrap/node.js:201:19)
[nodemon] app crashed - waiting for file changes before starting...

@Integralist That's because you don't have ES6 transpiling it seems.
A quick google search would have gotten you the answer:
https://stackoverflow.com/questions/39436322/node-js-syntaxerror-unexpected-token-import

Just replace

import fetch from 'node-fetch';

with

const fetch = require('node-fetch');

This issue just became a holder for other questions rather than the one asked by OP (@mogusbi).

I'm still not sure how to use Memory Storage...

Nah, it's looking for localStorage.

If you grep the aws-amplify code in node_modules, you'll see this:

 function BrowserStorageCache(config) {
        var _this = this;
        var cacheConfig = config ? Object.assign({}, Utils_1.defaultConfig, config) : Utils_1.defaultConfig;
        _this = _super.call(this, cacheConfig) || this;
        _this.config.storage = cacheConfig.storage;
        logger.debug('Using AsyncStorageCache');
        return _this;
    }

(Looks like it was transpiled to ES5 from TypeScript)

So it can take a localStorage polyfill in the config as storage.

You could use a polyfill from npm, or just use the ghetto polyfill from the AWS App Sync docs:

https://docs.aws.amazon.com/appsync/latest/devguide/building-a-client-app-javascript.html

Like this:

const storage = {
    store: {},
    getItem(key) {
        return this.store[key]
    },
    setItem(key, value) {
        this.store[key] = value
    },
    removeItem(key) {
        delete this.store[key]
    }
}

config = { storage, ...config }

We've added a polyfill for this that uses the InMemoryStorage in place that should address this:
https://github.com/aws/aws-amplify/blob/master/packages/aws-amplify/src/Common/Polyfills/Polyfills.ts

Please let us know if you still have issues here.

Was this pushed? Seems like this is still happening. It shows a warn like this four times, then hangs. The debug reveals that 'AWSPinpointProvider' is trying to connect, but (since I didn't configure it) it fails. Could be a red herring.

[WARN] 49:28.401 Cache - getItem failed! TypeError: Cannot read property 'getItem' of null

Should I create a new issue for this?

Yup, still getting the same issue:

WARN] 26:47.552 Cache - getItem failed! TypeError: Cannot read property 'getItem' of null

And page hangs.

Has this been resolved please? Getting the same error

@jwulf is the code you pasted meant to replace the one in BrowserStorageCache?

No mate, that's the enriched configuration object that you pass into the constructor to inject a polyfill implementation.

I haven't looked at this in a while, but quickly it looks like you pass that to Amplify.configure in https://github.com/aws/aws-amplify/issues/493#issuecomment-385999416

Thanks @jwulf. Will try that out.

For achieving SSR this is what I've done, just in case it helps someone. I'm using Koa but I think it should be easy to modify it for Express or something else.

In the server.js file:

    import Amplify from 'aws-amplify';
    import fetch from 'node-fetch';
    ...
    // Middleware for adding storage polyfill that uses incoming cookies 
    app.use(async (ctx, next) => {
        Amplify.configure({
            Auth: {
                ...
            },
            Storage: {
                // This one (uppercase) is for S3
            },
            storage: {
                store: {},
                getItem(key) {
                    return ctx.cookies.get(key);
                },
                setItem(key, value) {
                    return ctx.cookies.set(key, value, { httpOnly: false });
                },
                removeItem(key) {
                    ctx.cookies.set(key, '');
                },
            },
            API: {
                ...
            },
        });
        await next();
    });

Then in the client, in the app component:

import Amplify from 'aws-amplify';
...
if (isClient) {
    Amplify.configure({
        Auth: {
            ...
            cookieStorage: {
                domain: ...,
                expires: ...,
                path: ...,
                secure: ...,
            },
        },
        Storage: {
            ...
        },
        API: {
           ...
        },
    });
}

@juanjcampos thanks for sharing your solution. Can you please show the complete solution? I, and I think many other people, will appreciate your help.

Was this page helpful?
0 / 5 - 0 ratings