amplify-js
amplify-js copied to clipboard
SSR Cognito Authentication doesn't work with email address usernames
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:
- Configure a cognito instance which allows email addresses to be used as usernames
- Connect an amplify application to this cognito user pool
- 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()
}
- 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 aThe 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}
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, 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
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 likeCognitoIdentityServiceProvider.<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
We also need to validate both use-cases:
- Cognito configured with
username
, but using an email address - Cognito configured with
email
(and using an email address)
@cornwe19 how did you work around this?
@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.
It turned out that I forgot to use Amplify.configure inside every api route file (next.js) as they are lambda functions.
I got the same problem.
Anyoine knows a workaround to fix?
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.
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 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.
Recording of the bug in action provided by @Nubtehy in this comment.
Hi all, I am running into this issue as well. Do we have any updates?
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 created an issue here (https://github.com/aws-amplify/amplify-js/issues/11649), seems to be specific for next 13 for server side.
@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 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.
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>
</>
);
}
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.