From e498b6b46036987835f7aae49bd75e703d2af091 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 11 Aug 2026 15:28:36 +0530 Subject: [PATCH] Enhance SignUpButton component with asChild prop for custom rendering docs: update README files to clarify credential setup for various samples refactor: remove thunderid-config files and environment variables from samples fix: adjust SignInButton usage in Vue samples for asChild prop chore: clean up sample configurations and environment files across all frameworks --- .../src/__tests__/components/actions.test.ts | 54 ++++++++ .../src/components/actions/SignInButton.ts | 46 +++++-- .../src/components/actions/SignUpButton.ts | 45 ++++-- samples/browser/quickstart/README.md | 4 +- .../thunderid-config/thunderid-config.yaml | 128 ------------------ .../quickstart/thunderid-config/thunderid.env | 2 - samples/express/quickstart/README.md | 6 +- .../thunderid-config/thunderid-config.yaml | 122 ----------------- .../quickstart/thunderid-config/thunderid.env | 3 - samples/nextjs/quickstart/.env.example | 17 --- samples/nextjs/quickstart/README.md | 52 +++++-- .../nextjs/quickstart/scripts/prepare-dev.cjs | 49 +++++-- .../thunderid-config/thunderid-config.yaml | 125 ----------------- .../quickstart/thunderid-config/thunderid.env | 4 - samples/node/quickstart/.env.example | 4 +- samples/node/quickstart/README.md | 25 ++-- samples/node/quickstart/lib/ui.mjs | 2 +- samples/nuxt/quickstart/.env.example | 7 - samples/nuxt/quickstart/README.md | 43 ++++-- .../nuxt/quickstart/scripts/prepare-dev.cjs | 39 ++++-- .../thunderid-config/thunderid-config.yaml | 125 ----------------- .../quickstart/thunderid-config/thunderid.env | 4 - samples/react/quickstart/README.md | 4 +- .../thunderid-config/thunderid-config.yaml | 128 ------------------ .../quickstart/thunderid-config/thunderid.env | 2 - samples/vue/quickstart/README.md | 4 +- samples/vue/quickstart/src/components/Nav.vue | 2 +- samples/vue/quickstart/src/pages/HomePage.vue | 2 +- .../thunderid-config/thunderid-config.yaml | 128 ------------------ .../quickstart/thunderid-config/thunderid.env | 2 - 30 files changed, 293 insertions(+), 885 deletions(-) delete mode 100644 samples/browser/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/browser/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/express/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/express/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/nextjs/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/nuxt/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/react/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/react/quickstart/thunderid-config/thunderid.env delete mode 100644 samples/vue/quickstart/thunderid-config/thunderid-config.yaml delete mode 100644 samples/vue/quickstart/thunderid-config/thunderid.env diff --git a/packages/vue/src/__tests__/components/actions.test.ts b/packages/vue/src/__tests__/components/actions.test.ts index 77b11b9f..cd9d6444 100644 --- a/packages/vue/src/__tests__/components/actions.test.ts +++ b/packages/vue/src/__tests__/components/actions.test.ts @@ -194,6 +194,60 @@ describe('SignInButton', () => { expect(signIn).toHaveBeenCalledWith(options); }); }); + + it('should render plain text slot content inside the styled, click-wired button', async () => { + const signIn = vi.fn().mockResolvedValue(undefined); + const mockContext = createMockThunderIDContext({signIn}); + + const wrapper = mount(SignInButton, { + global: { + provide: { + [THUNDERID_KEY as symbol]: mockContext, + }, + }, + slots: { + default: 'Sign In', + }, + }); + + const button = wrapper.find('button'); + expect(button.exists()).toBe(true); + expect(button.text()).toBe('Sign In'); + + await button.trigger('click'); + await vi.waitFor(() => { + expect(signIn).toHaveBeenCalled(); + }); + }); + + it('should bypass the styled button when asChild is set', async () => { + const signIn = vi.fn().mockResolvedValue(undefined); + const mockContext = createMockThunderIDContext({signIn}); + + const wrapper = mount(SignInButton, { + global: { + provide: { + [THUNDERID_KEY as symbol]: mockContext, + }, + }, + props: { + asChild: true, + }, + slots: { + default: ({signIn: doSignIn, isLoading}: {signIn: () => void; isLoading: boolean}) => + h('a', {href: '#', onClick: doSignIn}, isLoading ? 'Signing in…' : 'Sign in'), + }, + }); + + const link = wrapper.find('a'); + expect(link.exists()).toBe(true); + expect(wrapper.find('button').exists()).toBe(false); + + await link.trigger('click'); + await vi.waitFor(() => { + expect(signIn).toHaveBeenCalled(); + }); + }); }); describe('SignOutButton', () => { diff --git a/packages/vue/src/components/actions/SignInButton.ts b/packages/vue/src/components/actions/SignInButton.ts index 775a7e17..d13f2085 100644 --- a/packages/vue/src/components/actions/SignInButton.ts +++ b/packages/vue/src/components/actions/SignInButton.ts @@ -21,14 +21,33 @@ import useThunderID from '../../composables/useThunderID'; * * If a custom `signInUrl` is configured, navigates to it instead. * Falls back to i18n translation for the button text. + * + * By default, slot content is rendered *inside* the styled button (matching + * {@link BaseSignInButton}'s convention) — the click handler and styling are + * wired up for you. + * + * @example + * + * Sign In + * {{ isLoading ? 'Signing in…' : 'Sign in' }} + * + * @example + * + * + * + * */ const SignInButton: Component = defineComponent({ name: 'SignInButton', props: { + asChild: {default: false, type: Boolean}, signInOptions: {default: undefined, type: Object as PropType>}, }, emits: ['click', 'error'], - setup(props: {signInOptions?: Record}, {slots, emit, attrs}: SetupContext): () => VNode { + setup( + props: {asChild: boolean; signInOptions?: Record}, + {slots, emit, attrs}: SetupContext, + ): () => VNode { const {signIn, signInUrl, signInOptions: contextSignInOptions} = useThunderID(); const isLoading: Ref = ref(false); @@ -55,17 +74,28 @@ const SignInButton: Component = defineComponent({ }; return (): VNode => { - if (slots['default']) { + // asChild: caller renders their own element and wires the click themselves. + if (props.asChild && slots['default']) { const nodes: VNode[] = slots['default']({isLoading: isLoading.value, signIn: handleSignIn}); return nodes.length === 1 ? nodes[0] : h(Fragment, null, nodes); } - return h(BaseSignInButton, { - class: attrs.class, - isLoading: isLoading.value, - onClick: handleSignIn, - style: attrs.style, - }); + // Default: forward slot content (or fallback text) into the styled, + // click-wired BaseSignInButton — same convention as BaseSignInButton itself. + const slotContent: (() => VNode[]) | undefined = slots['default'] + ? (): VNode[] => slots['default']!({isLoading: isLoading.value, signIn: handleSignIn}) + : undefined; + + return h( + BaseSignInButton, + { + class: attrs.class, + isLoading: isLoading.value, + onClick: handleSignIn, + style: attrs.style, + }, + slotContent, + ); }; }, }); diff --git a/packages/vue/src/components/actions/SignUpButton.ts b/packages/vue/src/components/actions/SignUpButton.ts index a17165fd..901cb950 100644 --- a/packages/vue/src/components/actions/SignUpButton.ts +++ b/packages/vue/src/components/actions/SignUpButton.ts @@ -11,11 +11,29 @@ import useThunderID from '../../composables/useThunderID'; * * If a custom `signUpUrl` is configured, navigates to it instead. * Falls back to i18n translation for the button text. + * + * By default, slot content is rendered *inside* the styled button (matching + * {@link BaseSignUpButton}'s convention) — the click handler and styling are + * wired up for you. + * + * @example + * + * Sign Up + * {{ isLoading ? 'Signing up…' : 'Sign up' }} + * + * @example + * + * + * + * */ const SignUpButton: Component = defineComponent({ name: 'SignUpButton', + props: { + asChild: {default: false, type: Boolean}, + }, emits: ['click', 'error'], - setup(_: {}, {slots, emit, attrs}: SetupContext): () => VNode { + setup(props: {asChild: boolean}, {slots, emit, attrs}: SetupContext): () => VNode { const {signUp, signUpUrl} = useThunderID(); const isLoading: Ref = ref(false); @@ -42,17 +60,28 @@ const SignUpButton: Component = defineComponent({ }; return (): VNode => { - if (slots['default']) { + // asChild: caller renders their own element and wires the click themselves. + if (props.asChild && slots['default']) { const nodes: VNode[] = slots['default']({isLoading: isLoading.value, signUp: handleSignUp}); return nodes.length === 1 ? nodes[0] : h(Fragment, null, nodes); } - return h(BaseSignUpButton, { - class: attrs.class, - isLoading: isLoading.value, - onClick: handleSignUp, - style: attrs.style, - }); + // Default: forward slot content (or fallback text) into the styled, + // click-wired BaseSignUpButton — same convention as BaseSignUpButton itself. + const slotContent: (() => VNode[]) | undefined = slots['default'] + ? (): VNode[] => slots['default']!({isLoading: isLoading.value, signUp: handleSignUp}) + : undefined; + + return h( + BaseSignUpButton, + { + class: attrs.class, + isLoading: isLoading.value, + onClick: handleSignUp, + style: attrs.style, + }, + slotContent, + ); }; }, }); diff --git a/samples/browser/quickstart/README.md b/samples/browser/quickstart/README.md index 9ba4c11f..78ad0dd9 100644 --- a/samples/browser/quickstart/README.md +++ b/samples/browser/quickstart/README.md @@ -17,9 +17,9 @@ A minimal Vite + vanilla JS app demonstrating sign-in and sign-out with the Thun cp .env.example .env ``` -2. Edit `.env` with the credentials you set in `thunderid-config/thunderid.env`: +2. Edit `.env` with your application's credentials: ``` - VITE_THUNDERID_CLIENT_ID=BROWSER_QUICKSTART + VITE_THUNDERID_CLIENT_ID= VITE_THUNDERID_BASE_URL=https://localhost:8090 ``` diff --git a/samples/browser/quickstart/thunderid-config/thunderid-config.yaml b/samples/browser/quickstart/thunderid-config/thunderid-config.yaml deleted file mode 100644 index 99377cc7..00000000 --- a/samples/browser/quickstart/thunderid-config/thunderid-config.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# resource_type: user_type -id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f -category: user -name: Customer -ouHandle: default -allowSelfRegistration: true -systemAttributes: - display: username -schema: { - "username": { - "type": "string", - "displayName": "Username", - "required": true, - "unique": true - }, - "password": { - "type": "string", - "displayName": "Password", - "required": false, - "credential": true - }, - "email": { - "type": "string", - "displayName": "Email", - "required": true, - "unique": true, - "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - }, - "given_name": { - "type": "string", - "displayName": "First Name", - "required": false - }, - "family_name": { - "type": "string", - "displayName": "Last Name", - "required": false - }, - "name": { - "type": "string", - "displayName": "Full Name", - "required": false - }, - "mobile_number": { - "type": "string", - "displayName": "Mobile Number", - "required": false - } - } - ---- -# resource_type: application -id: 2803b8eb-d167-4c69-bc7f-dad6b4d31555 -ouHandle: default -name: browser-quickstart -description: Sample Vite + vanilla JS application using the @thunderid/browser SDK -url: http://localhost:5173 -logoUrl: emoji:🌐 -authFlowHandle: default-basic-flow -isRegistrationFlowEnabled: true -isRecoveryFlowEnabled: false -assertion: - validityPeriod: 3600 -allowedUserTypes: - - Customer -inboundAuthConfig: - - type: oauth2 - config: - clientId: {{.BROWSER_QUICKSTART_CLIENT_ID}} - redirectUris: - {{- range .BROWSER_QUICKSTART_REDIRECT_URIS}} - - {{.}} - {{- end}} - grantTypes: - - authorization_code - responseTypes: - - code - tokenEndpointAuthMethod: none - pkceRequired: true - publicClient: true - requirePushedAuthorizationRequests: false - token: - accessToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - idToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - responseType: JWT - userInfo: - responseType: JSON - userAttributes: - - given_name - - family_name - - email - - groups - - name - scopeClaims: - email: - - email - - email_verified - group: - - groups - phone: - - phone_number - - phone_number_verified - profile: - - name - - given_name - - family_name - - picture - ---- -# resource_type: server_config -name: cors -value: - allowedOrigins: - - "http://localhost:5173" diff --git a/samples/browser/quickstart/thunderid-config/thunderid.env b/samples/browser/quickstart/thunderid-config/thunderid.env deleted file mode 100644 index c2dfd7fd..00000000 --- a/samples/browser/quickstart/thunderid-config/thunderid.env +++ /dev/null @@ -1,2 +0,0 @@ -BROWSER_QUICKSTART_CLIENT_ID=BROWSER_QUICKSTART -BROWSER_QUICKSTART_REDIRECT_URIS=["http://localhost:5173"] diff --git a/samples/express/quickstart/README.md b/samples/express/quickstart/README.md index 1eed6b8c..df8b1975 100644 --- a/samples/express/quickstart/README.md +++ b/samples/express/quickstart/README.md @@ -25,10 +25,10 @@ Protected routes validate the `Authorization: Bearer ` header against Thu cp .env.example .env ``` -2. Edit `.env` with the credentials you set in `thunderid-config/thunderid.env`: +2. Edit `.env` with your application's credentials: ``` - THUNDERID_CLIENT_ID=EXPRESS_QUICKSTART - THUNDERID_CLIENT_SECRET= + THUNDERID_CLIENT_ID= + THUNDERID_CLIENT_SECRET= THUNDERID_BASE_URL=https://localhost:8090 ``` diff --git a/samples/express/quickstart/thunderid-config/thunderid-config.yaml b/samples/express/quickstart/thunderid-config/thunderid-config.yaml deleted file mode 100644 index a0c35c8e..00000000 --- a/samples/express/quickstart/thunderid-config/thunderid-config.yaml +++ /dev/null @@ -1,122 +0,0 @@ -# resource_type: user_type -id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f -category: user -name: Customer -ouHandle: default -allowSelfRegistration: true -systemAttributes: - display: username -schema: { - "username": { - "type": "string", - "displayName": "Username", - "required": true, - "unique": true - }, - "password": { - "type": "string", - "displayName": "Password", - "required": false, - "credential": true - }, - "email": { - "type": "string", - "displayName": "Email", - "required": true, - "unique": true, - "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - }, - "given_name": { - "type": "string", - "displayName": "First Name", - "required": false - }, - "family_name": { - "type": "string", - "displayName": "Last Name", - "required": false - }, - "name": { - "type": "string", - "displayName": "Full Name", - "required": false - }, - "mobile_number": { - "type": "string", - "displayName": "Mobile Number", - "required": false - } - } - ---- -# resource_type: application -id: eb2214bd-5889-49cb-b9a7-d063cee76fb8 -ouHandle: default -name: express-quickstart -description: Sample Express.js application using the @thunderid/express SDK -url: http://localhost:3000 -logoUrl: emoji:🚂 -authFlowHandle: default-basic-flow -isRegistrationFlowEnabled: true -isRecoveryFlowEnabled: false -assertion: - validityPeriod: 3600 -allowedUserTypes: - - Customer -inboundAuthConfig: - - type: oauth2 - config: - clientId: {{.EXPRESS_QUICKSTART_CLIENT_ID}} - clientSecret: {{.EXPRESS_QUICKSTART_CLIENT_SECRET}} - redirectUris: - {{- range .EXPRESS_QUICKSTART_REDIRECT_URIS}} - - {{.}} - {{- end}} - grantTypes: - - authorization_code - responseTypes: - - code - tokenEndpointAuthMethod: client_secret_basic - pkceRequired: false - publicClient: false - requirePushedAuthorizationRequests: false - token: - accessToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - idToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - responseType: JWT - userInfo: - responseType: JSON - userAttributes: - - given_name - - family_name - - email - - groups - - name - scopeClaims: - email: - - email - - email_verified - group: - - groups - phone: - - phone_number - - phone_number_verified - profile: - - name - - given_name - - family_name - - picture diff --git a/samples/express/quickstart/thunderid-config/thunderid.env b/samples/express/quickstart/thunderid-config/thunderid.env deleted file mode 100644 index a8e3c920..00000000 --- a/samples/express/quickstart/thunderid-config/thunderid.env +++ /dev/null @@ -1,3 +0,0 @@ -EXPRESS_QUICKSTART_CLIENT_ID=EXPRESS_QUICKSTART -EXPRESS_QUICKSTART_CLIENT_SECRET=change-me-express-quickstart-secret -EXPRESS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/login"] diff --git a/samples/nextjs/quickstart/.env.example b/samples/nextjs/quickstart/.env.example index 005b735d..f30ce6ad 100644 --- a/samples/nextjs/quickstart/.env.example +++ b/samples/nextjs/quickstart/.env.example @@ -4,31 +4,14 @@ NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 # Flow Secret for this app. Sent in the `Flow-Secret` header to authenticate this app when the # native (embedded) flow starts. Shown once when the application is created; if lost, regenerate it # from the app's Credentials tab (under Edit). Server-only, never expose to the browser. -# -# This is NOT the OAuth2 Client Secret — they are separate credentials issued independently. An -# embedded app (no OAuth2 configuration) has a Flow Secret and no client secret; a redirect-based -# authorization_code app has a client secret and no Flow Secret. THUNDERID_FLOW_SECRET=your-flow-secret-here # Secret used to encrypt this app's session cookie. Generate locally, not from the console. THUNDERID_SECRET=generate-with-openssl-rand-base64-32 -# ── Native flow (default) ─────────────────────────────────────────────── -# Sign-in/sign-up render inline on this app's own routes below, with no -# redirect to ThunderID's hosted pages. Requires the three vars below. - # Application ID (spId) for this app. ThunderID Console -> your application -> Overview. NEXT_PUBLIC_THUNDERID_APPLICATION_ID=your-application-id-here # Local app route that renders the sign-in page. Not from the console. NEXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin # Local app route that renders the sign-up page. Not from the console. NEXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup - -# ── Redirect-based flow (opt-in) ──────────────────────────────────────── -# Uncomment to send the user to ThunderID's hosted sign-in page instead of -# the native flow above. Requires registering a redirect URI (see the app's -# config notice for the exact value), and replaces the three native-flow -# vars above entirely. The redirect-based flow uses the OAuth2 Client Secret -# instead of the Flow Secret above. -# NEXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here -# THUNDERID_CLIENT_SECRET=your-client-secret-here diff --git a/samples/nextjs/quickstart/README.md b/samples/nextjs/quickstart/README.md index bc6cbefc..2a77c86a 100644 --- a/samples/nextjs/quickstart/README.md +++ b/samples/nextjs/quickstart/README.md @@ -18,25 +18,22 @@ A minimal Next.js 15 App Router application demonstrating ThunderID authenticati cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`. - By default the file is set up for the native flow: +2. Fill in your ThunderID credentials in `.env`. By default the file is set up for the native flow: ```dotenv NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NEXT_PUBLIC_THUNDERID_APPLICATION_ID= + NEXT_PUBLIC_THUNDERID_APPLICATION_ID= + THUNDERID_FLOW_SECRET= NEXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin NEXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup - THUNDERID_CLIENT_SECRET= THUNDERID_SECRET= ``` - To use the redirect-based flow instead, comment out the three native-flow vars above (leaving - `NEXT_PUBLIC_THUNDERID_BASE_URL`, `THUNDERID_CLIENT_SECRET`, and `THUNDERID_SECRET` enabled) and uncomment - `NEXT_PUBLIC_THUNDERID_CLIENT_ID` — or regenerate `.env` for that flow directly: - - ```bash - npm run prepare-dev:redirect - ``` + The Flow Secret is generated by the server when the application is created, shown once, and only + retrievable afterward from the app's Credentials tab (under Edit). It's a separate credential from + the OAuth2 Client Secret used by the redirect-based flow below — an embedded app (no OAuth2 + configuration) has a Flow Secret and no client secret; a redirect-based `authorization_code` app has + a client secret and no Flow Secret. 3. Start the development server: @@ -45,3 +42,36 @@ A minimal Next.js 15 App Router application demonstrating ThunderID authenticati ``` The app is now running at [http://localhost:3000](http://localhost:3000). + +
+

Redirect-based flow

+ +By default this quickstart uses the native (embedded) flow, where sign-in/sign-up render inline on this +app's own `/signin` and `/signup` routes with no redirect to ThunderID's hosted pages. + +To send the user to ThunderID's hosted sign-in page instead, switch to the redirect-based flow: + +1. Register a redirect URI for your application (see the app's config notice in the console for the + exact value to use). +2. Regenerate `.env` for the redirect flow: + + ```bash + npm run prepare-dev:redirect + ``` + + This comments out the native-flow vars (`NEXT_PUBLIC_THUNDERID_APPLICATION_ID`, + `NEXT_PUBLIC_THUNDERID_SIGN_IN_URL`, `NEXT_PUBLIC_THUNDERID_SIGN_UP_URL`, `THUNDERID_FLOW_SECRET`) and + adds: + + ```dotenv + NEXT_PUBLIC_THUNDERID_CLIENT_ID= + THUNDERID_CLIENT_SECRET= + ``` + + Both values come from the application's Credentials tab in the console. To switch back to the native + flow, run `node scripts/prepare-dev.cjs --flow=native` (or manually re-enable the native-flow vars and + comment out the two above). +3. Fill in `NEXT_PUBLIC_THUNDERID_CLIENT_ID` and `THUNDERID_CLIENT_SECRET` in `.env`, then restart the dev + server. + +
diff --git a/samples/nextjs/quickstart/scripts/prepare-dev.cjs b/samples/nextjs/quickstart/scripts/prepare-dev.cjs index 7d638c94..9811ec0d 100644 --- a/samples/nextjs/quickstart/scripts/prepare-dev.cjs +++ b/samples/nextjs/quickstart/scripts/prepare-dev.cjs @@ -7,8 +7,17 @@ const path = require('node:path'); const root = path.join(__dirname, '..'); const PREFIX = 'NEXT_PUBLIC_THUNDERID_'; -const NATIVE_FLOW_VARS = [`${PREFIX}APPLICATION_ID`, `${PREFIX}SIGN_IN_URL`, `${PREFIX}SIGN_UP_URL`]; -const REDIRECT_FLOW_VARS = [`${PREFIX}CLIENT_ID`]; +const NATIVE_FLOW_VARS = [ + `${PREFIX}APPLICATION_ID`, + `${PREFIX}SIGN_IN_URL`, + `${PREFIX}SIGN_UP_URL`, + 'THUNDERID_FLOW_SECRET', +]; +const REDIRECT_FLOW_VARS = [`${PREFIX}CLIENT_ID`, 'THUNDERID_CLIENT_SECRET']; +const REDIRECT_FLOW_PLACEHOLDERS = { + [`${PREFIX}CLIENT_ID`]: 'your-client-id-here', + THUNDERID_CLIENT_SECRET: 'your-client-secret-here', +}; const flowArg = process.argv.find((arg) => arg.startsWith('--flow=')); const flowExplicitlyRequested = Boolean(flowArg); @@ -19,23 +28,37 @@ if (flowExplicitlyRequested && flow !== 'native' && flow !== 'redirect') { process.exit(1); } -/** Toggles the leading `# ` on env var lines to match the selected flow. */ +/** + * Toggles the leading `# ` on env var lines to match the selected flow, appending + * any vars for the selected flow that aren't present in the source file yet (the + * redirect-flow vars aren't checked into `.env.example`, so switching to `redirect` + * from a fresh copy needs to add them rather than just uncomment them). + */ function applyFlow(envContent, selectedFlow) { const varsToEnable = selectedFlow === 'redirect' ? REDIRECT_FLOW_VARS : NATIVE_FLOW_VARS; const varsToDisable = selectedFlow === 'redirect' ? NATIVE_FLOW_VARS : REDIRECT_FLOW_VARS; + const found = new Set(); - return envContent - .split('\n') - .map((line) => { - const enable = varsToEnable.find((key) => line.replace(/^#\s*/, '').startsWith(`${key}=`)); - if (enable) return line.replace(/^#\s*/, ''); + const lines = envContent.split('\n').map((line) => { + const enable = varsToEnable.find((key) => line.replace(/^#\s*/, '').startsWith(`${key}=`)); + if (enable) { + found.add(enable); + return line.replace(/^#\s*/, ''); + } + + const disable = varsToDisable.find((key) => line.startsWith(`${key}=`)); + if (disable) return `# ${line}`; - const disable = varsToDisable.find((key) => line.startsWith(`${key}=`)); - if (disable) return `# ${line}`; + return line; + }); + + const missing = varsToEnable.filter((key) => !found.has(key)); + if (missing.length > 0) { + lines.push('', `# ── ${selectedFlow} flow ─────────────────────────────────────────────────`); + for (const key of missing) lines.push(`${key}=${REDIRECT_FLOW_PLACEHOLDERS[key] ?? ''}`); + } - return line; - }) - .join('\n'); + return lines.join('\n'); } const envExample = path.join(root, '.env.example'); diff --git a/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml b/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml deleted file mode 100644 index ceeb0385..00000000 --- a/samples/nextjs/quickstart/thunderid-config/thunderid-config.yaml +++ /dev/null @@ -1,125 +0,0 @@ -# resource_type: user_type -id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f -category: user -name: Customer -ouHandle: default -allowSelfRegistration: true -systemAttributes: - display: username -schema: { - "username": { - "type": "string", - "displayName": "Username", - "required": true, - "unique": true - }, - "password": { - "type": "string", - "displayName": "Password", - "required": false, - "credential": true - }, - "email": { - "type": "string", - "displayName": "Email", - "required": true, - "unique": true, - "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - }, - "given_name": { - "type": "string", - "displayName": "First Name", - "required": false - }, - "family_name": { - "type": "string", - "displayName": "Last Name", - "required": false - }, - "name": { - "type": "string", - "displayName": "Full Name", - "required": false - }, - "mobile_number": { - "type": "string", - "displayName": "Mobile Number", - "required": false - } - } - ---- -# resource_type: application -# id is pinned (not left to auto-assignment) because the Next.js SDK's -# embedded sign-in flow needs a fixed applicationId matching -# NEXT_PUBLIC_THUNDERID_APPLICATION_ID. -id: {{.NEXTJS_QUICKSTART_APPLICATION_ID}} -ouHandle: default -name: nextjs-quickstart -description: Sample Next.js App Router application using the @thunderid/nextjs SDK -url: http://localhost:3000 -logoUrl: emoji:▲ -authFlowHandle: default-basic-flow -isRegistrationFlowEnabled: true -isRecoveryFlowEnabled: false -assertion: - validityPeriod: 3600 -allowedUserTypes: - - Customer -inboundAuthConfig: - - type: oauth2 - config: - clientId: {{.NEXTJS_QUICKSTART_CLIENT_ID}} - clientSecret: {{.NEXTJS_QUICKSTART_CLIENT_SECRET}} - redirectUris: - {{- range .NEXTJS_QUICKSTART_REDIRECT_URIS}} - - {{.}} - {{- end}} - grantTypes: - - authorization_code - responseTypes: - - code - tokenEndpointAuthMethod: client_secret_basic - pkceRequired: true - publicClient: false - requirePushedAuthorizationRequests: false - token: - accessToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - idToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - responseType: JWT - userInfo: - responseType: JSON - userAttributes: - - given_name - - family_name - - email - - groups - - name - scopeClaims: - email: - - email - - email_verified - group: - - groups - phone: - - phone_number - - phone_number_verified - profile: - - name - - given_name - - family_name - - picture diff --git a/samples/nextjs/quickstart/thunderid-config/thunderid.env b/samples/nextjs/quickstart/thunderid-config/thunderid.env deleted file mode 100644 index ac46a543..00000000 --- a/samples/nextjs/quickstart/thunderid-config/thunderid.env +++ /dev/null @@ -1,4 +0,0 @@ -NEXTJS_QUICKSTART_APPLICATION_ID=d1aa147c-9b2a-4a75-b97e-43656234ea8e -NEXTJS_QUICKSTART_CLIENT_ID=NEXTJS_QUICKSTART -NEXTJS_QUICKSTART_CLIENT_SECRET=change-me-nextjs-quickstart-secret -NEXTJS_QUICKSTART_REDIRECT_URIS=["http://localhost:3000"] diff --git a/samples/node/quickstart/.env.example b/samples/node/quickstart/.env.example index ac2c7149..a45eda11 100644 --- a/samples/node/quickstart/.env.example +++ b/samples/node/quickstart/.env.example @@ -2,10 +2,10 @@ THUNDERID_BASE_URL=https://localhost:8090 # OAuth2 Client ID for this service. ThunderID Console -> your application -> Overview. -THUNDERID_CLIENT_ID=your-agent-client-id +THUNDERID_CLIENT_ID=your-client-id # OAuth2 Client Secret for this service. Shown once when the application is created; # if lost, regenerate it from the app's Credentials tab (under Edit). -THUNDERID_CLIENT_SECRET=your-agent-client-secret +THUNDERID_CLIENT_SECRET=your-client-secret # Optional: space-separated scopes to request for this service. THUNDERID_SCOPE= diff --git a/samples/node/quickstart/README.md b/samples/node/quickstart/README.md index 6f4c90a5..1aeb7ee0 100644 --- a/samples/node/quickstart/README.md +++ b/samples/node/quickstart/README.md @@ -38,27 +38,28 @@ node/quickstart/ - Node.js 18+ - A running ThunderID instance (default: `https://localhost:8090`) -- An **agent** registered in ThunderID with the `client_credentials` grant +- A **Backend Service** application registered in ThunderID -## Create an agent +## Create an application -Agents are ThunderID's machine identities, distinct from user-facing applications. - -1. Open the ThunderID Console (`https://localhost:8090/console`) and go to **Agents**. -2. Create a new agent and give it a name (e.g. `node-service-quickstart`). -3. Under its OAuth 2.0 settings, enable the `client_credentials` grant type and set the token - endpoint auth method to `client_secret_basic`. -4. Copy the generated client ID and client secret, you'll need them below. +1. Open the ThunderID Console (`https://localhost:8090/console`), navigate to **Applications**, and + click **Add Application**. +2. From the **Choose a type** page, select **Backend Service**. +3. Enter a name (e.g. `node-service-quickstart`) and create the application. Backend Service apps + are pre-configured for the `client_credentials` grant with `client_secret_basic` authentication, + no extra setup needed. +4. Copy the **Client ID** from the **General** tab, and the **Client Secret** from the window that + pops up when the application is created (shown only once), you'll need them below. ## Getting started -1. Copy the environment file and fill in your agent's credentials: +1. Copy the environment file and fill in your application's credentials: ```sh cp .env.example .env ``` ``` - THUNDERID_CLIENT_ID= - THUNDERID_CLIENT_SECRET= + THUNDERID_CLIENT_ID= + THUNDERID_CLIENT_SECRET= THUNDERID_BASE_URL=https://localhost:8090 ``` diff --git a/samples/node/quickstart/lib/ui.mjs b/samples/node/quickstart/lib/ui.mjs index 73131851..f234454d 100644 --- a/samples/node/quickstart/lib/ui.mjs +++ b/samples/node/quickstart/lib/ui.mjs @@ -53,7 +53,7 @@ export function printConfigNeeded(missingEnvVars) { pc.cyan('.env.example') + pc.dim(' to ') + pc.cyan('.env') + - pc.dim(', fill in the client credentials for your agent, then run ') + + pc.dim(', fill in the client credentials for your application, then run ') + pc.cyan('npm start') + pc.dim(' again.'), ), diff --git a/samples/nuxt/quickstart/.env.example b/samples/nuxt/quickstart/.env.example index f38a92a9..73be597c 100644 --- a/samples/nuxt/quickstart/.env.example +++ b/samples/nuxt/quickstart/.env.example @@ -19,12 +19,5 @@ NUXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin # Local app route that renders the sign-up page. Not from the console. NUXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup -# ── Redirect-based flow (opt-in) ──────────────────────────────────────── -# Uncomment to send the user to ThunderID's hosted sign-in page instead of -# the native flow above. Requires registering a redirect URI (see the app's -# config notice for the exact value), and replaces the three native-flow -# vars above entirely. -# NUXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here - # DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production. NODE_TLS_REJECT_UNAUTHORIZED=0 diff --git a/samples/nuxt/quickstart/README.md b/samples/nuxt/quickstart/README.md index ab0c89de..530917e5 100644 --- a/samples/nuxt/quickstart/README.md +++ b/samples/nuxt/quickstart/README.md @@ -18,30 +18,51 @@ A minimal Nuxt 3 application demonstrating ThunderID authentication with OAuth 2 cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`. - By default the file is set up for the native flow: +2. Fill in your ThunderID credentials in `.env`. By default the file is set up for the native flow: ```dotenv NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NUXT_PUBLIC_THUNDERID_APPLICATION_ID= + NUXT_PUBLIC_THUNDERID_APPLICATION_ID= NUXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin NUXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup - THUNDERID_CLIENT_SECRET= + THUNDERID_CLIENT_SECRET= THUNDERID_SESSION_SECRET= ``` - To use the redirect-based flow instead, comment out the three native-flow vars above (leaving - `NUXT_PUBLIC_THUNDERID_BASE_URL`, `THUNDERID_CLIENT_SECRET`, and `THUNDERID_SESSION_SECRET` enabled) and - uncomment `NUXT_PUBLIC_THUNDERID_CLIENT_ID` — or regenerate `.env` for that flow directly: +3. Start the development server: ```bash - npm run prepare-dev:redirect + pnpm dev ``` -3. Start the development server: + The app is now running at [http://localhost:3000](http://localhost:3000). + +
+

Redirect-based flow

+ +By default this quickstart uses the native (embedded) flow, where sign-in/sign-up render inline on this +app's own `/signin` and `/signup` routes with no redirect to ThunderID's hosted pages. + +To send the user to ThunderID's hosted sign-in page instead, switch to the redirect-based flow: + +1. Register a redirect URI for your application (see the app's config notice in the console for the + exact value to use). +2. Regenerate `.env` for the redirect flow: ```bash - pnpm dev + npm run prepare-dev:redirect ``` - The app is now running at [http://localhost:3000](http://localhost:3000). + This comments out the native-flow vars (`NUXT_PUBLIC_THUNDERID_APPLICATION_ID`, + `NUXT_PUBLIC_THUNDERID_SIGN_IN_URL`, `NUXT_PUBLIC_THUNDERID_SIGN_UP_URL`) and adds: + + ```dotenv + NUXT_PUBLIC_THUNDERID_CLIENT_ID= + ``` + + `THUNDERID_CLIENT_SECRET` is already set from step 2 above and is reused as-is by both flows. To + switch back to the native flow, run `node scripts/prepare-dev.cjs --flow=native` (or manually + re-enable the native-flow vars and comment out the one above). +3. Fill in `NUXT_PUBLIC_THUNDERID_CLIENT_ID` in `.env`, then restart the dev server. + +
diff --git a/samples/nuxt/quickstart/scripts/prepare-dev.cjs b/samples/nuxt/quickstart/scripts/prepare-dev.cjs index 99dad6a2..53a9e762 100644 --- a/samples/nuxt/quickstart/scripts/prepare-dev.cjs +++ b/samples/nuxt/quickstart/scripts/prepare-dev.cjs @@ -9,6 +9,9 @@ const root = path.join(__dirname, '..'); const PREFIX = 'NUXT_PUBLIC_THUNDERID_'; const NATIVE_FLOW_VARS = [`${PREFIX}APPLICATION_ID`, `${PREFIX}SIGN_IN_URL`, `${PREFIX}SIGN_UP_URL`]; const REDIRECT_FLOW_VARS = [`${PREFIX}CLIENT_ID`]; +const REDIRECT_FLOW_PLACEHOLDERS = { + [`${PREFIX}CLIENT_ID`]: 'your-client-id-here', +}; const flowArg = process.argv.find((arg) => arg.startsWith('--flow=')); const flowExplicitlyRequested = Boolean(flowArg); @@ -19,23 +22,37 @@ if (flowExplicitlyRequested && flow !== 'native' && flow !== 'redirect') { process.exit(1); } -/** Toggles the leading `# ` on env var lines to match the selected flow. */ +/** + * Toggles the leading `# ` on env var lines to match the selected flow, appending + * any vars for the selected flow that aren't present in the source file yet (the + * redirect-flow vars aren't checked into `.env.example`, so switching to `redirect` + * from a fresh copy needs to add them rather than just uncomment them). + */ function applyFlow(envContent, selectedFlow) { const varsToEnable = selectedFlow === 'redirect' ? REDIRECT_FLOW_VARS : NATIVE_FLOW_VARS; const varsToDisable = selectedFlow === 'redirect' ? NATIVE_FLOW_VARS : REDIRECT_FLOW_VARS; + const found = new Set(); - return envContent - .split('\n') - .map((line) => { - const enable = varsToEnable.find((key) => line.replace(/^#\s*/, '').startsWith(`${key}=`)); - if (enable) return line.replace(/^#\s*/, ''); + const lines = envContent.split('\n').map((line) => { + const enable = varsToEnable.find((key) => line.replace(/^#\s*/, '').startsWith(`${key}=`)); + if (enable) { + found.add(enable); + return line.replace(/^#\s*/, ''); + } + + const disable = varsToDisable.find((key) => line.startsWith(`${key}=`)); + if (disable) return `# ${line}`; - const disable = varsToDisable.find((key) => line.startsWith(`${key}=`)); - if (disable) return `# ${line}`; + return line; + }); + + const missing = varsToEnable.filter((key) => !found.has(key)); + if (missing.length > 0) { + lines.push('', `# ── ${selectedFlow} flow ─────────────────────────────────────────────────`); + for (const key of missing) lines.push(`${key}=${REDIRECT_FLOW_PLACEHOLDERS[key] ?? ''}`); + } - return line; - }) - .join('\n'); + return lines.join('\n'); } const envExample = path.join(root, '.env.example'); diff --git a/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml b/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml deleted file mode 100644 index cb878a7a..00000000 --- a/samples/nuxt/quickstart/thunderid-config/thunderid-config.yaml +++ /dev/null @@ -1,125 +0,0 @@ -# resource_type: user_type -id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f -category: user -name: Customer -ouHandle: default -allowSelfRegistration: true -systemAttributes: - display: username -schema: { - "username": { - "type": "string", - "displayName": "Username", - "required": true, - "unique": true - }, - "password": { - "type": "string", - "displayName": "Password", - "required": false, - "credential": true - }, - "email": { - "type": "string", - "displayName": "Email", - "required": true, - "unique": true, - "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - }, - "given_name": { - "type": "string", - "displayName": "First Name", - "required": false - }, - "family_name": { - "type": "string", - "displayName": "Last Name", - "required": false - }, - "name": { - "type": "string", - "displayName": "Full Name", - "required": false - }, - "mobile_number": { - "type": "string", - "displayName": "Mobile Number", - "required": false - } - } - ---- -# resource_type: application -# id is pinned (not left to auto-assignment) because the Nuxt SDK's -# embedded sign-in flow needs a fixed applicationId matching -# NUXT_PUBLIC_THUNDERID_APPLICATION_ID. -id: {{.NUXT_QUICKSTART_APPLICATION_ID}} -ouHandle: default -name: nuxt-quickstart -description: Sample Nuxt 3 application using the @thunderid/vue SDK -url: http://localhost:3000 -logoUrl: emoji:💚 -authFlowHandle: default-basic-flow -isRegistrationFlowEnabled: true -isRecoveryFlowEnabled: false -assertion: - validityPeriod: 3600 -allowedUserTypes: - - Customer -inboundAuthConfig: - - type: oauth2 - config: - clientId: {{.NUXT_QUICKSTART_CLIENT_ID}} - clientSecret: {{.NUXT_QUICKSTART_CLIENT_SECRET}} - redirectUris: - {{- range .NUXT_QUICKSTART_REDIRECT_URIS}} - - {{.}} - {{- end}} - grantTypes: - - authorization_code - responseTypes: - - code - tokenEndpointAuthMethod: client_secret_basic - pkceRequired: true - publicClient: false - requirePushedAuthorizationRequests: false - token: - accessToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - idToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - responseType: JWT - userInfo: - responseType: JSON - userAttributes: - - given_name - - family_name - - email - - groups - - name - scopeClaims: - email: - - email - - email_verified - group: - - groups - phone: - - phone_number - - phone_number_verified - profile: - - name - - given_name - - family_name - - picture diff --git a/samples/nuxt/quickstart/thunderid-config/thunderid.env b/samples/nuxt/quickstart/thunderid-config/thunderid.env deleted file mode 100644 index 5332fc61..00000000 --- a/samples/nuxt/quickstart/thunderid-config/thunderid.env +++ /dev/null @@ -1,4 +0,0 @@ -NUXT_QUICKSTART_APPLICATION_ID=26ab2782-20dd-4d8d-ab65-123944cf3db7 -NUXT_QUICKSTART_CLIENT_ID=NUXT_QUICKSTART -NUXT_QUICKSTART_CLIENT_SECRET=change-me-nuxt-quickstart-secret -NUXT_QUICKSTART_REDIRECT_URIS=["http://localhost:3000/api/auth/callback"] diff --git a/samples/react/quickstart/README.md b/samples/react/quickstart/README.md index 78c807e9..47e77e45 100644 --- a/samples/react/quickstart/README.md +++ b/samples/react/quickstart/README.md @@ -17,9 +17,9 @@ A minimal React + Vite application demonstrating ThunderID authentication with O cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`: +2. Fill in your ThunderID credentials in `.env`: ``` - VITE_THUNDERID_CLIENT_ID=REACT_QUICKSTART + VITE_THUNDERID_CLIENT_ID= VITE_THUNDERID_BASE_URL=https://your-thunderid-instance ``` diff --git a/samples/react/quickstart/thunderid-config/thunderid-config.yaml b/samples/react/quickstart/thunderid-config/thunderid-config.yaml deleted file mode 100644 index 9615095f..00000000 --- a/samples/react/quickstart/thunderid-config/thunderid-config.yaml +++ /dev/null @@ -1,128 +0,0 @@ -# resource_type: user_type -id: 019e3a5c-04ea-7d18-8ae6-f828b1f3d60f -category: user -name: Customer -ouHandle: default -allowSelfRegistration: true -systemAttributes: - display: username -schema: { - "username": { - "type": "string", - "displayName": "Username", - "required": true, - "unique": true - }, - "password": { - "type": "string", - "displayName": "Password", - "required": false, - "credential": true - }, - "email": { - "type": "string", - "displayName": "Email", - "required": true, - "unique": true, - "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" - }, - "given_name": { - "type": "string", - "displayName": "First Name", - "required": false - }, - "family_name": { - "type": "string", - "displayName": "Last Name", - "required": false - }, - "name": { - "type": "string", - "displayName": "Full Name", - "required": false - }, - "mobile_number": { - "type": "string", - "displayName": "Mobile Number", - "required": false - } - } - ---- -# resource_type: application -id: 2971eb55-7673-4bd0-9ef8-2ed8b53e388f -ouHandle: default -name: react-quickstart -description: Sample React + Vite application using the @thunderid/react SDK -url: http://localhost:5173 -logoUrl: emoji:⚛️ -authFlowHandle: default-basic-flow -isRegistrationFlowEnabled: true -isRecoveryFlowEnabled: false -assertion: - validityPeriod: 3600 -allowedUserTypes: - - Customer -inboundAuthConfig: - - type: oauth2 - config: - clientId: {{.REACT_QUICKSTART_CLIENT_ID}} - redirectUris: - {{- range .REACT_QUICKSTART_REDIRECT_URIS}} - - {{.}} - {{- end}} - grantTypes: - - authorization_code - responseTypes: - - code - tokenEndpointAuthMethod: none - pkceRequired: true - publicClient: true - requirePushedAuthorizationRequests: false - token: - accessToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - idToken: - validityPeriod: 3600 - userAttributes: - - given_name - - family_name - - email - - groups - - name - responseType: JWT - userInfo: - responseType: JSON - userAttributes: - - given_name - - family_name - - email - - groups - - name - scopeClaims: - email: - - email - - email_verified - group: - - groups - phone: - - phone_number - - phone_number_verified - profile: - - name - - given_name - - family_name - - picture - ---- -# resource_type: server_config -name: cors -value: - allowedOrigins: - - "http://localhost:5173" diff --git a/samples/react/quickstart/thunderid-config/thunderid.env b/samples/react/quickstart/thunderid-config/thunderid.env deleted file mode 100644 index adc71478..00000000 --- a/samples/react/quickstart/thunderid-config/thunderid.env +++ /dev/null @@ -1,2 +0,0 @@ -REACT_QUICKSTART_CLIENT_ID=REACT_QUICKSTART -REACT_QUICKSTART_REDIRECT_URIS=["http://localhost:5173"] diff --git a/samples/vue/quickstart/README.md b/samples/vue/quickstart/README.md index 1d292f41..a43b42ea 100644 --- a/samples/vue/quickstart/README.md +++ b/samples/vue/quickstart/README.md @@ -17,9 +17,9 @@ A minimal Vue 3 + Vite application demonstrating ThunderID authentication with O cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env`, using the values you set in `thunderid-config/thunderid.env`: +2. Fill in your ThunderID credentials in `.env`: ``` - VITE_THUNDERID_CLIENT_ID=VUE_QUICKSTART + VITE_THUNDERID_CLIENT_ID= VITE_THUNDERID_BASE_URL=https://your-thunderid-instance ``` diff --git a/samples/vue/quickstart/src/components/Nav.vue b/samples/vue/quickstart/src/components/Nav.vue index a3a5aabf..22d1a4ef 100644 --- a/samples/vue/quickstart/src/components/Nav.vue +++ b/samples/vue/quickstart/src/components/Nav.vue @@ -64,7 +64,7 @@ function toggleDark() { - +