amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

SSR Cognito Authentication doesn't work with email address usernames

Open cornwe19 opened this issue 3 years ago • 11 comments

Describe the bug Cognito user authentication doesn't appear to support url-encoded cookie fields when authenticating during server-side renders using withSSRContext.

To Reproduce Steps to reproduce the behavior:

  1. Configure a cognito instance which allows email addresses to be used as usernames
  2. Connect an amplify application to this cognito user pool
  3. Use a framework like NextJs to perform some server-side rendering similar to the following
export const getServerSideProps = async (context) => {
    const { Auth } = withSSRContext(context)
    await Auth.getCurrentAuthenticatedUser()
}
  1. Note that when trying to server side render this page with a user whose username is formatted like an email address (contains an @), the server side render will fail with a The user is not authenticated message.

Expected behavior So long as Cognito identity pools support usernames formatted like email addresses, I'd expect those usernames to be able to be used for SSR applications.

What is Configured? If applicable, please provide what is configured for Amplify CLI:

  • Which steps did you follow via Amplify CLI when configuring your resources.
  • Which resources do you have configured?
    • Auth and API as follows:
{
  Auth: {
    region: 'us-east-1',
    userPoolId: 'us-east-xxxxxxxx',
    userPoolWebClientId: 'xxxxxxxxxxxxxxxxx',
    oauth: {
      "domain": "vdxauth.auth.us-east-1.amazoncognito.com",
      "redirectSignIn": `http://localhost:3000`,
      "redirectSignOut": `http://localhost:3000`,
      "responseType": "code"
    },
    cookieStorage: {
      domain: "localhost",
      secure: false
    }
  },
  API: {
    graphql_endpoint: `http://localhost:8082/graphql`,
  },
  ssr: true
}

Additional context Here's the specific stack trace when the SSR request fails

[DEBUG] 55:00.353 AuthClass - getting current authenticated user
[DEBUG] 55:00.353 AuthClass - get current authenticated userpool user
[DEBUG] 55:00.353 AuthClass - Failed to get the user session Error: Local storage is missing an ID Token, Please authenticate
    at CognitoUser.getSession (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/amazon-cognito-identity-js/lib/CognitoUser.js:1353:16)
    at AuthClass.<anonymous> (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:1125:34)
    at step (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:56:23)
    at Object.next (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:37:53)
    at /Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:31:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:27:12)
    at /Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:1086:44
[DEBUG] 55:00.354 AuthClass - The user is not authenticated by the error Error: Local storage is missing an ID Token, Please authenticate
    at CognitoUser.getSession (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/amazon-cognito-identity-js/lib/CognitoUser.js:1353:16)
    at AuthClass.<anonymous> (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:1125:34)
    at step (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:56:23)
    at Object.next (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:37:53)
    at /Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:31:71
    at new Promise (<anonymous>)
    at __awaiter (/Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:27:12)
    at /Users/dcornwell/Documents/projects/kmax/kmax-web/node_modules/aws-amplify/node_modules/@aws-amplify/auth/lib/Auth.js:1086:44
The user is not authenticated

I've verified that this seems to be a mismatch between the raw username [email protected] and the url-encoded cookie name that gets sent with the SSR request e.g. CognitoIdentityServiceProvider.2vphm6diig0n6p3nob5osjdn7j.user%40domain.com.idToken by sticking a debug breakpoint inside CognitoUser.js -> getSession(callback). The username used to compose token keys appears to be a raw [email protected] but the token storage has the keys saved under {prefix}.user%40domain.com.{field}

cornwe19 avatar Feb 04 '21 20:02 cornwe19

Missing cookies?

@cornwe19 I have a hunch why await Auth.getCurrentAuthenticatedUser() could be failing — you might be missing cognito cookies

Verify with:

export const getServerSideProps = async (context) => {
+   console.log("ssr cookies", context.req.cookies);
    const { Auth } = withSSRContext(context)
    await Auth.getCurrentAuthenticatedUser()
}

Bad

if you see {}, that's what I understand to be the cause.

Good

You should see keys like in your SSR logs

{ 
  'CognitoIdentityServiceProvider.<app_client_id>.<cognito_username_or_sub>.idToken': ey...
  'CognitoIdentityServiceProvider.<app_client_id>.<cognito_username_or_sub>.accessToken': ey...
}

How do you get those cookies?

I dunno? ... AWS TEAM PLEASE HELP 🙏

I've noticed Auth.federatedSignIn() set them there, but not Auth.sendCustomChallengeAnswer()

thiskevinwang avatar Feb 06 '21 03:02 thiskevinwang

@thiskevinwang, thanks for the debugging tip, but I've actually gotten to the point that I had a debugger hooked up and was able to hit a breakpoint in the amplify js code.

I do have cookies being delivered to the server. The problem here specifically seems to be that the cookies delivered,

{ 
  'CognitoIdentityServiceProvider.<app_client_id>.<cognito_username_or_sub>.idToken': ey...
  'CognitoIdentityServiceProvider.<app_client_id>.<cognito_username_or_sub>.accessToken': ey...
}

have the <cognito_username_or_sub> component URL encoded, but when the amplify code goes to look them up, it doesn't URL encode my username. Basically, a username like [email protected] can't be found when the cookie delivered looks like CognitoIdentityServiceProvider.<app_client_id>.user%40domain.com.idToken

cornwe19 avatar Feb 06 '21 22:02 cornwe19

This sounds like a bug for sure! Great detective work here :

have the <cognito_username_or_sub> component URL encoded, but when the amplify code goes to look them up, it doesn't URL encode my username. Basically, a username like [email protected] can't be found when the cookie delivered looks like CognitoIdentityServiceProvider.<app_client_id>.user%40domain.com.idToken

So we need to check the encoding on both sides to see the where the resolution lies:

https://github.com/aws-amplify/amplify-js/blob/0fc0fc29feaee5b9b86709571e71f14335c6095b/packages/core/src/UniversalStorage/index.ts#L101-L102

or

https://github.com/aws-amplify/amplify-js/blob/0fc0fc29feaee5b9b86709571e71f14335c6095b/packages/core/src/UniversalStorage/index.ts#L43-L45

ericclemmons avatar Feb 09 '21 18:02 ericclemmons

We also need to validate both use-cases:

  1. Cognito configured with username, but using an email address
  2. Cognito configured with email (and using an email address)

ericclemmons avatar Feb 09 '21 18:02 ericclemmons

@cornwe19 how did you work around this?

joekendal avatar Jun 30 '21 18:06 joekendal

@joekendal We ended up changing our identity provider's SAML configuration to side-step the problem and stop sending email addresses as usernames. Unfortunately, I'm not sure if that's a feasible workaround for you.

cornwe19 avatar Jun 30 '21 20:06 cornwe19

It turned out that I forgot to use Amplify.configure inside every api route file (next.js) as they are lambda functions.

joekendal avatar Jun 30 '21 20:06 joekendal

I got the same problem.

Anyoine knows a workaround to fix?

sergiors avatar Feb 07 '22 22:02 sergiors

Hi,

Today I faced the same problem and it fixed to tweak configuration method.

Before

I configured Amplify with Scoped Configuration^1

Amplify.configuration({
  Auth: {
    authenticationFlowType: "USER_PASSWORD_AUTH",
    region: process.env.NEXT_PUBLIC_APP_AUTH_REGION || "ap-northeast-1",
    userPoolId: "ap-northeast-1_xxxxxxxxx",
    userPoolWebClientId: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
    cookieStorage: {
      domain: process.env.NEXT_PUBLIC_APP_AUTH_COOKIE_STORAGE_DOMAIN || "localhost",
      path: "/",
      expires: 365,
      sameSite: "strict",
      secure: true,
    },
  },
});

After

I configured with Top level configuration^2

Amplify.configuration({
  aws_project_region: process.env.NEXT_PUBLIC_APP_AUTH_REGION || "ap-northeast-1",
  aws_cognito_region: process.env.NEXT_PUBLIC_APP_AUTH_REGION || "ap-northeast-1",
  aws_user_pools_id: "ap-northeast-1_xxxxxxxxx",
  aws_user_pools_web_client_id: "xxxxxxxxxxxxxxxxxxxxxxxxxx",
  Auth: {
    authenticationFlowType: "USER_PASSWORD_AUTH",
  },
});

I couldn't find it in the documentation, so I can't say for sure that it will solve the problem, but if anyone else is having this problem now, please try it and let me know if you were able to solve it.

By the way, if anyone who knows Amplify and Cognito well see this comment, please tell me if this behavior is expected.

toyamarinyon avatar Jul 27 '22 08:07 toyamarinyon

I attempted to reproduce, but the user is returned consistently in the SSR context. I used the scoped configuration specified in the original issue as well as in the comment left by @toyamarinyon.

@sergiors @toyamarinyon If this is still happening, can you provide more detailed reproduction steps? Thanks!

erinleigh90 avatar Sep 07 '22 21:09 erinleigh90

@erinleigh90 Thank you for attempting to reproduce!

After commented https://github.com/aws-amplify/amplify-js/issues/7684#issuecomment-1196436573, I attempted to reproduce too. Therefore I found that my comment is wrongs and I found the actual cause.

SSR Cognito Authentication doesn't work if we set cookieStorage to option because Amplify has different rules for deciding the cookie manipulating library between SSR and Browser.

If we set cookieStorage to option:

  • In browser, Amplify use js-cookie for manipulating cookie.
  • In SSR, use universal-cookie.
  • SSR Cognito Authentication fails because each library's implementation for getting cookie and setting cookie is different.

It is implemented by the following code: https://github.com/aws-amplify/amplify-js/blob/06504e649068f01b85392373fdf80e2ed2a6cada/packages/auth/src/Auth.ts#L172-L178

So we don't set cookieStorage to option and SSR Cognito authentication works properly.

toyamarinyon avatar Sep 08 '22 07:09 toyamarinyon

Recording of the bug in action provided by @Nubtehy in this comment.

cwomack avatar May 09 '23 22:05 cwomack

Hi all, I am running into this issue as well. Do we have any updates?

Rudrakle avatar May 11 '23 08:05 Rudrakle

I'm also having same issue with the cookieStorage option.

I could get the SSR authentication to work in next js 13 app router using the default localStorage option. As soon as I added cookieStorage configuration I get the user is not authenticated error when calling

await SSR.Auth.currentAuthenticatedUser()

ocsross96 avatar Jul 17 '23 15:07 ocsross96

@ocsross96 created an issue here (https://github.com/aws-amplify/amplify-js/issues/11649), seems to be specific for next 13 for server side.

asp3 avatar Jul 18 '23 23:07 asp3

@ocsross96 @cornwe19, I believe this should be resolved on the latest version of Amplify. Can you upgrade and see if it's still an issue?

cwomack avatar Oct 09 '23 21:10 cwomack

@cwomack glad to hear this one has likely been resolved and thanks for the heads up. Unfortunately, I'm no longer working on the project which was using amplify for SSO, so I don't have a simple way to test this out myself.

cornwe19 avatar Oct 11 '23 20:10 cornwe19

Thanks a lot @cwomack I have updated the aws-amplify version to 5.3.11 and I can confirm this issue is now fixed for me in NextJS version 13.4.2.

For clarity and to help others here is what the code looks like for this:

// utils/getWithSSRContext.ts

import { withSSRContext } from 'aws-amplify';
import { cookies } from 'next/headers';

type CookieObj = {
  name: string;
  value: string;
};

const config = {
  Auth: {
    region: process.env.AWS_REGION,
    userPoolId: process.env.AWS_COGNITO_USER_POOL_ID,
    userPoolWebClientId: process.env.AWS_COGNITO_USER_POOL_WEB_CLIENT_ID,
    cookieStorage: {
      domain: process.env.APP_DOMAIN,
      path: '/',
      expires: 1,
      sameSite: 'strict',
      secure: false,
    },
  },
  ssr: true,
};

function serializeCookies(cookies: CookieObj[]) {
  return cookies
    .map((c) => `${c.name}=${c.value ? decodeURIComponent(c.value) : ''};`)
    .join('');
}

export default function getWithSSRContext() {
  const allCookies = cookies().getAll();

  const SSR = withSSRContext({
    req: {
      headers: {
        cookie: serializeCookies(allCookies),
      },
    },
  });
  SSR.configure(config);

  return SSR;
}
// app/protected-server/page.tsx

import { redirect } from 'next/navigation';
import getWithSSRContext from 'utils/getWithSSRContext';

export default async function ProtectedServer() {
  const SSR = getWithSSRContext();

  try {
    const res = await SSR.Auth.currentAuthenticatedUser();
    console.log('ProtectedServer user', res); // logs auth user correctly
  } catch (err) {
    console.log('err', err);
    redirect('/login');
  }

  return (
    <>
      <div>If you can see this page, you are logged in.</div>
    </>
  );
}

ocsross96 avatar Oct 12 '23 10:10 ocsross96

Appreciate your still following up, @cornwe19! There's lots of improvements inbound with our developer preview of v6 released and the full suite of v6 going General Availability soon. Feel free to check out our announcement and updated documentation to see what has changed.

@ocsross96, thank you for confirming! That's great to hear and love that you provided some sample code for others that may come across this issue. I'll close out the issue then for now, but please feel free to comment back if there's further problems with this the latest version of Amplify.

cwomack avatar Oct 12 '23 20:10 cwomack