├── src
├── views
│ ├── Profile.js
│ ├── Logout.js
│ ├── DescopeLogin.js
│ └── Dashboard.js
├── setupTests.js
├── App.test.js
├── index.css
├── reportWebVitals.js
├── index.js
├── App.js
├── logo.svg
└── App.css
├── public
├── robots.txt
├── favicon.ico
├── logo192.png
├── logo512.png
├── manifest.json
└── index.html
├── .gitignore
├── package.json
├── README.md
└── oidc-flow.json
/src/views/Profile.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/descope-sample-apps/aws-cognito-oidc-sample/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/descope-sample-apps/aws-cognito-oidc-sample/HEAD/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/descope-sample-apps/aws-cognito-oidc-sample/HEAD/public/logo512.png
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/views/Logout.js:
--------------------------------------------------------------------------------
1 | import { Auth } from "aws-amplify";
2 |
3 | async function handleSignOut(event) {
4 | event.preventDefault();
5 | try {
6 | await Auth.signOut();
7 | } catch (error) {
8 | console.log("error signing out: ", error);
9 | }
10 | setUser(null);
11 | setIsLoading(false);
12 | }
13 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/views/DescopeLogin.js:
--------------------------------------------------------------------------------
1 | import { Descope } from "@descope/react-sdk";
2 |
3 |
4 | export const DescopeLogin = () => {
5 | return (
6 |
7 |
8 | console.log("Logged in!")}
11 | onError={(e) => console.log("Could not log in!")}
12 | theme="light"
13 | />
14 |
15 |
16 | );
17 | };
18 |
19 | export default DescopeLogin;
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 |
4 | #amplify-do-not-edit-begin
5 | amplify/\#current-cloud-backend
6 | amplify/.config/local-*
7 | amplify/logs
8 | amplify/mock-data
9 | amplify/mock-api-resources
10 | amplify/backend/amplify-meta.json
11 | amplify/backend/.temp
12 | build/
13 | dist/
14 | node_modules/
15 | aws-exports.js
16 | awsconfiguration.json
17 | amplifyconfiguration.json
18 | amplifyconfiguration.dart
19 | amplify-build-config.json
20 | amplify-gradle-config.json
21 | amplifytools.xcconfig
22 | .secret-*
23 | **.sample
24 | #amplify-do-not-edit-end
25 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-cognito-hosted-ui-react",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@descope/react-sdk": "^1.0.8",
7 | "@testing-library/jest-dom": "^5.16.4",
8 | "@testing-library/react": "^13.1.1",
9 | "@testing-library/user-event": "^13.5.0",
10 | "aws-amplify": "^4.3.18",
11 | "axios": "^0.27.2",
12 | "react": "^18.1.0",
13 | "react-dom": "^18.1.0",
14 | "react-json-pretty": "^2.2.0",
15 | "react-router-dom": "^6.3.0",
16 | "react-scripts": "5.0.1",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./index.css";
4 | import { BrowserRouter, Route, Routes } from "react-router-dom";
5 | import App from "./App";
6 | import Dashboard from "./views/Dashboard";
7 | import reportWebVitals from "./reportWebVitals";
8 | import DescopeLogin from "./views/DescopeLogin";
9 | import Profile from "./views/Profile";
10 | import { AuthProvider } from "@descope/react-sdk";
11 |
12 | const root = ReactDOM.createRoot(document.getElementById("root"));
13 | root.render(
14 |
15 |
16 |
17 |
18 | }>
19 | }>
20 | }>
21 | }>
22 |
23 |
24 |
25 |
26 | );
27 |
28 | // If you want to start measuring performance in your app, pass a function
29 | // to log results (for example: reportWebVitals(console.log))
30 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
31 | reportWebVitals();
32 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AWS Cognito with Descope in Hosted UI Sample App
2 |
3 | ## Description
4 |
5 | This is a very simple react app to demonstrate how to implement Descope as an OIDC provider with Cognito by using AWS Amplify.
6 |
7 | > **Note**: If you need the flow, to work with OIDC and Passkeys, it is the `oidc-flow.json` file in the root of this directory. This can be downloaded and imported via the [Descope Console](https://app.descope.com/flows).
8 |
9 | ## Project Setup
10 |
11 | 1. Create `.env` for env variables;
12 |
13 | ```
14 | REACT_APP_USERPOOL_ID=_
15 | REACT_APP_USERPOOL_WEB_CLIENT_ID=
16 | REACT_APP_AWS_REGION=
17 | REACT_APP_COGNITO_HOSTED_UI=
18 | REACT_APP_COGNITO_DOMAIN=
19 | REACT_APP_DESCOPE_PROJECT_ID=
20 | ```
21 |
22 | > **Note**: This app uses the AWS Hosted UI, so it can be easily re-configured. The app would operately similarly however if a custom login page was used instead.
23 |
24 | 2. Install all of the packages
25 |
26 | You can install all of the necessary packages by running `yarn install`
27 |
28 | ## Run
29 |
30 | Run the program using:
31 |
32 | `yarn start`
33 |
34 | ## Issue Reporting
35 |
36 | If you have found a bug or if you have a feature request, please report them at this repository issues section.
37 |
38 | ## Author
39 |
40 | [Descope](https://descope.com)
41 |
42 | ## License
43 |
44 | This project is licensed under the MIT license.
45 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import Amplify from "aws-amplify";
3 | import React from "react";
4 |
5 |
6 | Amplify.configure({
7 | Auth: {
8 | // REQUIRED - Amazon Cognito Region
9 | region: process.env.REACT_APP_AWS_REGION,
10 |
11 | // OPTIONAL - Amazon Cognito User Pool ID
12 | userPoolId: process.env.REACT_APP_USERPOOL_ID,
13 |
14 | // OPTIONAL - Amazon Cognito Web Client ID (26-char alphanumeric string)
15 | userPoolWebClientId: process.env.REACT_APP_USERPOOL_WEB_CLIENT_ID,
16 |
17 | // OPTIONAL - Enforce user authentication prior to accessing AWS resources or not
18 | mandatorySignIn: false,
19 |
20 | // OPTIONAL - Manually set the authentication flow type. Default is 'USER_SRP_AUTH'
21 | authenticationFlowType: "USER_PASSWORD_AUTH",
22 |
23 | // OPTIONAL - Hosted UI configuration
24 | oauth: {
25 | domain: `${process.env.REACT_APP_COGNITO_DOMAIN}.auth.us-west-2.amazoncognito.com`,
26 | scope: ["openid", "email", "phone", "profile"],
27 | redirectSignIn: "https://main.d1rua11q42kscj.amplifyapp.com/dashboard",
28 | redirectSignOut: "https://main.d1rua11q42kscj.amplifyapp.com/",
29 | responseType: "code", // or 'token', note that REFRESH token will only be generated when the responseType is code
30 | },
31 | },
32 | });
33 |
34 | function App(props) {
35 | return (
36 | <>
37 |
38 |
Sample App
39 |
48 |
49 |
50 |
51 |
Cognito Sample App with Hosted UI and Descope as an OIDC Provider
52 |
Powered by Descope
53 |
62 |
63 | >
64 | );
65 | }
66 |
67 | export default App;
68 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/views/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "../App.css";
3 | import { Auth } from "aws-amplify";
4 | import { Navigate } from "react-router-dom";
5 | import axios from "axios";
6 |
7 |
8 | import JSONPretty from "react-json-pretty";
9 | import "react-json-pretty/themes/monikai.css";
10 |
11 |
12 | function Dashboard(props) {
13 | const [user, setUser] = useState();
14 | const [apiResponse, setApiResponse] = useState("");
15 | const [isLoading, setIsLoading] = useState(true);
16 |
17 | async function getCurrentUser() {
18 | try {
19 | const authUser = await Auth.currentAuthenticatedUser();
20 | console.log(JSON.stringify(authUser));
21 | setUser(authUser);
22 | } catch (e) {
23 | console.log("error happened", e);
24 | setUser(null);
25 | setIsLoading(false);
26 | }
27 | }
28 |
29 | useEffect(() => {
30 | getCurrentUser();
31 | }, []);
32 |
33 | async function handleCallProtectedMethod(event) {
34 | event.preventDefault();
35 | const accessToken = user["signInUserSession"]["accessToken"]["jwtToken"];
36 | const headers = {
37 | Authorization: "Bearer " + accessToken,
38 | };
39 | const apiResp = await axios.get(
40 | `https://hkbcq1nsnh.execute-api.us-west-2.amazonaws.com/Stage/oidc-get`,
41 | { headers }
42 | );
43 | setApiResponse(JSON.stringify(apiResp.data));
44 | }
45 |
46 | async function handleSignOut(event) {
47 | event.preventDefault();
48 | try {
49 | await Auth.signOut();
50 | } catch (error) {
51 | console.log("error signing out: ", error);
52 | }
53 | setUser(null);
54 | setIsLoading(false);
55 | }
56 |
57 | if (!user && !isLoading) {
58 | return ;
59 | }
60 |
61 | return (
62 | <>
63 |
64 |
Sample App
65 |
68 |
69 |
70 |
71 |
You can test the AWS API Gateway Here
72 |
73 | Press this button to make a GET HTTP request to the OIDC-Test endpoint
74 | configured with our AWS API Gateway
75 |
76 |
79 |
80 |
81 |
82 |
83 | >
84 | );
85 | }
86 |
87 | export default Dashboard;
88 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;700&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap");
3 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap");
4 | @import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400&display=swap");
5 |
6 | html,
7 | body {
8 | font-family: "Inter", sans-serif;
9 | background-color: aliceblue;
10 | }
11 | h1,
12 | h2,
13 | p {
14 | max-width: 90vw;
15 | }
16 |
17 | /* ====== HOME PAGE ====== */
18 | .home {
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | text-align: center;
24 | }
25 | .blue {
26 | color: #167ae4;
27 | }
28 | .home {
29 | padding: 4vw 0;
30 | }
31 | .title {
32 | font-weight: 500;
33 | margin-top: 0;
34 | font-size: 4em;
35 | width: 70vw;
36 | }
37 | .powered {
38 | margin: 0;
39 | display: flex;
40 | flex-direction: row;
41 | align-items: center;
42 | justify-content: center;
43 | background-color: #e5f2ff;
44 | padding: 20px 40px;
45 | border-radius: 8px;
46 | }
47 | .blue-dot {
48 | width: 10px;
49 | height: 10px;
50 | border-radius: 100px;
51 | margin-right: 15px;
52 | box-shadow: 0px 0px 5px #71adec, 0px 0px 5px #78b1ef;
53 | background-color: #167ae4;
54 | }
55 | .dashboard-btn {
56 | margin-top: 5vh;
57 | background-color: #167ae4;
58 | border: none;
59 | padding: 15px 35px;
60 | padding-right: 30px;
61 | font-size: 1.15em;
62 | color: aliceblue;
63 | border-radius: 8px;
64 | }
65 | .arrow {
66 | font-family: "Space Grotesk", sans-serif;
67 | }
68 | .dashboard-btn:hover {
69 | background-color: #0d6bd0;
70 | }
71 |
72 | /* ====== Dashboard ====== */
73 | .dash-title {
74 | font-size: 2.2em;
75 | font-weight: 500;
76 | }
77 | .logout-btn {
78 | color: rgb(255, 68, 26);
79 | }
80 | .api-button {
81 | border: 2px solid #c9e1fb;
82 | cursor: pointer;
83 | background-color: #e5f2ff;
84 | color: #167ae4;
85 | padding: 2vh 5vh;
86 | font-size: 1.1em;
87 | border-radius: 8px;
88 | margin-top: 3vh;
89 | }
90 | .api-button:hover {
91 | box-shadow: 0px 0px 5px #cce6ff;
92 | }
93 | .api-response-box {
94 | padding: 2vh;
95 | margin-top: 5vh;
96 | width: 90vw;
97 | text-align: left;
98 | }
99 |
100 | .main-container-login {
101 | display: flex;
102 | align-items: center;
103 | justify-content: center;
104 | width: 120vw;
105 | height: 100vh;
106 | }
107 |
108 | .login-container {
109 | width: 100%;
110 | max-width: 800px;
111 | padding: 20px;
112 | }
113 |
114 | /* ====== NAVBAR ====== */
115 | .sample-logo {
116 | font-family: "Space Grotesk", sans-serif;
117 | }
118 | .navbar {
119 | display: flex;
120 | justify-content: space-between;
121 | padding: 4vh 7vh;
122 | background-color: none;
123 | }
124 | .navbar h2 {
125 | margin: 0;
126 | }
127 | .login-btn {
128 | border: none;
129 | cursor: pointer;
130 | font-size: 1em;
131 | font-family: "Golos Text", sans-serif;
132 | background-color: #e5f2ff;
133 | padding: 12px 4vh;
134 | border-radius: 8px;
135 | /* color: #3965a2; */
136 | }
137 | .login-btn:hover {
138 | background-color: #d4e9ff;
139 | }
140 |
141 | /* ===== Mobile ===== */
142 | @media only screen and (max-width: 768px) {
143 | .title {
144 | font-size: 2.5em;
145 | }
146 | .navbar {
147 | padding: 4vh 3vh;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/oidc-flow.json:
--------------------------------------------------------------------------------
1 | {"screens":[{"id":"SC2QqHKqIewjP0zmAW0pUCSdbXoaN","version":7,"flowId":"oidc-flow","inputs":[],"interactions":[{"id":"pnOqEGpL1b","type":"","label":"Choose another authentication method","icon":"","subType":""},{"id":"resend","type":"","label":"Send again","icon":"","subType":""},{"id":"submit","type":"","label":"Submit","icon":"","subType":""}],"htmlTemplate":{"L1T9iO4p5U":{"custom":{},"displayName":"One Time Code","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"yZNZ2ZVVu_","props":{"data-errormessage-pattern-mismatch":"Must be a 6 digits number","digits":6,"fullWidth":false,"id":"L1T9iO4p5U","label":"","name":"code","required":true,"size":"lg","type":"tel"},"type":{"resolvedName":"Code"}},"ROOT":{"custom":{},"displayName":"Container","hidden":false,"isCanvas":true,"linkedNodes":{},"nodes":["auED4dEJkJ","eBGiu9cYSb","yZNZ2ZVVu_"],"props":{"align":"center","background":"","data-editor-type":"container","direction":"col","id":"ROOT","justify":"center","paddingX":"4","paddingY":"4","spaceBetween":"5","width":"100"},"type":{"resolvedName":"Container"}},"auED4dEJkJ":{"custom":{},"displayName":"Text","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"align":"center","children":"We've sent a message containing a 6-digit code to {{sentTo.maskedEmail}}","fullWidth":false,"id":"auED4dEJkJ","italic":false,"typography":"subtitle1","variant":"primary"},"type":{"resolvedName":"Text"}},"eBGiu9cYSb":{"custom":{},"displayName":"Text","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"align":"center","children":"Enter it below and press the button to continue","fullWidth":false,"id":"eBGiu9cYSb","italic":false,"typography":"subtitle2","variant":"primary"},"type":{"resolvedName":"Text"}},"fyz-aN6hyx":{"custom":{},"displayName":"Error Message","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"thNbUTWOGY","props":{"align":"center","color":"error","data-type":"error-message","fullWidth":false,"id":"fyz-aN6hyx","italic":false,"typography":"body1"},"type":{"resolvedName":"ErrorMessage"}},"oOOUcyEOVr":{"custom":{},"displayName":"Container","hidden":false,"isCanvas":true,"linkedNodes":{},"nodes":["resend","pnOqEGpL1b"],"parent":"yZNZ2ZVVu_","props":{"align":"start","background":"#ffffff00","data-editor-type":"container","direction":"row","id":"oOOUcyEOVr","justify":"center","paddingX":"5","paddingY":"0","spaceBetween":"0","width":"100"},"type":{"resolvedName":"Container"}},"pnOqEGpL1b":{"custom":{"type":"button"},"displayName":"Button","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"oOOUcyEOVr","props":{"children":"Choose another authentication method","color":"primary","data-type":"button","formNoValidate":true,"fullWidth":true,"id":"pnOqEGpL1b","shape":"","size":"sm","variant":"link"},"type":{"resolvedName":"Button"}},"resend":{"custom":{"type":"button"},"displayName":"Button","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"oOOUcyEOVr","props":{"children":"Send again","color":"primary","data-type":"button","formNoValidate":true,"fullWidth":true,"id":"resend","shape":"","size":"sm","variant":"link"},"type":{"resolvedName":"Button"}},"submit":{"custom":{"type":"button"},"displayName":"Button","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"thNbUTWOGY","props":{"children":"Submit","color":"primary","data-type":"button","formNoValidate":false,"fullWidth":true,"id":"submit","shape":"","size":"md","variant":"contained"},"type":{"resolvedName":"Button"}},"thNbUTWOGY":{"custom":{},"displayName":"Container","hidden":false,"isCanvas":true,"linkedNodes":{},"nodes":["submit","fyz-aN6hyx"],"parent":"yZNZ2ZVVu_","props":{"align":"start","background":"#ffffff00","data-editor-type":"container","direction":"row","id":"thNbUTWOGY","justify":"center","paddingX":"4","paddingY":"3","spaceBetween":"1","width":"100"},"type":{"resolvedName":"Container"}},"yZNZ2ZVVu_":{"custom":{},"displayName":"Container","hidden":false,"isCanvas":true,"linkedNodes":{},"nodes":["L1T9iO4p5U","thNbUTWOGY","oOOUcyEOVr"],"parent":"ROOT","props":{"align":"center","background":"","data-editor-type":"container","direction":"col","id":"yZNZ2ZVVu_","justify":"start","paddingX":"4","paddingY":"4","spaceBetween":"1.5","width":"100"},"type":{"resolvedName":"Container"}}}},{"id":"SC2QqHKtZYR6cP00mjyjRU43JAMym","version":7,"flowId":"oidc-flow","inputs":[],"interactions":[{"id":"4EMLSPCn8j","type":"","label":"Login with Biometrics","icon":"fingerprint","subType":""}],"htmlTemplate":{"1sMmkeVxVT":{"custom":{},"displayName":"Error Message","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"align":"center","color":"error","data-type":"error-message","fullWidth":false,"id":"1sMmkeVxVT","italic":false,"typography":"body1"},"type":{"resolvedName":"ErrorMessage"}},"4EMLSPCn8j":{"custom":{"type":"button"},"displayName":"Biometrics Button","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"children":"Login with Biometrics","color":"primary","data-type":"biometrics","formNoValidate":false,"fullWidth":false,"id":"4EMLSPCn8j","shape":"","size":"md","startIcon":"fingerprint","startIconColorText":true,"variant":"outline"},"type":{"resolvedName":"BiometricsButton"}},"Alqr97dFtY":{"custom":{"type":"input"},"displayName":"Email Input","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"data-errormessage-pattern-mismatch":"Must be a valid email","fullWidth":true,"id":"Alqr97dFtY","label":"Email","max":100,"name":"email","pattern":"^[\\w._%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$","placeholder":"Email","required":false,"size":"md","type":"email"},"type":{"resolvedName":"EmailInput"}},"LmrSIGOWZb":{"custom":{},"displayName":"Text","hidden":false,"isCanvas":false,"linkedNodes":{},"nodes":[],"parent":"ROOT","props":{"align":"center","children":"Welcome!","fullWidth":false,"id":"LmrSIGOWZb","italic":false,"typography":"h3","variant":"primary"},"type":{"resolvedName":"Text"}},"ROOT":{"custom":{},"displayName":"Container","hidden":false,"isCanvas":true,"linkedNodes":{},"nodes":["LmrSIGOWZb","Alqr97dFtY","4EMLSPCn8j","1sMmkeVxVT"],"props":{"align":"center","background":"","data-editor-type":"container","direction":"col","id":"ROOT","justify":"center","paddingX":"4","paddingY":"8","spaceBetween":"4","width":"100"},"type":{"resolvedName":"Container"}}}}],"flow":{"id":"oidc-flow","version":8,"name":"Passkey OIDC Flow","description":"You should use this flow to implement Passkey authentication, when using Descope as an OIDC Provider with another IdP.","dsl":{"startTask":"1","tasks":{"0":{"action":"logged-in","id":"0","name":"End","next":{"rules":[]},"view":{"x":4148.528315394407,"y":-458.1860446534223}},"1":{"id":"1","name":"Sign in with Passkeys","next":{"rules":[{"interactionId":"4EMLSPCn8j","taskId":"2"}]},"screenId":"SC2QqHKtZYR6cP00mjyjRU43JAMym","type":"screen","view":{"x":-264.07407808,"y":-44.73801280000001}},"2":{"action":"webauthn-sign-up-or-in-start","arguments":{"finishStepId":{"type":"inline","value":"2.end"}},"id":"2","name":"Sign Up or In / WebAuthn","next":{"rules":[{"interactionId":"success","taskId":"2.end"}]},"type":"automated","view":{"x":168.68955968000023,"y":-26.16021568000002}},"3":{"conditions":[{"atomicConditions":[{"operator":"is-true","predicate":{"ArgType":"","type":"inline","value":""},"target":{"ArgType":"","type":"context","value":"user.verifiedEmail"}}],"interactionId":"zs5pg3cwhaj","name":"verifiedEmail"},{"interactionId":"ELSE","name":"!verifiedEmail"}],"id":"3","name":"Is the user's email verified?","next":{"rules":[{"interactionId":"zs5pg3cwhaj","taskId":"4"},{"interactionId":"ELSE","taskId":"5"}]},"type":"condition","view":{"x":599.1062085499391,"y":-36.75193241900209}},"4":{"action":"custom-claims","arguments":{"customClaims":{"ArgType":"","type":"inline","value":[{"key":"email","type":"context","value":"user.email"},{"key":"verified_email","type":"boolean","value":true}]}},"id":"4","name":"Add Email and EmailVerified Attributes","next":{"rules":[{"interactionId":"success","taskId":"8"}]},"type":"automated","view":{"x":1551.0807123199993,"y":-122.90609727999995}},"5":{"action":"update-user-otp-email","arguments":{"addToLoginIDs":{"ArgType":"","type":"inline","value":true},"onMergeUseExisting":{"ArgType":"","type":"inline","value":"true"}},"id":"5","name":"Update User / OTP / Email","next":{"rules":[{"interactionId":"success","taskId":"6"}]},"type":"automated","view":{"x":1067.3513043199998,"y":264.0774291199999}},"6":{"id":"6","name":"Verify OTP","next":{"rules":[{"interactionId":"resend","taskId":"5"},{"interactionId":"submit","taskId":"7"}]},"screenId":"SC2QqHKqIewjP0zmAW0pUCSdbXoaN","type":"screen","view":{"x":1487.3513043199998,"y":264.0774291199999}},"7":{"action":"verify-code-otp-email","id":"7","name":"Verify Code / OTP / Email","next":{"rules":[{"interactionId":"success","taskId":"4"}]},"type":"automated","view":{"x":1907.3513043199998,"y":264.0774291199999}},"8":{"action":"logged-in","arguments":{},"id":"8","name":"End","next":{"rules":[]},"type":"automated","view":{"x":1987.950546572639,"y":-164.44173841437947}},"2.end":{"action":"webauthn-sign-up-or-in-finish","arguments":{},"id":"2.end","name":"Sign Up or In / WebAuthn","next":{"rules":[{"interactionId":"success","taskId":"3"}]},"type":"automated","view":{"x":168.68955968000023,"y":-26.16021568000002}},"start":{"id":"start","name":"start","next":{"rules":[{"taskId":"1"}]},"view":{"x":-456.6067199999999,"y":0}}}},"disabled":false}}
--------------------------------------------------------------------------------