Skip to content

Commit 2002af2

Browse files
update aws amplify from v5 to v6 and fix its test (#1513)
* docs: fix typo in console error message for cognito index page * fix: global replace of case-sensitive VITE_APP with VITE; this seems to have been a mistake during the upgrade from create-react-app to vite. See historical commits 7b78b6e and e66f559 for the mystery of where VITE_APP came from. REACT_APP should have been replaced with VITE but was replaced with VITE_APP instead. * chore: upgrade amplify-js library from version 5 to version 6 and fix its example cypress tests for programmatic login and cy.origin() login. * docs: prettier fixes to README.md * build: update yarn lock changes only for amplify on chore branch by: removing amplify from package.json, yarn install, replacing amplify in package.json, yarn install. * Update README.md backtick-quote cy.origin() Co-authored-by: Bill Glesias <[email protected]> * Update README.md backtick-quote .env Co-authored-by: Bill Glesias <[email protected]> --------- Co-authored-by: Bill Glesias <[email protected]>
1 parent 2094abd commit 2002af2

File tree

15 files changed

+1146
-2916
lines changed

15 files changed

+1146
-2916
lines changed

.env

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ VITE_BACKEND_PORT=3001
3131
#VITE_OKTA_DOMAIN="dev-your-domain-id.okta.com"
3232
#VITE_OKTA_CLIENTID="your-client-id"
3333

34-
# AWS Cognito #Okta Configuration to be added to .env when running "yarn dev:cognito"
34+
# AWS Cognito Configuration to be added to .env when running "yarn dev:cognito"
3535
# Additional config taken from aws-exports.js
3636
#AWS_COGNITO_USERNAME="[email protected]"
3737
#AWS_COGNITO_PASSWORD="s3cret1234$"
38-
#AWS_COGNITO_DOMAIN="https://YOUR_COGNITO_INSTANCE.auth.us-east-1.amazoncognito.com"
38+
#AWS_COGNITO_DOMAIN="https://YOUR_COGNITO_USER_POOL_HOSTED_UI_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com"
3939

4040
# Google Auth Configuration to be added to .env when running "yarn dev:google"
4141
# client ID should look something like <identifier>.apps.googleusercontent.com

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,26 @@ A [guide has been written with detail around adapting the RWA](http://on.cypress
221221
222222
Prerequisites include an [Amazon Cognito][cognito] account. Environment variables from [Amazon Cognito][cognito] are provided by the [AWS Amplify CLI][awsamplify].
223223
224-
To start the application with Cognito, replace the current **src/index.tsx** file with the **src/index.cognito.tsx** file and start the application with `yarn dev:cognito` and run Cypress with `yarn cypress:open`.
224+
- A user pool is required (identity pool is not used here)
225+
- The user pool must have a hosted UI domain configured, which must:
226+
- allow callback and sign-out URLs of `http://localhost:3000/`,
227+
- allow implicit grant Oauth grant type,
228+
- allow these OpenID Connect scopes:
229+
- aws.cognito.signin.user.admin
230+
- email
231+
- openid
232+
- The user pool must have an app client configured, with:
233+
- enabled auth flow `ALLOW_USER_PASSWORD_AUTH`, only for programmatic login flavor of test.
234+
- The `cy.origin()` flavor of test only requires auth flow `ALLOW_USER_SRP_AUTH`, and does not require `ALLOW_USER_PASSWORD_AUTH`.
235+
- The user pool must have a user corresponding to the `AWS_COGNITO` env vars mentioned below, and the user's Confirmation Status must be `Confirmed`. If it is `Force Reset Password`, then use a browser to log in once at `http://localhost:3000` while `yarn dev:cognito` is running to reset their password.
236+
237+
The test knobs are in a few places:
238+
239+
- The `.env` file has `VITE_AUTH_TOKEN_NAME` and vars beginning `AWS_COGNITO`. Be careful not to commit any secrets.
240+
- Both `scripts/mock-aws-exports.js` and `scripts/mock-aws-exports-es5.js` must have the same data; only their export statements differ. These files can be edited manually or exported from the amplify CLI.
241+
- `cypress.config.ts` has `cognito_programmatic_login` to control flavor of the test.
242+
243+
To start the application with Cognito, replace the current **src/index.tsx** file with the **src/index.cognito.tsx** file and start the application with `yarn dev:cognito` and run Cypress with `yarn cypress:open`. `yarn dev` may need to have been run once first.
225244
226245
The **only passing spec on this branch** will be the [cognito spec](./cypress/tests/ui-auth-providers/cognito.spec.ts); all others will fail.
227246

backend/helpers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ export const verifyOktaToken = (req: Request, res: Response, next: NextFunction)
8080

8181
// Amazon Cognito Validate the JWT Signature
8282
// https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-step-2
83+
const userPoolId = awsConfig.Auth.Cognito.userPoolId;
84+
const region = userPoolId.split("_")[0];
8385
const awsCognitoJwtConfig = {
8486
secret: jwksRsa.expressJwtSecret({
85-
jwksUri: `https://cognito-idp.${awsConfig.aws_cognito_region}.amazonaws.com/${awsConfig.aws_user_pools_id}/.well-known/jwks.json`,
87+
jwksUri: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`,
8688
}),
8789

88-
issuer: `https://cognito-idp.${awsConfig.aws_cognito_region}.amazonaws.com/${awsConfig.aws_user_pools_id}`,
90+
issuer: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`,
8991
algorithms: ["RS256"],
9092
};
9193

cypress/support/auth-provider-commands/cognito.ts

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,44 @@
1-
import { Amplify, Auth } from "aws-amplify";
1+
import { Amplify } from "aws-amplify";
2+
import { fetchAuthSession, signIn } from "aws-amplify/auth";
23

34
Amplify.configure(Cypress.env("awsConfig"));
45

6+
const fetchJwts = async (username: string, password: string) => {
7+
const options = { authFlowType: "USER_PASSWORD_AUTH" as const };
8+
await signIn({ username, password, options });
9+
const authSession = await fetchAuthSession();
10+
const tokens = authSession.tokens!;
11+
const accessToken = tokens.accessToken;
12+
const accessTokenPayload = accessToken.payload;
13+
return {
14+
idToken: tokens.idToken!.toString(),
15+
accessToken: accessToken.toString(),
16+
clientId: accessTokenPayload.client_id as string,
17+
accessTokenSub: accessTokenPayload.sub!,
18+
};
19+
};
20+
type JwtResponse = Awaited<ReturnType<typeof fetchJwts>>;
21+
522
// Amazon Cognito
6-
Cypress.Commands.add("loginByCognitoApi", (username, password) => {
23+
Cypress.Commands.add("loginByCognitoApi", (username: string, password: string) => {
724
const log = Cypress.log({
825
displayName: "COGNITO LOGIN",
926
message: [`🔐 Authenticating | ${username}`],
10-
// @ts-ignore
1127
autoEnd: false,
1228
});
1329

1430
log.snapshot("before");
1531

16-
const signIn = Auth.signIn({ username, password });
32+
cy.wrap(fetchJwts(username, password), { log: false }).then((unknownJwts) => {
33+
const { idToken, accessToken, clientId, accessTokenSub } = unknownJwts as JwtResponse;
1734

18-
cy.wrap(signIn, { log: false }).then((cognitoResponse: any) => {
19-
const keyPrefixWithUsername = `${cognitoResponse.keyPrefix}.${cognitoResponse.username}`;
20-
window.localStorage.setItem(
21-
`${keyPrefixWithUsername}.idToken`,
22-
cognitoResponse.signInUserSession.idToken.jwtToken
23-
);
24-
window.localStorage.setItem(
25-
`${keyPrefixWithUsername}.accessToken`,
26-
cognitoResponse.signInUserSession.accessToken.jwtToken
27-
);
28-
window.localStorage.setItem(
29-
`${keyPrefixWithUsername}.refreshToken`,
30-
cognitoResponse.signInUserSession.refreshToken.token
31-
);
32-
window.localStorage.setItem(
33-
`${keyPrefixWithUsername}.clockDrift`,
34-
cognitoResponse.signInUserSession.clockDrift
35-
);
36-
window.localStorage.setItem(
37-
`${cognitoResponse.keyPrefix}.LastAuthUser`,
38-
cognitoResponse.username
39-
);
35+
const keyPrefix = `CognitoIdentityServiceProvider.${clientId}`;
36+
const keyPrefixWithUsername = `${keyPrefix}.${accessTokenSub}`;
4037

41-
window.localStorage.setItem("amplify-authenticator-authState", "signedIn");
38+
const ls = window.localStorage;
39+
ls.setItem(`${keyPrefixWithUsername}.idToken`, idToken);
40+
ls.setItem(`${keyPrefixWithUsername}.accessToken`, accessToken);
41+
ls.setItem(`${keyPrefix}.LastAuthUser`, accessTokenSub);
4242

4343
log.snapshot("after");
4444
log.end();
@@ -60,9 +60,6 @@ Cypress.Commands.add("loginByCognito", (username, password) => {
6060
});
6161

6262
cy.visit("/");
63-
cy.contains("Sign in with AWS", {
64-
includeShadowDom: true,
65-
}).click();
6663

6764
cy.origin(
6865
Cypress.env("cognito_domain"),
@@ -73,6 +70,7 @@ Cypress.Commands.add("loginByCognito", (username, password) => {
7370
},
7471
},
7572
({ username, password }) => {
73+
cy.contains("Sign in with your email and password");
7674
// cognito log in page has some elements of the same id but are off screen. we only want the visible elements to log in
7775
cy.get('input[name="username"]:visible').type(username);
7876
cy.get('input[name="password"]:visible').type(password, {

cypress/tests/ui-auth-providers/cognito.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import "../../support/auth-provider-commands/cognito";
22
import { isMobile } from "../../support/utils";
3+
const apiGraphQL = `${Cypress.env("apiUrl")}/graphql`;
34

45
if (Cypress.env("cognito_username")) {
56
// Sign in with AWS
67
if (Cypress.env("cognito_programmatic_login")) {
7-
describe("AWS Cognito", function () {
8+
describe("AWS Cognito, programmatic login (cypress.config.ts#cognito_programmatic_login: true)", function () {
89
beforeEach(function () {
910
cy.task("db:seed");
1011

11-
cy.intercept("POST", "/bankAccounts").as("createBankAccount");
12+
cy.intercept("POST", apiGraphQL).as("createBankAccount");
1213

1314
cy.loginByCognitoApi(Cypress.env("cognito_username"), Cypress.env("cognito_password"));
1415
});
@@ -49,7 +50,7 @@ if (Cypress.env("cognito_username")) {
4950
});
5051
});
5152
} else {
52-
describe("AWS Cognito", function () {
53+
describe("AWS Cognito, cy.origin() login (cypress.config.ts#cognito_programmatic_login: false)", function () {
5354
beforeEach(function () {
5455
cy.task("db:seed");
5556
cy.loginByCognito(Cypress.env("cognito_username"), Cypress.env("cognito_password"));

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
},
1414
"dependencies": {
1515
"@auth0/auth0-react": "2.2.4",
16-
"@aws-amplify/ui-react": "^5.0.4",
1716
"@babel/core": "7.23.9",
1817
"@babel/plugin-syntax-flow": "^7.14.5",
1918
"@babel/plugin-transform-react-jsx": "^7.14.9",
@@ -28,7 +27,7 @@
2827
"@okta/okta-react": "^6.7.0",
2928
"@types/detect-port": "^1.3.2",
3029
"@xstate/react": "3.2.2",
31-
"aws-amplify": "^5.3.3",
30+
"aws-amplify": "^6.0.16",
3231
"axios": "0.27.2",
3332
"clsx": "1.2.1",
3433
"date-fns": "2.30.0",

scripts/mock-aws-exports-es5.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
// mock aws-exports-es5.js
22

33
const awsmobile = {
4-
aws_cognito_region: "",
5-
aws_user_pools_id: "",
4+
Auth: {
5+
Cognito: {
6+
userPoolId: "us-east-1_abcdefghi",
7+
userPoolClientId: "a1b2c3d4e5f6g7h8i9j0k1l2m",
8+
loginWith: {
9+
oauth: {
10+
domain: "YOUR_COGNITO_USER_POOL_HOSTED_UI_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com",
11+
scopes: ["email", "openid", "aws.cognito.signin.user.admin"],
12+
redirectSignIn: ["http://localhost:3000/"],
13+
redirectSignOut: ["http://localhost:3000/"],
14+
responseType: "token",
15+
},
16+
},
17+
},
18+
},
619
};
720

821
exports.default = awsmobile;

scripts/mock-aws-exports.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
// mock aws-exports.js
22

33
const awsmobile = {
4-
aws_cognito_region: "",
5-
aws_user_pools_id: "",
4+
Auth: {
5+
Cognito: {
6+
userPoolId: "us-east-1_abcdefghi",
7+
userPoolClientId: "a1b2c3d4e5f6g7h8i9j0k1l2m",
8+
loginWith: {
9+
oauth: {
10+
domain: "YOUR_COGNITO_USER_POOL_HOSTED_UI_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com",
11+
scopes: ["email", "openid", "aws.cognito.signin.user.admin"],
12+
redirectSignIn: ["http://localhost:3000/"],
13+
redirectSignOut: ["http://localhost:3000/"],
14+
responseType: "token",
15+
},
16+
},
17+
},
18+
},
619
};
720

821
export default awsmobile;

src/containers/AppCognito.tsx

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
import React, { useEffect } from "react";
22
import { useActor, useMachine } from "@xstate/react";
33
import { makeStyles } from "@material-ui/core/styles";
4-
import { CssBaseline, Container } from "@material-ui/core";
4+
import { CssBaseline } from "@material-ui/core";
55

66
import { snackbarMachine } from "../machines/snackbarMachine";
77
import { notificationsMachine } from "../machines/notificationsMachine";
88
import { authService } from "../machines/authMachine";
99
import AlertBar from "../components/AlertBar";
1010
import { bankAccountsMachine } from "../machines/bankAccountsMachine";
1111
import PrivateRoutesContainer from "./PrivateRoutesContainer";
12-
import { Amplify } from "aws-amplify";
13-
import { Authenticator, useAuthenticator } from "@aws-amplify/ui-react";
12+
import { Amplify, ResourcesConfig } from "aws-amplify";
13+
import { fetchAuthSession, signInWithRedirect, signOut } from "aws-amplify/auth";
1414

1515
// @ts-ignore
1616
import awsConfig from "../aws-exports";
17-
18-
Amplify.configure(awsConfig);
17+
Amplify.configure(awsConfig as ResourcesConfig);
1918

2019
// @ts-ignore
2120
if (window.Cypress) {
@@ -39,35 +38,53 @@ const AppCognito: React.FC = /* istanbul ignore next */ () => {
3938

4039
const [, , bankAccountsService] = useMachine(bankAccountsMachine);
4140

42-
const { route, signOut, user } = useAuthenticator();
41+
const isLoggedIn =
42+
authState.matches("authorized") ||
43+
authState.matches("refreshing") ||
44+
authState.matches("updating");
4345

4446
useEffect(() => {
45-
console.log("auth route: ", route);
46-
if (route === "authenticated") {
47-
authService.send("COGNITO", { user });
47+
if (!isLoggedIn) {
48+
fetchAuthSession().then((authSession) => {
49+
if (authSession && authSession.tokens && authSession.tokens.accessToken) {
50+
const { tokens, userSub } = authSession;
51+
authService.send("COGNITO", {
52+
accessTokenJwtString: tokens!.accessToken.toString(),
53+
userSub: userSub!,
54+
email: tokens!.idToken!.payload.email,
55+
});
56+
} else {
57+
void signInWithRedirect();
58+
}
59+
});
4860
}
49-
}, [route, user]);
61+
}, [isLoggedIn]);
5062

5163
useEffect(() => {
5264
authService.onEvent(async (event) => {
53-
if (event.type === "done.invoke.performLogout") {
54-
console.log("AppCognito authService.onEvent done.invoke.performLogout");
65+
if (
66+
event.type === "done.invoke.performLogout" ||
67+
// we want the client-side app to discard its JWTs even if server-side errors out:
68+
event.type.startsWith("error.platform.authentication.logout")
69+
) {
70+
console.log(
71+
"AppCognito authService.onEvent done.invoke.performLogout|error.platform.authentication.logout"
72+
);
5573
await signOut();
5674
}
5775
});
58-
}, [signOut]);
76+
}, []);
5977

60-
const isLoggedIn =
61-
authState.matches("authorized") ||
62-
authState.matches("refreshing") ||
63-
authState.matches("updating");
78+
if (!isLoggedIn) {
79+
return null;
80+
}
6481

65-
return isLoggedIn ? (
82+
return (
6683
<div className={classes.root}>
6784
<CssBaseline />
6885

6986
<PrivateRoutesContainer
70-
isLoggedIn={isLoggedIn}
87+
isLoggedIn={true}
7188
notificationsService={notificationsService}
7289
authService={authService}
7390
snackbarService={snackbarService}
@@ -76,18 +93,6 @@ const AppCognito: React.FC = /* istanbul ignore next */ () => {
7693

7794
<AlertBar snackbarService={snackbarService} />
7895
</div>
79-
) : (
80-
<Container component="main" maxWidth="xs">
81-
<CssBaseline />
82-
<Authenticator>
83-
{({ signOut, user }) => (
84-
<main>
85-
<h1>Hello {user?.username}</h1>
86-
<button onClick={signOut}>Sign out</button>
87-
</main>
88-
)}
89-
</Authenticator>
90-
</Container>
9196
);
9297
};
9398

src/index.auth0.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ const onRedirectCallback = (appState: any) => {
2222
const root = createRoot(document.getElementById("root")!);
2323

2424
/* istanbul ignore if */
25-
if (process.env.VITE_APP_AUTH0) {
25+
if (process.env.VITE_AUTH0) {
2626
root.render(
2727
<Auth0Provider
28-
domain={process.env.VITE_APP_AUTH0_DOMAIN!}
29-
clientId={process.env.VITE_APP_AUTH0_CLIENTID!}
28+
domain={process.env.VITE_AUTH0_DOMAIN!}
29+
clientId={process.env.VITE_AUTH0_CLIENTID!}
3030
redirectUri={window.location.origin}
31-
audience={process.env.VITE_APP_AUTH0_AUDIENCE}
32-
scope={process.env.VITE_APP_AUTH0_SCOPE}
31+
audience={process.env.VITE_AUTH0_AUDIENCE}
32+
scope={process.env.VITE_AUTH0_SCOPE}
3333
onRedirectCallback={onRedirectCallback}
3434
cacheLocation="localstorage"
3535
>

0 commit comments

Comments
 (0)