├── .env.example
├── .gitignore
├── README.md
├── backend-nodejs
├── Dockerfile
├── app.ts
├── bin
│ └── startup.sh
├── controllers
│ ├── authCognitoController.ts
│ └── authCorbadoController.ts
├── package.json
├── tsconfig.app.json
├── tsconfig.json
└── utils
│ ├── constants.ts
│ └── helper.ts
├── docker-compose.yml
└── frontend-angular
├── .editorconfig
├── .gitignore
├── Dockerfile
├── README.md
├── angular.json
├── package.json
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── auth.service.spec.ts
│ ├── auth.service.ts
│ ├── auth
│ │ ├── auth.component.html
│ │ ├── auth.component.scss
│ │ ├── auth.component.spec.ts
│ │ └── auth.component.ts
│ └── logged-in
│ │ ├── logged-in.component.html
│ │ ├── logged-in.component.scss
│ │ ├── logged-in.component.spec.ts
│ │ └── logged-in.component.ts
├── assets
│ └── .gitkeep
├── favicon.ico
├── index.html
├── main.ts
└── styles.scss
├── tsconfig.app.json
├── tsconfig.json
└── tsconfig.spec.json
/.env.example:
--------------------------------------------------------------------------------
1 | AWS_ACCESS_KEY_ID=
2 | AWS_SECRET_ACCESS_KEY=
3 |
4 | COGNITO_REGION=
5 | COGNITO_USER_POOL_ID=
6 | COGNITO_CLIENT_ID=
7 | COGNITO_CLIENT_SECRET=
8 | COGNITO_JWKS=
9 |
10 | CORBADO_PROJECT_ID=
11 | CORBADO_API_SECRET=
12 | CORBADO_CLI_SECRET=
13 | CORBADO_WEBHOOK_USERNAME=
14 | CORBADO_WEBHOOK_PASSWORD=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .env
3 | node_modules
4 | dist
5 | package-lock.json
6 | .vscode
7 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Passkeys integration into Amazon Cognito
7 |
8 |
9 | Sample project that shows how to integrate passkeys into your existing password-based Amazon Cognito project.
10 |
11 |
12 | View passkeys demo
13 | ·
14 | Report Bug
15 | ·
16 | Request Feature
17 |
18 |
19 |
20 |
21 |
22 |
23 | ## Overview
24 |
25 |
26 |
27 | Passkeys are the new standard for authentication on the web. Currently, they're being rolled out by Apple, Google and Microsoft. Though, not many code
28 | sample projects exist, and even less for integrating them into existing authentication and user management systems. We provide a guide that shows how to easily add
29 | passkeys into your existing Amazon Cognito project.
30 |
31 | For this sample, we leverage [Corbado](https://www.corbado.com)'s passkey-first web components that let you integrate it in <1 hour, while still keeping Amazon Cognito as core user management system.
32 |
33 | Find a detailed step-by-step tutorial [here](https://www.corbado.com/blog/passkeys-amazon-cognito).
34 |
35 | (back to top )
36 |
37 |
38 |
39 | ### Built With
40 |
41 | The sample project uses Angular as web frontend framework and Node.js / Express in the backend (both in TypeScript).
42 |
43 | * [![Angular][Angular.io]][Angular-url]
44 | * [![Node.js][Nodejs.org]][Nodejs-url]
45 | * [![Express][Expressjs.com]][Express-url]
46 |
47 | (back to top )
48 |
49 |
50 |
51 | ## Getting Started
52 |
53 | We provide a docker file for quickly getting started. Note that you need to enter Amazon Cognito and Corbado environment variables to get things working (see `docker-compose.yml`).
54 |
55 | To start project without locally without Docker, you need to run the [Corbado CLI](https://docs.corbado.com/helpful-guides/corbado-cli). Besides, you may also need to copy the AWS CLI credentials from `.aws/credentials`:
56 |
57 | ```
58 | AWS_ACCESS_KEY_ID=
59 | AWS_SECRET_ACCESS_KEY=
60 |
61 | COGNITO_REGION=
62 | COGNITO_USER_POOL_ID=
63 | COGNITO_CLIENT_ID=
64 | COGNITO_CLIENT_SECRET=
65 | COGNITO_JWKS=
66 |
67 | CORBADO_PROJECT_ID=
68 | CORBADO_API_SECRET=
69 | CORBADO_CLI_SECRET=
70 | CORBADO_WEBHOOK_USERNAME=
71 | CORBADO_WEBHOOK_PASSWORD=
72 | ```
73 |
74 | Start the docker containers with:
75 |
76 | ```
77 | docker compose up
78 | ```
79 |
80 | ## Integration
81 |
82 | Please see [this link](https://www.corbado.com/blog/passkeys-amazon-cognito) for a detailed step-by-step tutorial on how to integrate passkeys into Amazon Cognito.
83 |
84 | ### Sign-up flow
85 | The following steps provide a high-level overview what happens during the passkey sign-up flow.
86 |
87 | 1. Sign-up via Corbado web component on Angular frontend
88 | 2. Sign-up handled by Corbado backend (existing users are checked via webhooks)
89 | 4. Redirect to `/api/corbado/authTokenValidate` on Node.js / Express backend with `corbadoAuthToken`
90 | 5. API call to Corbado backend to verify `corbadoAuthToken` (API returns `email`)
91 | 6. API calls to create the user in Amazon Cognito
92 | 1. AdminCreateUser (`email`=`email` returned from previous step, `email_verified`=`true`, `custom:createdByCorbado`=`true`)
93 | 2. AdminSetUserPassword (`Username`=`email`, `Password`=`randomPassword`)
94 | 7. API calls to create session in Amazon Cognito
95 | 1. AdminInitiateAuthCommand (`AuthFlow`=`CUSTOM_AUTH`, `USERNAME`=`email` returned from previous step, no password)
96 | 2. AWS Lambda functions are executed
97 | 1. `Define auth challenge`: Cognito invokes this trigger to initiate the custom auth flow
98 | 2. `Create auth challenge`: Cognito invokes this trigger after `Define auth challenge` to create custom challenge
99 | 3. `Verify auth challenge response`: Cognito invokes this trigger to verify response from user for custom challenge
100 | 3. RespondToAuthChallenge
101 | 8. Amazon Cognito returns JWTs (`idToken`, `accessToken`, `refreshToken`)
102 | 9. `accessToken` "saved in Angular"
103 | 10. Logged-in page in Angular verifies `accessToken` in JavaScript
104 |
105 | ### Login flow
106 | The following steps provide a high-level overview what happens during the passkey login flow.
107 |
108 | 1. Login via Corbado web component on Angular frontend
109 | 2. Login handled by Corbado backend (existing users are checked via webhooks)
110 | 3. Redirect to `/api/corbado/authTokenValidate` on Node.js / Express backend with `corbadoAuthToken`
111 | 4. API call to Corbado backend to verify `corbadoAuthToken` (API returns `email`)
112 | 5. API calls to create session in Amazon Cognito
113 | 1. AdminInitiateAuthCommand (`AuthFlow`=`CUSTOM_AUTH`, `USERNAME`=`email` returned from previous step, no password)
114 | 2. AWS Lambda functions are executed
115 | 1. `Define auth challenge`: Cognito invokes this trigger to initiate the custom auth flow
116 | 2. `Create auth challenge`: Cognito invokes this trigger after `Define auth challenge` to create custom challenge
117 | 3. `Verify auth challenge response`: Cognito invokes this trigger to verify response from user for custom challenge
118 | 3. RespondToAuthChallenge
119 | 6. Amazon Cognito returns JWTs (`idToken`, `accessToken`, `refreshToken`)
120 | 7. `accessToken` "saved in Angular"
121 | 8. Logged-in page in Angular verifies `accessToken` in JavaScript
122 |
123 | (back to top )
124 |
125 |
126 |
127 | ## Contact
128 |
129 | Vincent Delitz - [@vdelitz](https://twitter.com/vdelitz) - vincent@corbado.com
130 |
131 | Project link: [https://github.com/corbado/passkeys-amazon-cognito](https://github.com/corbado/passkeys-amazon-cognito)
132 |
133 | Tutorial link: [https://www.corbado.com/blog/passkeys-amazon-cognito](https://www.corbado.com/blog/passkeys-amazon-cognito)
134 |
135 | (back to top )
136 |
137 |
138 |
139 |
140 | [product-screenshot]: images/screenshot.png
141 | [Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
142 | [Angular-url]: https://angular.io/
143 |
144 | [Nodejs-url]: https://nodejs.org/
145 | [Nodejs.org]: https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white
146 |
147 | [Express-url]: https://expressjs.com/
148 | [Expressjs.com]: https://img.shields.io/badge/Express-AEAEAE?style=for-the-badge&logo=express&logoColor=white
149 |
--------------------------------------------------------------------------------
/backend-nodejs/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Node.js runtime as a parent image
2 | FROM node:16-alpine
3 | ARG TARGETOS
4 | ARG TARGETARCH
5 |
6 | ENV CORBADO_PROJECT_ID \
7 | CORBADO_API_SECRET \
8 | CORBADO_CLI_SECRET
9 |
10 |
11 | RUN apk add --no-cache curl
12 |
13 | RUN apk add git
14 |
15 | RUN curl -o /tmp/corbado_cli.tar.gz -sSL https://github.com/corbado/cli/releases/download/v1.0.2/corbado_cli_v1.0.2_${TARGETOS}_${TARGETARCH}.tar.gz && tar xfz /tmp/corbado_cli.tar.gz && mv corbado /usr/local/bin/corbado && chmod +x /usr/local/bin/corbado
16 |
17 | # Set the working directory to /app
18 | WORKDIR /app
19 |
20 | # Copy the package.json and package-lock.json files to the container
21 | COPY package*.json ./
22 |
23 | # Expose the port used by the backend
24 | EXPOSE 3000
25 |
26 |
27 | ENTRYPOINT /bin/sh bin/startup.sh
28 |
--------------------------------------------------------------------------------
/backend-nodejs/app.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | const express = require( 'express');
3 | // @ts-ignore
4 | import cors from 'cors';
5 | // @ts-ignore
6 | import bodyParser from 'body-parser';
7 | // @ts-ignore
8 | import dotenv from 'dotenv';
9 | import {/*signUp, login, */logout} from './controllers/authCognitoController';
10 | import {handleWebhook, authTokenValidate} from './controllers/authCorbadoController';
11 | import {json, Request, Response} from "express";
12 | const Corbado = require('@corbado/node-sdk');
13 |
14 | const projectID = process.env.CORBADO_PROJECT_ID;
15 | const apiSecret = process.env.CORBADO_API_SECRET;
16 | const corbadoWebhookUsername = process.env.CORBADO_WEBHOOK_USERNAME;
17 | const corbadoWebhookPassword = process.env.CORBADO_WEBHOOK_PASSWORD;
18 |
19 | const config = new Corbado.Configuration(projectID, apiSecret);
20 | config.webhookUsername = corbadoWebhookUsername;
21 | config.webhookPassword = corbadoWebhookPassword;
22 | const corbado = new Corbado.SDK(config);
23 |
24 | dotenv.config();
25 | const app = express();
26 |
27 | app.use(bodyParser.json());
28 | app.use(cors());
29 |
30 | // Old authentication process for Amazon Cognito
31 | //app.post('/api/auth/signup', signUp);
32 | //app.post('/api/auth/login', login);
33 | app.post('/api/auth/logout', logout);
34 |
35 |
36 |
37 | app.post('/api/corbado/webhook', corbado.webhooks.middleware, json(), handleWebhook);
38 | app.get('/api/corbado/authTokenValidate', json(), authTokenValidate);
39 |
40 | app.get('/ping', (req: Request, res: Response) => {
41 | res.send('pong');
42 | });
43 |
44 |
45 | const port = process.env.PORT || 3000;
46 |
47 | app.listen(port, () => {
48 | console.log(`Server started on port ${port}`);
49 | });
50 |
--------------------------------------------------------------------------------
/backend-nodejs/bin/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Function to check if npm run dev is successfully spun up
4 | function check_dev {
5 | response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/ping)
6 | if [[ $response == "200" ]]; then
7 | return 0
8 | else
9 | return 1
10 | fi
11 | }
12 |
13 | corbado login --projectID $CORBADO_PROJECT_ID --cliSecret $CORBADO_CLI_SECRET
14 |
15 | # Install dependencies and start npm run dev
16 | npm install && npm run dev &
17 |
18 | # Wait for npm run dev to spin up successfully
19 | while ! check_dev; do
20 | sleep 1
21 | echo "Check if backend is reachable"
22 | done
23 |
24 | # Wait for an additional 10 seconds to ensure the application is fully running
25 | sleep 10
26 |
27 | echo "Running corbado subscribe"
28 |
29 | # Subscribe to Corbado
30 | corbado subscribe http://localhost:3000
--------------------------------------------------------------------------------
/backend-nodejs/controllers/authCognitoController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response} from "express";
2 | import {
3 | AdminCreateUserCommand,
4 | AdminCreateUserCommandInput,
5 | AdminGetUserCommand,
6 | AdminGetUserCommandInput,
7 | AdminInitiateAuthCommand,
8 | AdminInitiateAuthCommandInput,
9 | AdminSetUserPasswordCommand,
10 | AdminSetUserPasswordCommandInput, AttributeType,
11 | AuthFlowType,
12 | ChallengeNameType,
13 | CognitoIdentityProviderClient,
14 | CognitoIdentityProviderClientConfig,
15 | GlobalSignOutCommand,
16 | InitiateAuthCommand,
17 | InitiateAuthCommandInput,
18 | InitiateAuthCommandOutput,
19 | MessageActionType,
20 | RespondToAuthChallengeCommand,
21 | RespondToAuthChallengeCommandInput,
22 | RespondToAuthChallengeCommandOutput,
23 | } from "@aws-sdk/client-cognito-identity-provider";
24 | // @ts-ignore
25 | import crypto from "crypto";
26 | // @ts-ignore
27 | import {hashSecret, validateJWT, generatePassword} from '../utils/helper';
28 | import {EXISTS, NOT_EXISTS} from "../utils/constants";
29 |
30 | require("dotenv").config({path: '../.env' });
31 |
32 |
33 | const userPoolId = process.env.COGNITO_USER_POOL_ID;
34 | const clientId = process.env.COGNITO_CLIENT_ID || "";
35 | const clientSecret = process.env.COGNITO_CLIENT_SECRET || "";
36 | const region = process.env.COGNITO_REGION || "";
37 | const poolData = {UserPoolId: userPoolId, ClientId: clientId};
38 |
39 | const clientConfig: CognitoIdentityProviderClientConfig = {
40 | region: region,
41 | };
42 | const client = new CognitoIdentityProviderClient(clientConfig);
43 |
44 | // old Amazon Cognito code commented out
45 | /*export const signUp = async (req: Request, res: Response) => {
46 | const {email, password} = req.body;
47 |
48 | // Workaround, as don't want to send out confirm emails
49 | const paramsAdminCreateUser: AdminCreateUserCommandInput = {
50 | ...poolData,
51 | Username: email,
52 | DesiredDeliveryMediums: ["EMAIL"],
53 | UserAttributes: [
54 | {Name: 'email', Value: email},
55 | {Name: 'email_verified', Value: 'true'},
56 | ],
57 | MessageAction: MessageActionType.SUPPRESS
58 | };
59 |
60 | try {
61 | const createUserCommand = new AdminCreateUserCommand(paramsAdminCreateUser);
62 | await client.send(createUserCommand);
63 |
64 | console.log("USER SUCCESSFULLY CREATED");
65 |
66 | const paramsSetUserPassword: AdminSetUserPasswordCommandInput = {
67 | ...poolData,
68 | Username: email,
69 | Permanent: true,
70 | Password: password
71 | };
72 | const confirmUserCommand = new AdminSetUserPasswordCommand(paramsSetUserPassword);
73 | await client.send(confirmUserCommand);
74 |
75 | console.log("USER SUCCESSFULLY CONFIRMED");
76 | res.status(200).json({message: 'User created successfully'});
77 | } catch (err) {
78 | console.log(err);
79 | res.status(500).json({message: 'An error occurred'});
80 | }
81 | };
82 |
83 | export const login = async (req: Request, res: Response) => {
84 | const {email, password} = req.body;
85 |
86 | const authParams = {
87 | ...poolData,
88 | AuthFlow: 'USER_PASSWORD_AUTH',
89 | AuthParameters: {
90 | USERNAME: email,
91 | PASSWORD: password,
92 | SECRET_HASH: ""
93 | },
94 | };
95 | const hash = hashSecret(clientSecret, email, clientId);
96 | if (hash && authParams.AuthParameters) {
97 | authParams.AuthParameters.SECRET_HASH = hash;
98 | }
99 | try {
100 | const initiateAuthCommand = new InitiateAuthCommand(authParams);
101 | const authResult = await client.send(initiateAuthCommand);
102 |
103 | if (!authResult.AuthenticationResult) {
104 | res.status(401).json({message: "Invalid credentials"});
105 | return;
106 | }
107 | res.status(200).json({token: authResult.AuthenticationResult.AccessToken});
108 | } catch (err) {
109 | console.log(err);
110 | res.status(401).json({message: 'Invalid credentials'});
111 | }
112 | };*/
113 |
114 | export const logout = async (req: Request, res: Response) => {
115 | const {token} = req.body;
116 | const params = {
117 | ...poolData,
118 | AccessToken: token,
119 | };
120 | try {
121 | const globalSignOutCommand = new GlobalSignOutCommand(params);
122 | await client.send(globalSignOutCommand);
123 | res.status(200).json({message: 'User logged out successfully'});
124 | } catch (err) {
125 | console.log(err);
126 | res.status(500).json({message: 'An error occurred'});
127 | }
128 | };
129 |
130 |
131 | export const verifyPassword = async (email: string, password: string): Promise => {
132 |
133 | if (!(email?.trim() && password?.trim())) {
134 | console.log("Error with email or password");
135 | return false;
136 | }
137 | const params: InitiateAuthCommandInput = {
138 | ClientId: clientId,
139 | AuthFlow: AuthFlowType.USER_PASSWORD_AUTH,
140 | AuthParameters: {
141 | USERNAME: email,
142 | PASSWORD: password,
143 | SECRET_HASH: hashSecret(clientSecret, email, clientId) || ""
144 | },
145 | };
146 | try {
147 | const command = new InitiateAuthCommand(params);
148 | const output = (await client.send(command)) as InitiateAuthCommandOutput;
149 | return Boolean(output.AuthenticationResult);
150 | } catch (error) {
151 | console.log("Error authenticating user:", error);
152 | return false;
153 | }
154 | }
155 |
156 | interface UserAttributes {
157 | Name: string,
158 | Value: string
159 | }
160 |
161 | export const getUserStatus = async (email: string) => {
162 |
163 | const params: AdminGetUserCommandInput = {
164 | ...poolData,
165 | Username: email
166 | };
167 |
168 | try {
169 | const command = new AdminGetUserCommand(params);
170 | const response = await client.send(command);
171 |
172 | // We add a workaround for users, as users who have never been showed a password
173 | // exist in Cognito (with a random password). Therefore, we introduce the
174 | // custom variable "createdByCorbado" in the user pool which is stored on
175 | // every create user event caused by the Corbado web component.
176 |
177 | if (!response || !response.UserAttributes || !Array.isArray(response.UserAttributes)) {
178 | throw new Error('Invalid response object');
179 | }
180 |
181 | let createdByCorbado: boolean = false;
182 |
183 |
184 | response.UserAttributes.forEach((attr: AttributeType | undefined) => {
185 | if (attr && attr.Name === 'custom:createdByCorbado') {
186 | createdByCorbado = attr.Value === 'true';
187 | }
188 | });
189 |
190 | // if Corbado has created the user in AWS Cognito, we send back that the user
191 | // is not an existing user in the sense, he existed prior to Corbado
192 | return {
193 | userStatus: EXISTS,
194 | createdByCorbado: createdByCorbado
195 | };
196 | } catch (error: any) {
197 | if (error.name === 'UserNotFoundException') {
198 | return {
199 | userStatus: NOT_EXISTS,
200 | createdByCorbado: false
201 | };
202 | } else {
203 | throw error;
204 | }
205 | }
206 | }
207 |
208 | interface SessionInfo {
209 | email: string;
210 | cognitoUUID: string;
211 | name: string;
212 | expires: number;
213 | idToken: string;
214 | refreshToken: string;
215 | }
216 |
217 |
218 | export const createSession = async (username: string): Promise => {
219 |
220 | console.log("START CREATE SESSION")
221 | const params: AdminInitiateAuthCommandInput = {
222 | ...poolData,
223 | AuthFlow: AuthFlowType.CUSTOM_AUTH,
224 | ClientId: clientId,
225 | AuthParameters: {
226 | USERNAME: username,
227 | SECRET_HASH : hashSecret(clientSecret, username, clientId) || ""
228 | },
229 | };
230 |
231 | try {
232 | const adminInitiateAuthCommand = new AdminInitiateAuthCommand(params);
233 | const response = await client.send(adminInitiateAuthCommand);
234 |
235 | let answer = "FAILURE";
236 | if (response.ChallengeParameters) {
237 | answer = response.ChallengeParameters.challenge;
238 | }
239 |
240 | console.log("ANSWER:", answer)
241 | const respondToAuthChallengeCommand: RespondToAuthChallengeCommandInput = {
242 | ClientId: clientId,
243 | ChallengeName: ChallengeNameType.CUSTOM_CHALLENGE,
244 | ChallengeResponses: {
245 | ANSWER: answer,
246 | USERNAME: username,
247 | SECRET_HASH : hashSecret(clientSecret, username, clientId) || ""
248 |
249 | },
250 | Session: response.Session
251 | };
252 |
253 | const AuthChallengeCommand = new RespondToAuthChallengeCommand(respondToAuthChallengeCommand);
254 | const authResult = await client.send(AuthChallengeCommand) as RespondToAuthChallengeCommandOutput;
255 |
256 | if (authResult?.AuthenticationResult) {
257 | const token = await validateJWT(
258 | authResult.AuthenticationResult.IdToken as string
259 | )
260 | const user: any = token as any;
261 |
262 | return {
263 | email: user.email as string,
264 | cognitoUUID: user.sub,
265 | name: user.name,
266 | expires: user.exp,
267 | idToken: authResult.AuthenticationResult.IdToken as string,
268 | refreshToken: authResult.AuthenticationResult.RefreshToken as string,
269 | };
270 | } else throw Error("Failed to create session");
271 | } catch (error) {
272 | throw error;
273 | }
274 | }
275 |
276 |
277 | export const createUser = async (email: string) => {
278 | const params: AdminCreateUserCommandInput = {
279 | ...poolData,
280 | Username: email,
281 | DesiredDeliveryMediums: ["EMAIL"],
282 | UserAttributes: [
283 | {Name: 'email', Value: email},
284 | {Name: 'email_verified', Value: 'true'},
285 | {Name: 'custom:createdByCorbado', Value: 'true'}
286 | ],
287 | MessageAction: MessageActionType.SUPPRESS
288 | };
289 | try {
290 | const createUserCommand = new AdminCreateUserCommand(params);
291 | const responseCreateUserCommand = await client.send(createUserCommand);
292 |
293 | console.log("USER SUCCESSFULLY CREATED");
294 |
295 | const setPasswordParams: AdminSetUserPasswordCommandInput = {
296 | ...poolData,
297 | Username: email,
298 | Permanent: true,
299 | Password: generatePassword(15)
300 | };
301 | const confirmUserCommand = new AdminSetUserPasswordCommand(setPasswordParams);
302 | await client.send(confirmUserCommand);
303 | console.log("USER SUCCESSFULLY CONFIRMED");
304 |
305 | return responseCreateUserCommand;
306 | } catch (error: any) {
307 | console.log("Error during user creation: ", error);
308 | return false;
309 | }
310 | }
311 |
312 |
--------------------------------------------------------------------------------
/backend-nodejs/controllers/authCorbadoController.ts:
--------------------------------------------------------------------------------
1 | import {Request, Response} from "express";
2 | // @ts-ignore
3 | import crypto from "crypto";
4 | // @ts-ignore
5 | import jwt from "jsonwebtoken";
6 | import {verifyPassword, getUserStatus, createUser, createSession} from "./authCognitoController";
7 | import {NOT_EXISTS} from "../utils/constants";
8 | const Corbado = require('@corbado/node-sdk');
9 |
10 |
11 | require("dotenv").config({path: '../.env' });
12 |
13 |
14 | // Corbado Node.js SDK
15 | const CORBADO_PROJECT_ID = process.env.CORBADO_PROJECT_ID;
16 | const CORBADO_API_SECRET = process.env.CORBADO_API_SECRET;
17 | const config = new Corbado.Configuration(CORBADO_PROJECT_ID, CORBADO_API_SECRET);
18 | const corbado = new Corbado.SDK(config);
19 |
20 |
21 | export const handleWebhook = async (req: Request, res: Response) => {
22 | try {
23 | // Get the webhook action and act accordingly. Every Corbado
24 | // webhook has an action.
25 |
26 | let request: any;
27 | let response: any;
28 | console.log("BEFORE ACTION");
29 | switch (corbado.webhooks.getAction(req)) {
30 |
31 | // Handle the "authMethods" action which basically checks
32 | // if a user exists on your side/in your database.
33 | case corbado.webhooks.WEBHOOK_ACTION.AUTH_METHODS: {
34 | console.log("WEBHOOK AUTH METHODS");
35 | request = corbado.webhooks.getAuthMethodsRequest(req);
36 |
37 | // Now check if the given user/username exists in your
38 | // database and send status. Implement getUserStatus()
39 | // function below.#
40 | console.log("BEFORE USER STATUS");
41 |
42 | const status = await getUserStatus(request.data.username);
43 | let correctUserStatus = status.userStatus;
44 | if(status.createdByCorbado) {
45 | correctUserStatus = "not_exists"
46 | }
47 | response = corbado.webhooks.getAuthMethodsResponse(correctUserStatus);
48 | res.json(response);
49 | break;
50 | }
51 |
52 | // Handle the "passwordVerify" action which basically checks
53 | // if the given username and password are valid.
54 | case corbado.webhooks.WEBHOOK_ACTION.PASSWORD_VERIFY: {
55 | console.log("WEBHOOK PASSWORD VERIFY");
56 | request = corbado.webhooks.getPasswordVerifyRequest(req);
57 |
58 | // Now check if the given username and password is
59 | // valid. Implement verifyPassword() function below.
60 | const isValid = await verifyPassword(request.data.username, request.data.password)
61 | response = corbado.webhooks.getPasswordVerifyResponse(isValid);
62 | res.json(response);
63 | break;
64 | }
65 | default: {
66 | res.status(400).send('Bad Request');
67 | return;
68 | }
69 | }
70 | } catch (error: any) {
71 |
72 | // We expose the full error message here. Usually you would
73 | // not do this (security!) but in this case Corbado is the
74 | // only consumer of your webhook. The error message gets
75 | // logged at Corbado and helps you and us debugging your
76 | // webhook.
77 | console.log(error);
78 |
79 | // If something went wrong just return HTTP status
80 | // code 500. For successful requests Corbado always
81 | // expects HTTP status code 200. Everything else
82 | // will be treated as error.
83 |
84 | res.status(500).send(error.message);
85 | return;
86 | }
87 | }
88 |
89 | export const authTokenValidate = async (req: Request, res: Response) => {
90 | console.log("AUTH TOKEN VALIDATE STARTED");
91 |
92 | try {
93 | let corbadoAuthToken = req.query["corbadoAuthToken"] as string;
94 | let clientInfo = corbado.utils.getClientInfo(req);
95 | let corbadoUser = await corbado.authTokens.validate(corbadoAuthToken, clientInfo);
96 | let username = JSON.parse(corbadoUser.data.userData).username;
97 | const status = await getUserStatus(username);
98 | console.log("USER EXISTS: ", status.userStatus);
99 |
100 | // if the user does not yet exist in AWS Cognito, add him in AWS Cognito
101 | if (status.userStatus === NOT_EXISTS) {
102 | console.log("CREATING USER...");
103 | await createUser(username);
104 | }
105 |
106 | // create an AWS Session
107 | console.log("GET AWS COGNITO SESSION TOKEN")
108 | let data = await createSession(username);
109 |
110 | res.json(data);
111 | } catch (error: any) {
112 | console.log(error);
113 | res.status(500).send(error.message)
114 | }
115 | };
--------------------------------------------------------------------------------
/backend-nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend-nodejs",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1",
7 | "start": "ts-node app.ts",
8 | "dev": "nodemon app.ts"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@aws-sdk/client-cognito-identity-provider": "^3.321.1",
14 | "@corbado/node-sdk": "^1.1.12",
15 | "amazon-cognito-identity-js": "^6.2.0",
16 | "aws-sdk": "^2.1368.0",
17 | "body-parser": "^1.20.2",
18 | "cors": "^2.8.5",
19 | "dotenv": "^16.0.3",
20 | "express": "^4.18.2",
21 | "express-validator": "^7.0.1",
22 | "jsonwebtoken": "^9.0.0",
23 | "jwk-to-pem": "^2.0.5"
24 | },
25 | "devDependencies": {
26 | "@types/body-parser": "^1.19.2",
27 | "@types/cors": "^2.8.13",
28 | "@types/express": "^4.17.17",
29 | "@types/jsonwebtoken": "^9.0.2",
30 | "@types/jwk-to-pem": "^2.0.1",
31 | "@types/node": "^18.16.3",
32 | "nodemon": "^2.0.22",
33 | "ts-node": "^10.9.1",
34 | "typescript": "^5.0.4"
35 | },
36 | "description": ""
37 | }
38 |
--------------------------------------------------------------------------------
/backend-nodejs/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "target": "es5",
8 | "typeRoots": [
9 | "node_modules/@types"
10 | ],
11 | "types": [
12 | "node"
13 | ]
14 | },
15 | "exclude": [
16 | "test.ts",
17 | "**/*.spec.ts"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/backend-nodejs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "CommonJS",
5 | "outDir": "dist",
6 | "esModuleInterop": true,
7 | "strict": true,
8 | "moduleResolution": "node",
9 | "sourceMap": true
10 | },
11 | "include": ["src/**/*"],
12 | "exclude": ["node_modules", "**/*.spec.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/backend-nodejs/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const EXISTS = 'exists';
2 | export const NOT_EXISTS = 'not_exists';
3 | export const BLOCKED = 'blocked';
4 |
--------------------------------------------------------------------------------
/backend-nodejs/utils/helper.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import crypto from "crypto";
3 | // @ts-ignore
4 | import jwt from "jsonwebtoken";
5 | import {int} from "aws-sdk/clients/datapipeline";
6 | // @ts-ignore
7 | import jwkToPem, {JWK} from "jwk-to-pem";
8 |
9 | require("dotenv").config({path: '../.env' });
10 |
11 | const clientId = process.env.COGNITO_CLIENT_ID;
12 | const region = process.env.COGNITO_REGION;
13 | const envJWKS = process.env.COGNITO_JWKS;
14 | const userPoolId = process.env.COGNITO_USER_POOL_ID;
15 | const jwks: JWK[] | any[] = JSON.parse(envJWKS as string);
16 |
17 | export function hashSecret(clientSecret: string, username: string, clientId: string) {
18 | if (!clientSecret) {
19 | return null;
20 | }
21 | return crypto
22 | .createHmac("SHA256", clientSecret)
23 | .update(username + clientId)
24 | .digest("base64");
25 | }
26 |
27 | export function validateJWT(jwtToken: string, skipExpiredCheck?: boolean) {
28 | let res;
29 | try {
30 | let pem = jwkToPem(jwks[0]);
31 | res = jwt.verify(jwtToken, pem, {algorithms: jwks[0].alg});
32 | } catch (error) {
33 | let pem = jwkToPem(jwks[1]);
34 | res = jwt.verify(jwtToken, pem, {algorithms: jwks[1].alg});
35 | }
36 |
37 | if (!jwtToken.trim()) {
38 | console.log("Error with JWT");
39 | return;
40 | }
41 | let decoded = jwt.decode(jwtToken);
42 | const now = +new Date() / 1000;
43 | // @ts-ignore
44 | if (!skipExpiredCheck && decoded.exp < now) {
45 | console.log("Token expired");
46 | return;
47 | }
48 | // @ts-ignore
49 | if (decoded.aud !== clientId) {
50 | console.log("Invalid audience in token");
51 | return;
52 | }
53 | // @ts-ignore
54 | if (decoded.iss !== `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`) {
55 | console.log("Invalid iss in token");
56 | return;
57 | }
58 | return res;
59 | }
60 |
61 | export function generatePassword(length: int) {
62 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+~`|}{[]:;?><,./-=";
63 | let password = "";
64 | for (let i = 0; i < length; i++) {
65 | const randomIndex = Math.floor(Math.random() * charset.length);
66 | password += charset[randomIndex];
67 | }
68 | return password;
69 | };
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | frontend:
4 | build:
5 | context: ./frontend-angular
6 | dockerfile: Dockerfile
7 | volumes:
8 | - './frontend-angular:/app'
9 | ports:
10 | - '4200:4200'
11 | backend:
12 | build:
13 | context: ./backend-nodejs
14 | dockerfile: Dockerfile
15 | volumes:
16 | - './backend-nodejs:/app'
17 | ports:
18 | - '3000:3000'
19 | environment:
20 | - COGNITO_REGION=${COGNITO_REGION}
21 | - COGNITO_USER_POOL_ID=${COGNITO_USER_POOL_ID}
22 | - COGNITO_CLIENT_ID=${COGNITO_CLIENT_ID}
23 | - COGNITO_CLIENT_SECRET=${COGNITO_CLIENT_SECRET}
24 | - COGNITO_JWKS=${COGNITO_JWKS}
25 | - CORBADO_PROJECT_ID=${CORBADO_PROJECT_ID}
26 | - CORBADO_API_SECRET=${CORBADO_API_SECRET}
27 | - CORBADO_CLI_SECRET=${CORBADO_CLI_SECRET}
28 | - CORBADO_WEBHOOK_USERNAME=${CORBADO_WEBHOOK_USERNAME}
29 | - CORBADO_WEBHOOK_PASSWORD=${CORBADO_WEBHOOK_PASSWORD}
30 | - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
31 | - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
--------------------------------------------------------------------------------
/frontend-angular/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/frontend-angular/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # Compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | /bazel-out
8 |
9 | # Node
10 | /node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # IDEs and editors
15 | .idea/
16 | .project
17 | .classpath
18 | .c9/
19 | *.launch
20 | .settings/
21 | *.sublime-workspace
22 |
23 | # Visual Studio Code
24 | .vscode/*
25 | !.vscode/settings.json
26 | !.vscode/tasks.json
27 | !.vscode/launch.json
28 | !.vscode/extensions.json
29 | .history/*
30 |
31 | # Miscellaneous
32 | /.angular/cache
33 | .sass-cache/
34 | /connect.lock
35 | /coverage
36 | /libpeerconnection.log
37 | testem.log
38 | /typings
39 |
40 | # System files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/frontend-angular/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Node.js runtime as a parent image
2 | FROM node:16-alpine
3 |
4 | # Set the working directory to /app
5 | WORKDIR /app
6 |
7 | # Copy the package.json and package-lock.json files to the container
8 | COPY package*.json ./
9 |
10 | # Expose the port used by the frontend
11 | EXPOSE 4200
12 |
13 | # Start the frontend using the "ng serve" command
14 | CMD ["/bin/sh", "-c", "npm install && npm install -g @angular/cli && ng serve --host 0.0.0.0"]
15 |
--------------------------------------------------------------------------------
/frontend-angular/README.md:
--------------------------------------------------------------------------------
1 | # FrontendAngular
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.7.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
28 |
--------------------------------------------------------------------------------
/frontend-angular/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "frontend-angular": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:browser",
19 | "options": {
20 | "outputPath": "dist/frontend-angular",
21 | "index": "src/index.html",
22 | "main": "src/main.ts",
23 | "polyfills": [
24 | "zone.js"
25 | ],
26 | "tsConfig": "tsconfig.app.json",
27 | "inlineStyleLanguage": "scss",
28 | "assets": [
29 | "src/favicon.ico",
30 | "src/assets"
31 | ],
32 | "styles": [
33 | "src/styles.scss"
34 | ],
35 | "scripts": []
36 | },
37 | "configurations": {
38 | "production": {
39 | "budgets": [
40 | {
41 | "type": "initial",
42 | "maximumWarning": "500kb",
43 | "maximumError": "1mb"
44 | },
45 | {
46 | "type": "anyComponentStyle",
47 | "maximumWarning": "2kb",
48 | "maximumError": "4kb"
49 | }
50 | ],
51 | "outputHashing": "all"
52 | },
53 | "development": {
54 | "buildOptimizer": false,
55 | "optimization": false,
56 | "vendorChunk": true,
57 | "extractLicenses": false,
58 | "sourceMap": true,
59 | "namedChunks": true
60 | }
61 | },
62 | "defaultConfiguration": "production"
63 | },
64 | "serve": {
65 | "builder": "@angular-devkit/build-angular:dev-server",
66 | "configurations": {
67 | "production": {
68 | "browserTarget": "frontend-angular:build:production"
69 | },
70 | "development": {
71 | "browserTarget": "frontend-angular:build:development"
72 | }
73 | },
74 | "defaultConfiguration": "development"
75 | },
76 | "extract-i18n": {
77 | "builder": "@angular-devkit/build-angular:extract-i18n",
78 | "options": {
79 | "browserTarget": "frontend-angular:build"
80 | }
81 | },
82 | "test": {
83 | "builder": "@angular-devkit/build-angular:karma",
84 | "options": {
85 | "polyfills": [
86 | "zone.js",
87 | "zone.js/testing"
88 | ],
89 | "tsConfig": "tsconfig.spec.json",
90 | "inlineStyleLanguage": "scss",
91 | "assets": [
92 | "src/favicon.ico",
93 | "src/assets"
94 | ],
95 | "styles": [
96 | "src/styles.scss"
97 | ],
98 | "scripts": []
99 | }
100 | }
101 | }
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/frontend-angular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-angular",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "watch": "ng build --watch --configuration development",
9 | "test": "ng test"
10 | },
11 | "private": true,
12 | "dependencies": {
13 | "@angular/animations": "^15.2.0",
14 | "@angular/common": "^15.2.0",
15 | "@angular/compiler": "^15.2.0",
16 | "@angular/core": "^15.2.0",
17 | "@angular/forms": "^15.2.0",
18 | "@angular/platform-browser": "^15.2.0",
19 | "@angular/platform-browser-dynamic": "^15.2.0",
20 | "@angular/router": "^15.2.0",
21 | "rxjs": "~7.8.0",
22 | "tslib": "^2.3.0",
23 | "zone.js": "~0.12.0"
24 | },
25 | "devDependencies": {
26 | "@angular-devkit/build-angular": "^15.2.7",
27 | "@angular/cli": "~15.2.7",
28 | "@angular/compiler-cli": "^15.2.0",
29 | "@types/jasmine": "~4.3.0",
30 | "jasmine-core": "~4.5.0",
31 | "karma": "~6.4.0",
32 | "karma-chrome-launcher": "~3.1.0",
33 | "karma-coverage": "~2.2.0",
34 | "karma-jasmine": "~5.1.0",
35 | "karma-jasmine-html-reporter": "~2.0.0",
36 | "typescript": "~4.9.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 |
4 | import { AuthComponent } from './auth/auth.component';
5 | import { LoggedInComponent } from './logged-in/logged-in.component';
6 |
7 | const routes: Routes = [
8 | { path: '', redirectTo: '/auth', pathMatch: 'full' },
9 | { path: 'auth', component: AuthComponent },
10 | { path: 'logged-in', component: LoggedInComponent }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forRoot(routes)],
15 | exports: [RouterModule]
16 | })
17 | export class AppRoutingModule { }
18 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corbado/passkeys-amazon-cognito/c0bb18986424f7d4e3b44bdfe59a7ad26b47441f/frontend-angular/src/app/app.component.scss
--------------------------------------------------------------------------------
/frontend-angular/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { AppComponent } from './app.component';
4 |
5 | describe('AppComponent', () => {
6 | beforeEach(async () => {
7 | await TestBed.configureTestingModule({
8 | imports: [
9 | RouterTestingModule
10 | ],
11 | declarations: [
12 | AppComponent
13 | ],
14 | }).compileComponents();
15 | });
16 |
17 | it('should create the app', () => {
18 | const fixture = TestBed.createComponent(AppComponent);
19 | const app = fixture.componentInstance;
20 | expect(app).toBeTruthy();
21 | });
22 |
23 | it(`should have as title 'frontend-angular'`, () => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | const app = fixture.componentInstance;
26 | expect(app.title).toEqual('frontend-angular');
27 | });
28 |
29 | it('should render title', () => {
30 | const fixture = TestBed.createComponent(AppComponent);
31 | fixture.detectChanges();
32 | const compiled = fixture.nativeElement as HTMLElement;
33 | expect(compiled.querySelector('.content span')?.textContent).toContain('frontend-angular app is running!');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.scss']
7 | })
8 | export class AppComponent {
9 | title = 'frontend-angular';
10 | }
11 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {CUSTOM_ELEMENTS_SCHEMA, NgModule} from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { FormsModule } from '@angular/forms';
4 | import {HttpClientModule} from "@angular/common/http";
5 | import { AppRoutingModule } from './app-routing.module';
6 | import { AppComponent } from './app.component';
7 | import { AuthComponent } from './auth/auth.component';
8 | import { LoggedInComponent } from './logged-in/logged-in.component';
9 |
10 | @NgModule({
11 | declarations: [
12 | AppComponent,
13 | AuthComponent,
14 | LoggedInComponent
15 | ],
16 | imports: [
17 | BrowserModule,
18 | FormsModule,
19 | AppRoutingModule,
20 | HttpClientModule
21 | ],
22 | providers: [],
23 | bootstrap: [AppComponent],
24 | schemas: [CUSTOM_ELEMENTS_SCHEMA]
25 | })
26 | export class AppModule { }
27 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { AuthService } from './auth.service';
4 |
5 | describe('AuthService', () => {
6 | let service: AuthService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(AuthService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 | import {HttpClient} from '@angular/common/http';
3 | import {Router} from '@angular/router';
4 | import {map, Observable, Subject} from 'rxjs';
5 |
6 | @Injectable({
7 | providedIn: 'root'
8 | })
9 | export class AuthService {
10 |
11 | private isAuthenticated = false;
12 | private authStatusListener = new Subject();
13 | private token: string = '';
14 | private userId: string = '';
15 |
16 | constructor(private http: HttpClient, private router: Router) {
17 | }
18 |
19 | getToken() {
20 | return this.token;
21 | }
22 |
23 | getUserId() {
24 | return this.userId;
25 | }
26 |
27 | getAuthStatusListener() {
28 | return this.authStatusListener.asObservable();
29 | }
30 |
31 | signUp(email: string, password: string): Observable {
32 | const signUpData = {email: email, password: password};
33 | return this.http.post<{ message: string }>('http://localhost:3000/api/auth/signup', signUpData);
34 | }
35 |
36 | login(email: string, password: string): Observable {
37 | const loginData = {email: email, password: password};
38 | return this.http.post<{ token: string }>('http://localhost:3000/api/auth/login', loginData)
39 | .pipe(
40 | map(responseData => {
41 | const token = responseData.token;
42 | this.token = token;
43 | console.log(responseData)
44 | if (token) {
45 | this.isAuthenticated = true;
46 | this.authStatusListener.next(true);
47 | this.router.navigate(['/logged-in']);
48 | }
49 | })
50 | );
51 | }
52 |
53 | logout() {
54 | this.token = '';
55 | this.isAuthenticated = false;
56 | this.authStatusListener.next(false);
57 | this.router.navigate(['/login']);
58 | }
59 |
60 | getIsAuth() {
61 | return this.isAuthenticated;
62 | }
63 |
64 | async corbadoAuthTokenValidate(corbadoAuthToken: string) {
65 | try {
66 | this.http.get<{ idToken: string }>(`http://localhost:3000/api/corbado/authTokenValidate?corbadoAuthToken=${corbadoAuthToken}`)
67 | .subscribe(responseData => {
68 | const token = responseData.idToken;
69 | this.token = token;
70 | if (token) {
71 | this.isAuthenticated = true;
72 | this.authStatusListener.next(true);
73 | this.router.navigate(['/logged-in']);
74 | }
75 | });
76 | } catch (error) {
77 | console.error(error);
78 | throw error;
79 | }
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth/auth.component.html:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
21 |
22 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth/auth.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corbado/passkeys-amazon-cognito/c0bb18986424f7d4e3b44bdfe59a7ad26b47441f/frontend-angular/src/app/auth/auth.component.scss
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth/auth.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { AuthComponent } from './auth.component';
4 |
5 | describe('AuthComponent', () => {
6 | let component: AuthComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ AuthComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(AuthComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/auth/auth.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {ActivatedRoute, Router} from '@angular/router';
3 | import {AuthService} from '../auth.service';
4 | import {Subscription} from 'rxjs';
5 |
6 | @Component({
7 | selector: 'app-auth',
8 | templateUrl: './auth.component.html',
9 | styleUrls: ['./auth.component.scss']
10 | })
11 | export class AuthComponent implements OnInit {
12 |
13 | email = '';
14 | password = '';
15 | errorMessage = '';
16 | queryParamsSubscription!: Subscription;
17 |
18 | constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute) {
19 | this.queryParamsSubscription = this.route.queryParams.subscribe((queryParams) => {
20 | if (queryParams['corbadoAuthToken'] != undefined) {
21 | let corbadoAuthToken = queryParams['corbadoAuthToken'];
22 | this.authService.corbadoAuthTokenValidate(corbadoAuthToken)
23 | .then(res => {
24 | router.navigate(['/logged-in'])
25 | })
26 | .catch(error => console.log(error));
27 | }
28 | })
29 | }
30 |
31 | ngOnInit(): void {
32 | }
33 |
34 | signUp(): void {
35 | this.authService.signUp(this.email, this.password).subscribe(
36 | (response) => {
37 | console.log(response);
38 | this.router.navigate(['/logged-in']);
39 | },
40 | (error) => {
41 | console.log(error);
42 | this.errorMessage = error.error.message;
43 | }
44 | );
45 | }
46 |
47 | login(): void {
48 | this.authService.login(this.email, this.password).subscribe(
49 | (response) => {
50 | console.log(response);
51 | this.router.navigate(['/logged-in']);
52 | },
53 | (error) => {
54 | console.log(error);
55 | this.errorMessage = error.error.message;
56 | }
57 | );
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/logged-in/logged-in.component.html:
--------------------------------------------------------------------------------
1 | Welcome!
2 |
3 | You are now logged in.
4 |
5 | Logout
6 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/logged-in/logged-in.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corbado/passkeys-amazon-cognito/c0bb18986424f7d4e3b44bdfe59a7ad26b47441f/frontend-angular/src/app/logged-in/logged-in.component.scss
--------------------------------------------------------------------------------
/frontend-angular/src/app/logged-in/logged-in.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LoggedInComponent } from './logged-in.component';
4 |
5 | describe('LoggedInComponent', () => {
6 | let component: LoggedInComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ LoggedInComponent ]
12 | })
13 | .compileComponents();
14 |
15 | fixture = TestBed.createComponent(LoggedInComponent);
16 | component = fixture.componentInstance;
17 | fixture.detectChanges();
18 | });
19 |
20 | it('should create', () => {
21 | expect(component).toBeTruthy();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/frontend-angular/src/app/logged-in/logged-in.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from '@angular/core';
2 | import {Router} from '@angular/router';
3 | import {AuthService} from '../auth.service';
4 |
5 | @Component({
6 | selector: 'app-logged-in',
7 | templateUrl: './logged-in.component.html',
8 | styleUrls: ['./logged-in.component.scss']
9 | })
10 | export class LoggedInComponent implements OnInit {
11 |
12 | constructor(private authService: AuthService, private router: Router) {
13 | }
14 |
15 | ngOnInit(): void {
16 | }
17 |
18 | logout(): void {
19 | this.authService.logout();
20 | this.router.navigate(['/auth']);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/frontend-angular/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corbado/passkeys-amazon-cognito/c0bb18986424f7d4e3b44bdfe59a7ad26b47441f/frontend-angular/src/assets/.gitkeep
--------------------------------------------------------------------------------
/frontend-angular/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/corbado/passkeys-amazon-cognito/c0bb18986424f7d4e3b44bdfe59a7ad26b47441f/frontend-angular/src/favicon.ico
--------------------------------------------------------------------------------
/frontend-angular/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FrontendAngular
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/frontend-angular/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app/app.module';
4 |
5 |
6 | platformBrowserDynamic().bootstrapModule(AppModule)
7 | .catch(err => console.error(err));
8 |
--------------------------------------------------------------------------------
/frontend-angular/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/frontend-angular/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/frontend-angular/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "target": "ES2022",
20 | "module": "ES2022",
21 | "useDefineForClassFields": false,
22 | "lib": [
23 | "ES2022",
24 | "dom"
25 | ]
26 | },
27 | "angularCompilerOptions": {
28 | "enableI18nLegacyMessageIdFormat": false,
29 | "strictInjectionParameters": true,
30 | "strictInputAccessModifiers": true,
31 | "strictTemplates": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend-angular/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "include": [
11 | "src/**/*.spec.ts",
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------