From d922a462d949f5eb62d6e368209f478eccc5c7e3 Mon Sep 17 00:00:00 2001 From: Brion Date: Tue, 4 Aug 2026 22:52:11 +0530 Subject: [PATCH] Enhance ThunderID quickstart samples with redirect flow support - Added support for redirect-based authentication flow in Next.js, Nuxt, React, and Vue samples. - Updated environment variable handling to accommodate both native and redirect flows. - Enhanced configuration notices to guide users through setting up redirect URIs and CORS. - Improved styling for configuration steps and added documentation links for further assistance. --- samples/browser/quickstart/.env.example | 5 +- samples/browser/quickstart/README.md | 2 +- .../quickstart/scripts/prepare-dev.cjs | 7 +- samples/browser/quickstart/src/main.js | 3 +- samples/browser/quickstart/src/pages/home.js | 86 ++++++++++++++-- samples/browser/quickstart/src/style.css | 83 ++++++++++++++++ samples/express/quickstart/.env.example | 8 +- samples/express/quickstart/README.md | 2 +- samples/express/quickstart/index.mjs | 40 ++++++-- samples/express/quickstart/public/styles.css | 76 ++++++++++++++ .../quickstart/scripts/prepare-dev.cjs | 7 +- samples/nextjs/quickstart/.env.example | 28 +++++- samples/nextjs/quickstart/README.md | 20 +++- .../app/components/ConfigNotice.tsx | 60 ++++++++--- samples/nextjs/quickstart/app/globals.css | 76 ++++++++++++++ samples/nextjs/quickstart/app/layout.tsx | 12 ++- samples/nextjs/quickstart/package.json | 1 + .../nextjs/quickstart/scripts/prepare-dev.cjs | 47 ++++++++- samples/node/quickstart/.env.example | 9 +- samples/node/quickstart/README.md | 2 +- samples/node/quickstart/lib/ui.mjs | 7 ++ .../node/quickstart/scripts/prepare-dev.cjs | 7 +- samples/nuxt/quickstart/.env.example | 27 ++++- samples/nuxt/quickstart/README.md | 18 +++- samples/nuxt/quickstart/app/app.vue | 17 +++- samples/nuxt/quickstart/app/assets/styles.css | 76 ++++++++++++++ .../app/components/ConfigNotice.vue | 50 ++++++++-- samples/nuxt/quickstart/package.json | 1 + .../nuxt/quickstart/scripts/prepare-dev.cjs | 45 ++++++++- samples/react/quickstart/.env.example | 5 +- samples/react/quickstart/README.md | 2 +- .../react/quickstart/scripts/prepare-dev.cjs | 7 +- samples/react/quickstart/src/App.css | 83 ++++++++++++++++ .../src/components/ConfigNotice.jsx | 99 +++++++++++++++++-- samples/vue/quickstart/.env.example | 5 +- samples/vue/quickstart/README.md | 2 +- .../vue/quickstart/scripts/prepare-dev.cjs | 7 +- .../src/components/ConfigNotice.vue | 93 +++++++++++++++-- samples/vue/quickstart/src/style.css | 83 ++++++++++++++++ 39 files changed, 1114 insertions(+), 94 deletions(-) diff --git a/samples/browser/quickstart/.env.example b/samples/browser/quickstart/.env.example index bd724177..6a999c07 100644 --- a/samples/browser/quickstart/.env.example +++ b/samples/browser/quickstart/.env.example @@ -1,2 +1,5 @@ -VITE_THUNDERID_CLIENT_ID=your-client-id-here +# Base URL of your ThunderID deployment (org tenant). VITE_THUNDERID_BASE_URL=https://localhost:8090 + +# OAuth2 Client ID for this app. ThunderID Console -> your application -> Overview. +VITE_THUNDERID_CLIENT_ID=your-client-id-here diff --git a/samples/browser/quickstart/README.md b/samples/browser/quickstart/README.md index 4dd7245a..b17367fc 100644 --- a/samples/browser/quickstart/README.md +++ b/samples/browser/quickstart/README.md @@ -1,6 +1,6 @@ # ThunderID Browser Quickstart -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/browser/quickstart?file=.env&terminal=dev) +Open in StackBlitz A minimal Vite + vanilla JS app demonstrating sign-in and sign-out with the ThunderID JavaScript SDK (`@thunderid/browser`). diff --git a/samples/browser/quickstart/scripts/prepare-dev.cjs b/samples/browser/quickstart/scripts/prepare-dev.cjs index 6cd6ed3d..6a08904f 100644 --- a/samples/browser/quickstart/scripts/prepare-dev.cjs +++ b/samples/browser/quickstart/scripts/prepare-dev.cjs @@ -9,7 +9,12 @@ const root = path.join(__dirname, '..'); const envExample = path.join(root, '.env.example'); const envTarget = path.join(root, '.env'); if (fs.existsSync(envExample) && !fs.existsSync(envTarget)) { - fs.copyFileSync(envExample, envTarget); + // Blank placeholder values (e.g. `your-client-id-here`) so the copied .env + // still trips the app's missing-env-var check until real values are filled in. + const envContent = fs + .readFileSync(envExample, 'utf8') + .replace(/^([A-Z0-9_]+)=(your-\S*|generate-with-\S*)$/gm, '$1='); + fs.writeFileSync(envTarget, envContent); } const pkgPath = path.join(root, 'package.json'); diff --git a/samples/browser/quickstart/src/main.js b/samples/browser/quickstart/src/main.js index fac06fd1..f9822a4f 100644 --- a/samples/browser/quickstart/src/main.js +++ b/samples/browser/quickstart/src/main.js @@ -2,7 +2,7 @@ import './style.css' import auth, { missingEnvVars } from './auth.js' import { renderSignedOutNav, renderSignedInNav, attachNavHandlers, attachSignedOutNavHandlers } from './components/nav.js' import { renderProfileDialog, attachProfileDialogHandlers } from './components/profileDialog.js' -import { renderSignedOut, renderHome, renderConfigNeeded, startCountdown, attachSignedOutHandlers } from './pages/home.js' +import { renderSignedOut, renderHome, renderConfigNeeded, startCountdown, attachSignedOutHandlers, attachConfigNeededHandlers } from './pages/home.js' import { renderTokenDebug, attachTokenHandlers } from './pages/token.js' let isDark = false @@ -68,6 +68,7 @@ async function renderApp() { if (missingEnvVars.length > 0) { app.innerHTML = renderSignedOutNav({ isDark, hideSignIn: true }) + renderConfigNeeded(missingEnvVars) attachSignedOutNavHandlers({ auth }) + attachConfigNeededHandlers() return } diff --git a/samples/browser/quickstart/src/pages/home.js b/samples/browser/quickstart/src/pages/home.js index 7e23c672..24be3a57 100644 --- a/samples/browser/quickstart/src/pages/home.js +++ b/samples/browser/quickstart/src/pages/home.js @@ -139,6 +139,8 @@ export function attachSignedOutHandlers({ auth }) { } export function renderConfigNeeded(missing) { + const origin = typeof window !== 'undefined' ? window.location.origin : '' + return `
@@ -150,20 +152,88 @@ export function renderConfigNeeded(missing) {

Configuration needed

- This quickstart can't reach ThunderID yet. Set the following - environment variable(s), then restart the dev server. + This quickstart can't reach ThunderID yet. Follow the steps below, + then restart the dev server.

- -

- Copy .env.example to .env.local, fill in the - values from your ThunderID application, then run npm run dev again. + +

+
Step 1 · Set environment variables
+
    + ${missing.map(key => `
  • ${escapeHtml(key)}
  • `).join('')} +
+

+ Copy .env.example to .env, fill in the + values from your ThunderID application, then run npm run dev again. +

+
+ +
+
Step 2 · Allow this origin for CORS
+
+

+ Sign-in requests from this origin will be blocked by the browser + until it's added to your ThunderID deployment's allowed CORS + origins. In the ThunderID Console, go to + Settings → CORS → Allowed origins and + add it. +

+
+ ${escapeHtml(origin)} + +
+
+
+ +
+
Step 3 · Register redirect URIs
+
+

+ This origin also doubles as this app's Authorized redirect URI + and Post-Logout Redirect URI. In the ThunderID + Console, open this application and go to + Advanced Settings → OAuth2 Configuration, + then add it to both fields below. +

+
+
+
Authorized redirect URI
+
+ ${escapeHtml(origin)} + +
+
+
+
Post-Logout Redirect URI
+
+ ${escapeHtml(origin)} + +
+
+
+
+
+ +

+ Need more info? Take a look at the + Browser quickstart guide.

` } +export function attachConfigNeededHandlers() { + if (!navigator.clipboard) return + document.querySelectorAll('.config-copy-btn').forEach((btn) => { + btn.addEventListener('click', () => { + navigator.clipboard.writeText(btn.getAttribute('data-copy') || '').then(() => { + const original = btn.textContent + btn.textContent = 'Copied!' + setTimeout(() => { btn.textContent = original }, 1500) + }) + }) + }) +} + export function renderHome({ user, idToken }) { const now = Math.floor(Date.now() / 1000) const givenName = user?.given_name || user?.name || user?.username || 'there' diff --git a/samples/browser/quickstart/src/style.css b/samples/browser/quickstart/src/style.css index de707e97..aac59665 100644 --- a/samples/browser/quickstart/src/style.css +++ b/samples/browser/quickstart/src/style.css @@ -248,6 +248,89 @@ body { font-size: 13px; } +.config-step { + width: 100%; + max-width: 420px; + margin: 0 auto 28px; +} + +.config-step-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--muted); + margin-bottom: 10px; + text-align: left; +} + +.config-step .config-list, +.config-step .config-hint { + margin-left: 0; + margin-right: 0; +} + +.config-box { + text-align: left; + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 16px; +} + +.config-box-body { + font-size: 13px; + color: var(--muted); + line-height: 1.6; + margin-bottom: 14px; +} + +.config-value-row { + display: flex; + align-items: center; + gap: 8px; +} + +.config-value { + flex: 1; + min-width: 0; + overflow-x: auto; + white-space: nowrap; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + color: var(--text); + background: var(--blue-subtle); + border-radius: 4px; + padding: 8px 10px; +} + +.config-value-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--muted); + margin-bottom: 4px; +} + +.config-value-group { + display: flex; + flex-direction: column; + gap: 12px; +} + +.config-docs-note { + font-size: 13px; + color: var(--muted); + max-width: 420px; + margin: 0 auto; +} + +.config-docs-note a { + color: var(--blue); + font-weight: 600; +} + /* Buttons */ .btn-primary, .btn-outline, diff --git a/samples/express/quickstart/.env.example b/samples/express/quickstart/.env.example index 6e80aa4a..d874a505 100644 --- a/samples/express/quickstart/.env.example +++ b/samples/express/quickstart/.env.example @@ -1,5 +1,11 @@ +# Base URL of your ThunderID deployment (org tenant). +THUNDERID_BASE_URL=https://localhost:8090 + +# OAuth2 Client ID for this app. ThunderID Console -> your application -> Overview. THUNDERID_CLIENT_ID=your-client-id-here +# OAuth2 Client Secret for this app. 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. THUNDERID_CLIENT_SECRET=your-client-secret-here -THUNDERID_BASE_URL=https://localhost:8090 + # 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/express/quickstart/README.md b/samples/express/quickstart/README.md index a2b752f2..69c01208 100644 --- a/samples/express/quickstart/README.md +++ b/samples/express/quickstart/README.md @@ -1,6 +1,6 @@ # ThunderID Express Quickstart -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/express/quickstart?file=.env&terminal=dev) +Open in StackBlitz A minimal Express.js **API** protected by ThunderID access tokens, using the ThunderID JavaScript SDK (`@thunderid/express`). Unlike the browser-focused quickstarts, this sample doesn't have a diff --git a/samples/express/quickstart/index.mjs b/samples/express/quickstart/index.mjs index 178014ed..7b8a0fb0 100644 --- a/samples/express/quickstart/index.mjs +++ b/samples/express/quickstart/index.mjs @@ -69,13 +69,39 @@ function renderConfigNeeded() {
${thunderMark(40)}
Setup required

Configuration needed

-

This quickstart can't reach ThunderID yet. Set the following environment - variable(s), then restart the server.

- -

Copy .env.example to .env, fill in the values from - your ThunderID application, then run npm run dev again.

+

This quickstart can't reach ThunderID yet. Follow the steps below, then + restart the server.

+ +
+
Step 1 · Set environment variables
+ +

Copy .env.example to .env, fill in the values from + your ThunderID application, then run npm run dev again.

+
+ +
+
Step 2 · Register redirect URIs
+
+

Sign-in and sign-out are handled by this app's server, so no CORS + configuration is needed. In the ThunderID Console, open this application and go to + Advanced Settings → OAuth2 Configuration, then add the exact URIs below.

+
+
+
Authorized redirect URI
+ http://localhost:3000/login +
+
+
Post-Logout Redirect URI
+ http://localhost:3000/logout +
+
+
+
+ +

Need more info? Take a look at the + Express quickstart guide.

`, }); diff --git a/samples/express/quickstart/public/styles.css b/samples/express/quickstart/public/styles.css index d16c159f..26fffc19 100644 --- a/samples/express/quickstart/public/styles.css +++ b/samples/express/quickstart/public/styles.css @@ -671,6 +671,82 @@ button { font-size: 13px; } +.config-step { + width: 100%; + max-width: 420px; + margin: 0 auto 28px; +} + +.config-step-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--color-muted); + margin-bottom: 10px; + text-align: left; +} + +.config-step .config-list, +.config-step .config-hint { + margin-left: 0; + margin-right: 0; +} + +.config-box { + text-align: left; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + padding: 16px; +} + +.config-box-body { + font-size: 13px; + color: var(--color-muted); + line-height: 1.6; + margin-bottom: 14px; +} + +.config-value-group { + display: flex; + flex-direction: column; + gap: 12px; +} + +.config-value-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-muted); + margin-bottom: 4px; +} + +.config-value { + display: block; + overflow-x: auto; + white-space: nowrap; + font-family: var(--font-mono); + font-size: 12px; + color: var(--color-text); + background: rgba(54, 136, 255, 0.08); + border-radius: 4px; + padding: 8px 10px; +} + +.config-docs-note { + font-size: 13px; + color: var(--color-muted); + max-width: 420px; + margin: 0 auto; +} + +.config-docs-note a { + color: var(--color-primary); + font-weight: 600; +} + /* ── Token debug page ── */ .token-header { display: flex; diff --git a/samples/express/quickstart/scripts/prepare-dev.cjs b/samples/express/quickstart/scripts/prepare-dev.cjs index 6cd6ed3d..6a08904f 100644 --- a/samples/express/quickstart/scripts/prepare-dev.cjs +++ b/samples/express/quickstart/scripts/prepare-dev.cjs @@ -9,7 +9,12 @@ const root = path.join(__dirname, '..'); const envExample = path.join(root, '.env.example'); const envTarget = path.join(root, '.env'); if (fs.existsSync(envExample) && !fs.existsSync(envTarget)) { - fs.copyFileSync(envExample, envTarget); + // Blank placeholder values (e.g. `your-client-id-here`) so the copied .env + // still trips the app's missing-env-var check until real values are filled in. + const envContent = fs + .readFileSync(envExample, 'utf8') + .replace(/^([A-Z0-9_]+)=(your-\S*|generate-with-\S*)$/gm, '$1='); + fs.writeFileSync(envTarget, envContent); } const pkgPath = path.join(root, 'package.json'); diff --git a/samples/nextjs/quickstart/.env.example b/samples/nextjs/quickstart/.env.example index d1bf3e3d..43ae09b6 100644 --- a/samples/nextjs/quickstart/.env.example +++ b/samples/nextjs/quickstart/.env.example @@ -1,9 +1,27 @@ +# Base URL of your ThunderID deployment (org tenant). NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 -NEXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here + +# OAuth2 Client Secret for this app. 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. +THUNDERID_CLIENT_SECRET=your-client-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 -THUNDERID_CLIENT_SECRET=your-client-secret-here -THUNDERID_SECRET=generate-with-openssl-rand-base64-32 -# DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production. -NODE_TLS_REJECT_UNAUTHORIZED=0 + +# ── 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. +# NEXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here diff --git a/samples/nextjs/quickstart/README.md b/samples/nextjs/quickstart/README.md index dde93510..d45dea30 100644 --- a/samples/nextjs/quickstart/README.md +++ b/samples/nextjs/quickstart/README.md @@ -1,6 +1,6 @@ # ThunderID Next.js Quickstart -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/nextjs/quickstart?file=.env.local&terminal=dev) +Open in StackBlitz A minimal Next.js 15 App Router application demonstrating ThunderID authentication with OAuth 2.0, PKCE, and JWT out of the box. @@ -15,19 +15,29 @@ A minimal Next.js 15 App Router application demonstrating ThunderID authenticati 1. Copy the example environment file: ```bash - cp .env.example .env.local + cp .env.example .env ``` -2. Fill in your ThunderID credentials in `.env.local`, using the values you set in `thunderid-config/thunderid.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: - ``` + ```dotenv NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NEXT_PUBLIC_THUNDERID_CLIENT_ID=NEXTJS_QUICKSTART NEXT_PUBLIC_THUNDERID_APPLICATION_ID= + 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 + ``` + 3. Start the development server: ```bash diff --git a/samples/nextjs/quickstart/app/components/ConfigNotice.tsx b/samples/nextjs/quickstart/app/components/ConfigNotice.tsx index 0c9d8bb3..4d581cb2 100644 --- a/samples/nextjs/quickstart/app/components/ConfigNotice.tsx +++ b/samples/nextjs/quickstart/app/components/ConfigNotice.tsx @@ -23,7 +23,7 @@ function SunIcon() { ) } -export default function ConfigNotice({ missing }: { missing: string[] }) { +export default function ConfigNotice({ missing, isRedirectFlow }: { missing: string[]; isRedirectFlow: boolean }) { const [dark, setDark] = useState(false) const toggle = () => { @@ -62,19 +62,57 @@ export default function ConfigNotice({ missing }: { missing: string[] }) {

Configuration needed

- This quickstart can't reach ThunderID yet. Set the following - environment variable(s), then restart the dev server. + This quickstart can't reach ThunderID yet. Follow the steps + below, then restart the dev server.

-
    - {missing.map((key) => ( -
  • {key}
  • - ))} -
+
+
Step 1 · Set environment variables
-

- Copy .env.example to .env.local, fill in the - values from your ThunderID application, then run npm run dev again. +

    + {missing.map((key) => ( +
  • {key}
  • + ))} +
+ +

+ Copy .env.example to .env, fill in the + values from your ThunderID application, then run npm run dev again. +

+
+ + {isRedirectFlow && ( +
+
Step 2 · Register redirect URIs
+ +
+

+ Sign-in and sign-out are handled by this app's server, so + no CORS configuration is needed. In the{' '} + ThunderID Console, open this application and go + to Advanced Settings → OAuth2 Configuration, + then add the exact URIs below. +

+ +
+
+
Authorized redirect URI
+ http://localhost:3000 +
+
+
Post-Logout Redirect URI
+ http://localhost:3000 +
+
+
+
+ )} + +

+ Need more info? Take a look at the{' '} + + Next.js quickstart guide. +

diff --git a/samples/nextjs/quickstart/app/globals.css b/samples/nextjs/quickstart/app/globals.css index 62bbaf09..918b551c 100644 --- a/samples/nextjs/quickstart/app/globals.css +++ b/samples/nextjs/quickstart/app/globals.css @@ -383,6 +383,82 @@ button:focus-visible { font-size: 13px; } +.config-step { + width: 100%; + max-width: 420px; + margin: 0 auto 28px; +} + +.config-step-label { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.09em; + color: var(--color-text-muted); + margin-bottom: 10px; + text-align: left; +} + +.config-step .config-list, +.config-step .config-hint { + margin-left: 0; + margin-right: 0; +} + +.config-box { + text-align: left; + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: var(--radius-card); + padding: 16px; +} + +.config-box-body { + font-size: 13px; + color: var(--color-text-muted); + line-height: 1.6; + margin-bottom: 14px; +} + +.config-value-group { + display: flex; + flex-direction: column; + gap: 12px; +} + +.config-value-label { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-text-muted); + margin-bottom: 4px; +} + +.config-value { + display: block; + overflow-x: auto; + white-space: nowrap; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + color: var(--color-text); + background: rgba(54, 136, 255, 0.1); + border-radius: 4px; + padding: 8px 10px; +} + +.config-docs-note { + font-size: 13px; + color: var(--color-text-muted); + max-width: 420px; + margin: 0 auto; +} + +.config-docs-note a { + color: var(--color-primary); + font-weight: 600; +} + /* ── User / welcome card ── */ .user-card { display: flex; diff --git a/samples/nextjs/quickstart/app/layout.tsx b/samples/nextjs/quickstart/app/layout.tsx index 77535c2a..d8172a5c 100644 --- a/samples/nextjs/quickstart/app/layout.tsx +++ b/samples/nextjs/quickstart/app/layout.tsx @@ -10,11 +10,19 @@ export const metadata: Metadata = { description: 'ThunderID authentication with Next.js', } +// Redirect-based flow (NEXT_PUBLIC_THUNDERID_CLIENT_ID set) sends the user to +// ThunderID's hosted pages and needs a registered redirect URI. The default, +// native flow renders sign-in/sign-up inline via the app's own routes and +// needs an application ID instead — no redirect URI or CORS setup required. +const isRedirectFlow = Boolean(process.env.NEXT_PUBLIC_THUNDERID_CLIENT_ID) + const REQUIRED_ENV_VARS = [ 'NEXT_PUBLIC_THUNDERID_BASE_URL', - 'NEXT_PUBLIC_THUNDERID_CLIENT_ID', 'THUNDERID_CLIENT_SECRET', 'THUNDERID_SECRET', + ...(isRedirectFlow + ? ['NEXT_PUBLIC_THUNDERID_CLIENT_ID'] + : ['NEXT_PUBLIC_THUNDERID_APPLICATION_ID', 'NEXT_PUBLIC_THUNDERID_SIGN_IN_URL', 'NEXT_PUBLIC_THUNDERID_SIGN_UP_URL']), ] export default function RootLayout({ @@ -28,7 +36,7 @@ export default function RootLayout({ {missingEnvVars.length > 0 ? ( - + ) : ( {children} diff --git a/samples/nextjs/quickstart/package.json b/samples/nextjs/quickstart/package.json index f6e8405a..1d6c9f43 100644 --- a/samples/nextjs/quickstart/package.json +++ b/samples/nextjs/quickstart/package.json @@ -4,6 +4,7 @@ "version": "0.0.0", "scripts": { "prepare-dev": "node scripts/prepare-dev.cjs", + "prepare-dev:redirect": "node scripts/prepare-dev.cjs --flow=redirect", "dev": "next dev", "build": "next build", "start": "next start" diff --git a/samples/nextjs/quickstart/scripts/prepare-dev.cjs b/samples/nextjs/quickstart/scripts/prepare-dev.cjs index db573761..c90ba9b2 100644 --- a/samples/nextjs/quickstart/scripts/prepare-dev.cjs +++ b/samples/nextjs/quickstart/scripts/prepare-dev.cjs @@ -6,10 +6,51 @@ 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 flowArg = process.argv.find((arg) => arg.startsWith('--flow=')); +const flowExplicitlyRequested = Boolean(flowArg); +const flow = flowArg ? flowArg.slice('--flow='.length) : 'native'; + +if (flowExplicitlyRequested && flow !== 'native' && flow !== 'redirect') { + console.error(`Unknown --flow value "${flow}". Expected "native" or "redirect".`); + process.exit(1); +} + +/** Toggles the leading `# ` on env var lines to match the selected flow. */ +function applyFlow(envContent, selectedFlow) { + const varsToEnable = selectedFlow === 'redirect' ? REDIRECT_FLOW_VARS : NATIVE_FLOW_VARS; + const varsToDisable = selectedFlow === 'redirect' ? NATIVE_FLOW_VARS : REDIRECT_FLOW_VARS; + + return envContent + .split('\n') + .map((line) => { + const enable = varsToEnable.find((key) => line.replace(/^#\s*/, '').startsWith(`${key}=`)); + if (enable) return line.replace(/^#\s*/, ''); + + const disable = varsToDisable.find((key) => line.startsWith(`${key}=`)); + if (disable) return `# ${line}`; + + return line; + }) + .join('\n'); +} + const envExample = path.join(root, '.env.example'); -const envTarget = path.join(root, '.env.local'); -if (fs.existsSync(envExample) && !fs.existsSync(envTarget)) { - fs.copyFileSync(envExample, envTarget); +const envTarget = path.join(root, '.env'); + +// Only explicit `--flow=` invocations are allowed to (re)write an existing +// .env — a plain `npm run prepare-dev` (e.g. from .stackblitzrc's +// startCommand) stays a safe, idempotent copy-if-missing. +if (fs.existsSync(envExample) && (flowExplicitlyRequested || !fs.existsSync(envTarget))) { + const envSource = fs.existsSync(envTarget) ? envTarget : envExample; + const envContent = applyFlow(fs.readFileSync(envSource, 'utf8'), flow) + // Blank placeholder values (e.g. `your-client-id-here`) so the copied .env + // still trips the app's missing-env-var check until real values are filled in. + .replace(/^([A-Z0-9_]+)=(your-\S*|generate-with-\S*)$/gm, '$1='); + fs.writeFileSync(envTarget, envContent); } const pkgPath = path.join(root, 'package.json'); diff --git a/samples/node/quickstart/.env.example b/samples/node/quickstart/.env.example index b9006799..b7fceca6 100644 --- a/samples/node/quickstart/.env.example +++ b/samples/node/quickstart/.env.example @@ -1,7 +1,14 @@ +# Base URL of your ThunderID deployment (org tenant). +THUNDERID_BASE_URL=https://localhost:8090 + +# OAuth2 Client ID for this service. ThunderID Console -> your application -> Overview. THUNDERID_CLIENT_ID=your-agent-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_BASE_URL=https://localhost:8090 + # Optional: space-separated scopes to request for this service. THUNDERID_SCOPE= + # 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/node/quickstart/README.md b/samples/node/quickstart/README.md index 37e5f764..eaa51c3f 100644 --- a/samples/node/quickstart/README.md +++ b/samples/node/quickstart/README.md @@ -1,6 +1,6 @@ # ThunderID Node.js Service Quickstart -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/node/quickstart?file=.env&terminal=dev) +Open in StackBlitz A minimal machine-to-machine (service-to-service) client, using the ThunderID Node.js SDK (`@thunderid/node`). Unlike the other quickstarts, there's no user and no browser sign-in: this diff --git a/samples/node/quickstart/lib/ui.mjs b/samples/node/quickstart/lib/ui.mjs index cc520674..73131851 100644 --- a/samples/node/quickstart/lib/ui.mjs +++ b/samples/node/quickstart/lib/ui.mjs @@ -59,6 +59,13 @@ export function printConfigNeeded(missingEnvVars) { ), ); console.log(); + console.log( + indent( + pc.dim('Need more info? Take a look at the Node quickstart guide: ') + + pc.cyan('https://thunderid.dev/docs/next/getting-started/connect-your-application/node/'), + ), + ); + console.log(); } export function printAuthenticated({baseUrl, clientId, scope}) { diff --git a/samples/node/quickstart/scripts/prepare-dev.cjs b/samples/node/quickstart/scripts/prepare-dev.cjs index 6cd6ed3d..6a08904f 100644 --- a/samples/node/quickstart/scripts/prepare-dev.cjs +++ b/samples/node/quickstart/scripts/prepare-dev.cjs @@ -9,7 +9,12 @@ const root = path.join(__dirname, '..'); const envExample = path.join(root, '.env.example'); const envTarget = path.join(root, '.env'); if (fs.existsSync(envExample) && !fs.existsSync(envTarget)) { - fs.copyFileSync(envExample, envTarget); + // Blank placeholder values (e.g. `your-client-id-here`) so the copied .env + // still trips the app's missing-env-var check until real values are filled in. + const envContent = fs + .readFileSync(envExample, 'utf8') + .replace(/^([A-Z0-9_]+)=(your-\S*|generate-with-\S*)$/gm, '$1='); + fs.writeFileSync(envTarget, envContent); } const pkgPath = path.join(root, 'package.json'); diff --git a/samples/nuxt/quickstart/.env.example b/samples/nuxt/quickstart/.env.example index e8af8957..011d93f9 100644 --- a/samples/nuxt/quickstart/.env.example +++ b/samples/nuxt/quickstart/.env.example @@ -1,9 +1,30 @@ +# Base URL of your ThunderID deployment (org tenant). NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 -NUXT_PUBLIC_THUNDERID_CLIENT_ID=your-client-id-here + +# OAuth2 Client Secret for this app. 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. +THUNDERID_CLIENT_SECRET=your-client-secret-here + +# Secret used to encrypt this app's session cookie. Generate locally, not from the console. +THUNDERID_SESSION_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. NUXT_PUBLIC_THUNDERID_APPLICATION_ID=your-application-id-here +# Local app route that renders the sign-in page. Not from the console. 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 -THUNDERID_CLIENT_SECRET=your-client-secret-here -THUNDERID_SESSION_SECRET=generate-with-openssl-rand-base64-32 + +# ── 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 b57c3a46..78fc5bed 100644 --- a/samples/nuxt/quickstart/README.md +++ b/samples/nuxt/quickstart/README.md @@ -1,6 +1,6 @@ # ThunderID Nuxt Quickstart -[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/nuxt/quickstart?file=.env&terminal=dev) +Open in StackBlitz A minimal Nuxt 3 application demonstrating ThunderID authentication with OAuth 2.0, PKCE, and JWT out of the box. @@ -18,16 +18,26 @@ 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`: +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: - ``` + ```dotenv NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090 - NUXT_PUBLIC_THUNDERID_CLIENT_ID=NUXT_QUICKSTART NUXT_PUBLIC_THUNDERID_APPLICATION_ID= + NUXT_PUBLIC_THUNDERID_SIGN_IN_URL=/signin + NUXT_PUBLIC_THUNDERID_SIGN_UP_URL=/signup 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: + + ```bash + npm run prepare-dev:redirect + ``` + 3. Start the development server: ```bash diff --git a/samples/nuxt/quickstart/app/app.vue b/samples/nuxt/quickstart/app/app.vue index 3561126f..d463ae9a 100644 --- a/samples/nuxt/quickstart/app/app.vue +++ b/samples/nuxt/quickstart/app/app.vue @@ -1,14 +1,27 @@