diff --git a/backend/cmd/server/bootstrap/01-default-resources.yaml b/backend/cmd/server/bootstrap/01-default-resources.yaml
index 42aa640666..af7134e1ff 100644
--- a/backend/cmd/server/bootstrap/01-default-resources.yaml
+++ b/backend/cmd/server/bootstrap/01-default-resources.yaml
@@ -3,7 +3,7 @@ id: 01900000-0000-7000-8000-000000000001
handle: default
name: Default
description: Default organization unit
-logoUrl: "avatar:shape=rounded,variant=anonymous_entity,content=pavilion,colors=0,bg=#2c4ed4"
+logoUrl: "avatar:shape=rounded,variant=anonymous_entity,content=pavilion,colors=0"
---
resource_type: user_type
id: 01900000-0000-7000-8000-000000000010
@@ -4900,7 +4900,7 @@ description: Management application for ThunderID
type: browser
ouId: 01900000-0000-7000-8000-000000000001
url: "{{ .PUBLIC_URL }}/console"
-logoUrl: "avatar:shape=rounded,variant=anonymous_entity,content=cube,colors=0,bg=#64be90"
+logoUrl: "avatar:shape=rounded,variant=anonymous_entity,content=cube,colors=0"
authFlowId: 01900000-0000-7000-8000-000000000068
registrationFlowId: 01900000-0000-7000-8000-000000000069
isRegistrationFlowEnabled: false
diff --git a/docs/.gitignore b/docs/.gitignore
index 1f8041536f..5b8e15a1f4 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -13,6 +13,7 @@ static/api/next/combined.yaml
static/api/next/postman/
static/data/releases.json
static/data/sdk-releases.json
+static/docs/
# Misc
.DS_Store
diff --git a/docs/content/getting-started/connect-your-application/android.mdx b/docs/content/getting-started/connect-your-application/android.mdx
index d269027256..3fcc7fab5d 100644
--- a/docs/content/getting-started/connect-your-application/android.mdx
+++ b/docs/content/getting-started/connect-your-application/android.mdx
@@ -64,7 +64,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My Android App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The Android SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create an Android App
@@ -102,48 +106,6 @@ implementation 'dev.thunderid:compose:0.1.0'
```
:::
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme in two places.
-
-**1. Register the scheme in your app**
-
-Open `AndroidManifest.xml` and add an intent filter with your scheme to your launcher activity. Use `singleTask` launch mode so the redirect reuses the existing activity instance instead of creating a new one.
-
-```xml title="AndroidManifest.xml" showLineNumbers
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**2. Register the redirect URI in the console**
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-dev.thunderid.quickstart://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-dev.thunderid.quickstart://logout
-```
-
## Initialize the SDK
Open your `MainActivity` and wrap the content you pass to `setContent` with the `ThunderIDProvider` composable. This provides `ThunderIDState` to all child composables through `LocalThunderID`.
@@ -167,10 +129,7 @@ class MainActivity : ComponentActivity() {
ThunderIDProvider(
config = ThunderIDConfig(
baseUrl = "https://localhost:8090",
- clientId = "",
scopes = listOf("openid", "profile", "email"),
- afterSignInUrl = "dev.thunderid.quickstart://callback",
- afterSignOutUrl = "dev.thunderid.quickstart://logout",
applicationId = ""
)
) {
@@ -183,7 +142,7 @@ class MainActivity : ComponentActivity() {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -191,11 +150,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `"openid"` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/content/getting-started/connect-your-application/flutter.mdx b/docs/content/getting-started/connect-your-application/flutter.mdx
index 162fee924a..590ece4a16 100644
--- a/docs/content/getting-started/connect-your-application/flutter.mdx
+++ b/docs/content/getting-started/connect-your-application/flutter.mdx
@@ -65,7 +65,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My Flutter App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The Flutter SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create a Flutter App
@@ -95,55 +99,6 @@ Then install it:
flutter pub get
```
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme on both platforms.
-
-### iOS
-
-Open `ios/Runner/Info.plist` and add a URL scheme under **URL Types**:
-
-```xml title="ios/Runner/Info.plist"
-CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- dev.thunderid.app
-
-
-
-```
-
-### Android
-
-Open `android/app/src/main/AndroidManifest.xml` and add an intent filter inside your `` tag:
-
-```xml title="android/app/src/main/AndroidManifest.xml"
-
-
-
-
-
-
-```
-
-### Register the URIs in the console
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-dev.thunderid.app://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-dev.thunderid.app://logout
-```
-
## Initialize the SDK
Wrap your root widget with `ThunderIDProvider` in `lib/main.dart`:
@@ -157,10 +112,7 @@ void main() {
ThunderIDProvider(
config: ThunderIDConfig(
baseUrl: 'https://localhost:8090',
- clientId: '',
scopes: const ['openid', 'profile', 'email'],
- afterSignInUrl: 'dev.thunderid.app://callback',
- afterSignOutUrl: 'dev.thunderid.app://logout',
applicationId: '',
),
child: const MyApp(),
@@ -181,7 +133,7 @@ class MyApp extends StatelessWidget {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -189,11 +141,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `'openid'` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/content/getting-started/connect-your-application/ios.mdx b/docs/content/getting-started/connect-your-application/ios.mdx
index 8518cc6925..29d9f15e4a 100644
--- a/docs/content/getting-started/connect-your-application/ios.mdx
+++ b/docs/content/getting-started/connect-your-application/ios.mdx
@@ -65,7 +65,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My iOS App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The iOS SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create an iOS App
@@ -93,40 +97,6 @@ pod 'ThunderIDSwiftUI'
```
:::
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme in two places.
-
-**1. Register the scheme in your app**
-
-Open your app's `Info.plist` and add a URL scheme under **URL Types**. For example:
-
-```xml
-CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- io.thunderid.b2c
-
-
-
-```
-
-**2. Register the redirect URI in the console**
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-io.thunderid.b2c://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-io.thunderid.b2c://logout
-```
-
## Initialize the SDK
Open your app's entry point (the file that conforms to `App`) and apply the `.thunderIDProvider(config:)` modifier to your root view. This injects a `ThunderIDState` environment object into all child views.
@@ -142,10 +112,7 @@ struct MyApp: App {
ContentView()
.thunderIDProvider(config: ThunderIDConfig(
baseUrl: "https://localhost:8090",
- clientId: "",
scopes: ["openid", "profile", "email"],
- afterSignInUrl: "io.thunderid.b2c://callback",
- afterSignOutUrl: "io.thunderid.b2c://logout",
applicationId: ""
))
}
@@ -154,7 +121,7 @@ struct MyApp: App {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -162,11 +129,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `"openid"` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/content/getting-started/connect-your-application/node.mdx b/docs/content/getting-started/connect-your-application/node.mdx
index d7100d7911..0875c9a4b9 100644
--- a/docs/content/getting-started/connect-your-application/node.mdx
+++ b/docs/content/getting-started/connect-your-application/node.mdx
@@ -4,7 +4,7 @@ title: Node.js Quickstart
docType: quickstart
sidebar_position: 5
persona: app
-description: Add {{ProductName}} authentication to a vanilla Node.js application using the @thunderid/node SDK.
+description: Authenticate a Node.js service to {{ProductName}} as itself using the OAuth 2.0 client_credentials grant with the @thunderid/node SDK.
---
import {
@@ -20,7 +20,7 @@ import {
# Node.js Quickstart
-Use this guide to add authentication to a vanilla Node.js application using the `@thunderid/node` SDK and the built-in `http` module. No framework required.
+Use this guide to authenticate a Node.js service to using the `@thunderid/node` SDK. Unlike the other quickstarts in this section, there's no user and no browser sign-in: the service authenticates as **itself** with the OAuth 2.0 `client_credentials` grant, then uses the resulting access token to call another piece of business logic. Use this pattern for background jobs, cron tasks, or one backend service calling another, where there's no human in the loop to redirect through a login page.
@@ -28,18 +28,22 @@ Use this guide to add authentication to a vanilla Node.js applic
}>Create a Node.js project
}>Install the @thunderid/node package
-}>Add working sign-in and sign-out routes
-}>Protect routes and display the signed-in user's profile
+}>Authenticate a service with the client_credentials grant
+}>Use the resulting access token to call business logic
## Prerequisites
-}>About 15 minutes
+}>About 10 minutes
}>Node.js 18+ installed on your system
}>npm, yarn, or pnpm
}>Your preferred code editor
+:::tip Example Source Code
+Check out the complete Node.js Quickstart Sample in the repository.
+:::
+
## Run
@@ -50,7 +54,9 @@ Start a local instance. Pick the method that works best for you:
Once it's running, the console is available at [https://localhost:8090/console](https://localhost:8090/console).
-## Create an Application
+## Create an Agent
+
+Agents are 's machine identities, distinct from user-facing applications. A background service like this one authenticates as an agent, not as an application.
1. Sign in to the Console.
@@ -58,12 +64,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
If you used the default setup, sign in to the Console as `admin` with the password generated during setup and printed to the setup output (unless you supplied your own).
:::
-2. Navigate to **Applications**, and click **Add Application**.
-
-3. Under **Technology**, select **Node.js**.
-4. Enter a name (e.g. `My Node.js App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy both the **Client ID** and **Client Secret** from the window that pops up. The Client ID can also be found in the **General** tab.
-6. Under **General**, add `http://localhost:3000/callback` to the list of **Authorized Redirect URIs**.
+2. Navigate to **Agents** and click **Add Agent**.
+3. Enter an **Agent name** (e.g. `node-service-quickstart`) and select an **Owner**, then click **Create agent**.
+4. displays the agent's **Client Secret** once. Copy it now; it cannot be retrieved again.
+5. Open the agent's **Advanced Settings** tab, enable the `client_credentials` grant type, and set the **Client authentication method** to `client_secret_basic`.
+6. Copy the **Client ID** from the **General** tab.
## Create a Node.js Project
@@ -71,20 +76,20 @@ Initialize a new Node.js project:
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
npm init -y
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
yarn init -y
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
pnpm init
@@ -107,309 +112,92 @@ Install the Node.js SDK:
-## Initialize the Client
+## Authenticate as the Service
-Create an `index.js` file and initialize the `ThunderIDNodeClient` with your application credentials:
+Create an `index.mjs` file and initialize `ThunderIDNodeClient` with `grantType: 'client_credentials'`. With that set, `getAccessToken()` authenticates as the service itself, no session and no sign-in, and transparently fetches, caches, and refreshes the token.
-```js title="index.js" showLineNumbers
-const http = require('http');
-const { URL } = require('url');
-const { randomUUID } = require('crypto');
-const { ThunderIDNodeClient } = require('@thunderid/node');
+```js title="index.mjs" showLineNumbers
+import { ThunderIDNodeClient } from '@thunderid/node';
-const PORT = 3000;
-const SESSION_COOKIE = 'tid_session';
+const client = new ThunderIDNodeClient();
-const auth = new ThunderIDNodeClient();
+await client.initialize({
+ baseUrl: 'https://localhost:8090',
+ clientId: '',
+ clientSecret: '',
+ grantType: 'client_credentials',
+});
-function getSessionId(req) {
- const cookieHeader = req.headers.cookie ?? '';
- for (const part of cookieHeader.split(';')) {
- const [name, value] = part.trim().split('=');
- if (name === SESSION_COOKIE) return decodeURIComponent(value);
- }
- return null;
-}
-
-async function main() {
- await auth.initialize({
- clientId: '',
- clientSecret: '',
- baseUrl: 'https://localhost:8090',
- afterSignInUrl: 'http://localhost:3000/callback',
- afterSignOutUrl: 'http://localhost:3000',
- });
-
- const server = http.createServer(async (req, res) => {
- // routes added in the next step
- });
-
- server.listen(PORT, () => {
- console.log(`Server running on http://localhost:${PORT}`);
- });
-}
+const accessToken = await client.getAccessToken();
+const { scope } = await client.decodeJwtToken(accessToken);
-main();
+console.log(`Authenticated with scope: ${scope}`);
```
:::warning Configuration
-Replace `` and `` with the values from your application. Set the authorized redirect URL in your application settings to `http://localhost:3000/callback`.
+Replace `` and `` with the **Client ID** and **Client Secret** from your agent.
:::
### Configuration Parameters
| Parameter | Description |
|-----------|-------------|
-| `clientId` | The Client ID from your application |
-| `clientSecret` | The Client Secret from your application |
| `baseUrl` | Your instance URL (e.g., `https://localhost:8090`) |
-| `afterSignInUrl` | The callback URL redirects to after sign-in |
-| `afterSignOutUrl` | The URL to redirect to after sign-out |
-
-## Add Sign-In and Sign-Out Routes
-
-The `signIn` method works in two phases: it first redirects the user to , then handles the authorization code on the callback. Session state is tied to a session ID stored in a cookie.
-
-Replace the `// routes added in the next step` comment with:
-
-```js title="index.js" showLineNumbers
- const url = new URL(req.url, `http://localhost:${PORT}`);
-
- try {
- if (url.pathname === '/login') {
- let sessionId = getSessionId(req);
- const extraHeaders = {};
- if (!sessionId) {
- sessionId = randomUUID();
- extraHeaders['Set-Cookie'] =
- `${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
- }
- await auth.signIn((authUrl) => {
- res.writeHead(302, { ...extraHeaders, Location: authUrl });
- res.end();
- }, sessionId);
-
- } else if (url.pathname === '/callback') {
- const code = url.searchParams.get('code');
- const state = url.searchParams.get('state');
- const sessionState = url.searchParams.get('session_state');
- const sessionId = getSessionId(req);
-
- if (!sessionId || !code || !state) {
- res.writeHead(400);
- return res.end('Bad request');
- }
-
- await auth.signIn(() => {}, sessionId, code, sessionState, state);
- res.writeHead(302, { Location: '/profile' });
- res.end();
-
- } else if (url.pathname === '/logout') {
- const sessionId = getSessionId(req);
- if (!sessionId) {
- res.writeHead(302, { Location: '/' });
- return res.end();
- }
- const signOutUrl = await auth.signOut(sessionId);
- res.writeHead(302, {
- Location: signOutUrl,
- 'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
- });
- res.end();
-
- }
- } catch {
- res.writeHead(500);
- res.end('Internal server error');
- }
-```
+| `clientId` | The Client ID from your agent |
+| `clientSecret` | The Client Secret from your agent |
+| `grantType` | Set to `'client_credentials'` to authenticate the service as itself, with no user and no browser redirect |
-**How the sign-in flow works:**
-
-1. `GET /login`: generates a session ID, stores it in a cookie, and calls `signIn` with an `authUrlCallback`. The callback receives the authorization URL and redirects the user's browser there.
-2. `GET /callback`: redirects back with `code` and `state` query parameters. Calling `signIn` again with those values exchanges the code for tokens and stores the session.
-3. `GET /logout`: calls `signOut` to get the OIDC end-session URL, clears the local cookie, then redirects the browser to complete the logout at .
-
-## Protect a Route and Display User Info
-
-Use `isSignedIn` to guard routes and `getUser` to retrieve the authenticated user's profile. Add these inside the same `try` block, before the closing `}`:
-
-```js title="index.js" showLineNumbers
- if (url.pathname === '/') {
- const sessionId = getSessionId(req);
- const signedIn = sessionId && (await auth.isSignedIn(sessionId));
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(signedIn
- ? 'View profile | Sign out'
- : 'Sign in'
- );
-
- } else if (url.pathname === '/profile') {
- const sessionId = getSessionId(req);
- if (!sessionId || !(await auth.isSignedIn(sessionId))) {
- res.writeHead(302, { Location: '/login' });
- return res.end();
- }
- const user = await auth.getUser(sessionId);
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(`
-
Welcome, ${user.name || user.username}!
-
Email: ${user.email}
-
First name: ${user.given_name}
-
Last name: ${user.family_name}
- Sign out
- `);
-
- } else if (url.pathname === '/login') {
-```
+## Call Business Logic With the Token
-## Complete index.js
+A real backend keeps authentication in its own module instead of scattering it across business logic. Update `index.mjs` so the rest of the app asks for a token only when it needs one, instead of handling `getAccessToken()` and the `Authorization` header directly:
-Here is the full file for reference:
+```js title="index.mjs" showLineNumbers
+import { ThunderIDNodeClient } from '@thunderid/node';
-```js title="index.js" showLineNumbers
-const http = require('http');
-const { URL } = require('url');
-const { randomUUID } = require('crypto');
-const { ThunderIDNodeClient } = require('@thunderid/node');
+const client = new ThunderIDNodeClient();
-const PORT = 3000;
-const SESSION_COOKIE = 'tid_session';
+await client.initialize({
+ baseUrl: 'https://localhost:8090',
+ clientId: '',
+ clientSecret: '',
+ grantType: 'client_credentials',
+});
-const auth = new ThunderIDNodeClient();
+async function getStock(sku) {
+ const accessToken = await client.getAccessToken();
-function getSessionId(req) {
- const cookieHeader = req.headers.cookie ?? '';
- for (const part of cookieHeader.split(';')) {
- const [name, value] = part.trim().split('=');
- if (name === SESSION_COOKIE) return decodeURIComponent(value);
- }
- return null;
-}
+ // A real backend would attach this as `Authorization: Bearer ${accessToken}`
+ // on a request to a separate inventory service. This quickstart just proves
+ // the token was obtained before answering.
+ console.log(`Requesting ${sku} with a valid access token`);
-async function main() {
- await auth.initialize({
- clientId: '',
- clientSecret: '',
- baseUrl: 'https://localhost:8090',
- afterSignInUrl: 'http://localhost:3000/callback',
- afterSignOutUrl: 'http://localhost:3000',
- });
-
- const server = http.createServer(async (req, res) => {
- const url = new URL(req.url, `http://localhost:${PORT}`);
-
- try {
- if (url.pathname === '/') {
- const sessionId = getSessionId(req);
- const signedIn = sessionId && (await auth.isSignedIn(sessionId));
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(signedIn
- ? 'View profile | Sign out'
- : 'Sign in'
- );
-
- } else if (url.pathname === '/profile') {
- const sessionId = getSessionId(req);
- if (!sessionId || !(await auth.isSignedIn(sessionId))) {
- res.writeHead(302, { Location: '/login' });
- return res.end();
- }
- const user = await auth.getUser(sessionId);
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(`
-
Welcome, ${user.name || user.username}!
-
Email: ${user.email}
-
First name: ${user.given_name}
-
Last name: ${user.family_name}
- Sign out
- `);
-
- } else if (url.pathname === '/login') {
- let sessionId = getSessionId(req);
- const extraHeaders = {};
- if (!sessionId) {
- sessionId = randomUUID();
- extraHeaders['Set-Cookie'] =
- `${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
- }
- await auth.signIn((authUrl) => {
- res.writeHead(302, { ...extraHeaders, Location: authUrl });
- res.end();
- }, sessionId);
-
- } else if (url.pathname === '/callback') {
- const code = url.searchParams.get('code');
- const state = url.searchParams.get('state');
- const sessionState = url.searchParams.get('session_state');
- const sessionId = getSessionId(req);
-
- if (!sessionId || !code || !state) {
- res.writeHead(400);
- return res.end('Bad request');
- }
-
- await auth.signIn(() => {}, sessionId, code, sessionState, state);
- res.writeHead(302, { Location: '/profile' });
- res.end();
-
- } else if (url.pathname === '/logout') {
- const sessionId = getSessionId(req);
- if (!sessionId) {
- res.writeHead(302, { Location: '/' });
- return res.end();
- }
- const signOutUrl = await auth.signOut(sessionId);
- res.writeHead(302, {
- Location: signOutUrl,
- 'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
- });
- res.end();
-
- } else {
- res.writeHead(404);
- res.end('Not found');
- }
- } catch {
- res.writeHead(500);
- res.end('Internal server error');
- }
- });
-
- server.listen(PORT, () => {
- console.log(`Server running on http://localhost:${PORT}`);
- });
+ return { sku, inStock: true };
}
-main();
+const item = await getStock('SKU-100');
+console.log(item);
```
-## Run Your App
+## Run Your Service
-Start the server:
+Start the script:
- node index.js
+ node index.mjs
- yarn node index.js
+ yarn node index.mjs
- pnpm node index.js
+ pnpm node index.mjs
-Open [http://localhost:3000](http://localhost:3000).
-
-:::note Test credentials
-You'll need a user to sign in with. If you haven't created one yet, open , navigate to **Users**, and add a test user with an email and password.
-:::
-
:::tip Success
-You should see the sign-in link. Click it to be redirected to the -hosted sign-in page. After authenticating with your test user, you'll return to the `/profile` route with your user profile displayed.
+The script authenticates once, prints the scope it received, then calls `getStock()` and prints the result before exiting.
:::
@@ -418,11 +206,11 @@ You should see the sign-in link. Click it to be redirected to the
-
+
diff --git a/docs/content/getting-started/connect-your-application/prompts/android/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/android/redirect-based.txt
new file mode 100644
index 0000000000..2affef1349
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/android/redirect-based.txt
@@ -0,0 +1,36 @@
+# Integrate {{productName}} Authentication in Android Application
+
+## Context
+I have an Android application (Kotlin, Jetpack Compose) and I want to integrate {{productName}}'s authentication system using the {{productName}} Android SDK, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `dev.thunderid:compose` Gradle dependency for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` composables
+- Guard content with the `SignedIn`/`SignedOut` composables
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` composable
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: dev.thunderid:compose
+
+## IMPORTANT Configuration Rules
+- Add the {{productName}} Maven repository (`https://maven.thunderid.dev/releases`) to `settings.gradle.kts` before adding the dependency
+- Wrap the content passed to `setContent` with the `ThunderIDProvider` composable, configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via `LocalThunderID.current`, not a separate hook or singleton
+- Required scopes: at minimum `"openid"`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Android Studio project using the Empty Activity template with Kotlin and Jetpack Compose
+2. Add the {{productName}} Maven repository to `settings.gradle.kts`
+3. Add the `dev.thunderid:compose` dependency to `build.gradle.kts`
+4. Wrap your `setContent` content with `ThunderIDProvider`, configured with `baseUrl`, `scopes`, and `applicationId`
+5. Build a root composable that checks `thunder.isInitialized` and renders a `SignedIn`/fallback split
+6. Build an auth screen using the `SignIn` composable, passing the `applicationId`
+7. Build a home screen that reads `LocalThunderID.current.user` to display the signed-in user's name and email, with a `SignOutButton`
+8. Run the app on an API 24+ emulator or device
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} Android SDK for Jetpack Compose.
diff --git a/docs/content/getting-started/connect-your-application/prompts/browser/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/browser/embedded.txt
new file mode 100644
index 0000000000..b3a5777548
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/browser/embedded.txt
@@ -0,0 +1,36 @@
+# Integrate {{productName}} Authentication in Vanilla JavaScript Application (Custom UI Mode)
+
+## Context
+I have a vanilla JavaScript application and I want to integrate {{productName}}'s authentication system using the ThunderID Browser SDK's embedded flow functions, rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/browser SDK for authentication
+- Use `executeEmbeddedSignInFlow` (re-exported by @thunderid/browser from the core @thunderid/javascript client) to drive the sign-in flow directly, instead of `ThunderIDBrowserClient.signIn()` (which is redirect-only)
+- Render your own sign-in form driven by the flow response's `components` array
+- Display signed-in user's profile information
+- Handle authentication state
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/browser
+
+## IMPORTANT Configuration Rules
+- `@thunderid/browser` has NO bundled ``-style DOM component — it is framework-agnostic with no rendering layer. Build the form yourself from the flow response
+- Call `executeEmbeddedSignInFlow({applicationId, baseUrl, flowType: 'AUTHENTICATION'})` to start the flow
+- Each step returns `components` describing the fields to render next (e.g. identifier, password, OTP); render inputs for those fields, then call `executeEmbeddedSignInFlow` again with the same `executionId` and the user's submitted `inputs` to advance the flow
+- Repeat until the flow response indicates completion (tokens/session issued)
+- NEVER call `ThunderIDBrowserClient.signIn()` or navigate to a {{productName}}-hosted URL in this mode — that redirects the browser instead of using the embedded flow
+- Still use `ThunderIDBrowserClient.initialize(config)` for session/token management (`isSignedIn()`, `getUser()`, `getAccessToken()`, `signOut()`) once the embedded flow completes
+
+## Implementation Steps
+1. Create a vanilla JS app using Vite by running: `npm create vite@latest js-demo -- --template vanilla`
+2. Navigate into the project directory: `cd js-demo`
+3. Install dependencies: `npm install`
+4. Install @thunderid/browser package: `npm install @thunderid/browser`
+5. Create src/auth.js to initialize ThunderIDBrowserClient with applicationId and baseUrl
+6. Create a sign-in form in src/main.js; on submit, call `executeEmbeddedSignInFlow` and render whatever `components` the response specifies for the next step
+7. Once the flow completes, use `isSignedIn()`/`getUser()` to show the signed-in profile, and `signOut()` for sign-out
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code for {{productName}} authentication using the ThunderID Browser SDK's embedded flow functions, with a custom sign-in form (not a redirect).
diff --git a/docs/content/getting-started/connect-your-application/prompts/browser/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/browser/redirect-based.txt
new file mode 100644
index 0000000000..f5022077b9
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/browser/redirect-based.txt
@@ -0,0 +1,57 @@
+# Integrate {{productName}} Authentication in Vanilla JavaScript Application
+
+## Context
+I have a vanilla JavaScript application and I want to integrate {{productName}}'s authentication system using the ThunderID Browser SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/browser SDK for authentication
+- Configure {{productName}}-hosted login pages
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+- Handle authentication state
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/browser
+
+## IMPORTANT Configuration Rules
+- Create a ThunderIDBrowserClient instance and call initialize() with a config object
+- Required config properties: `clientId` and `baseUrl`
+- Optional config properties: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array), `storage`
+- Storage options: 'sessionStorage' (default), 'localStorage', 'browserMemory'
+- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`
+
+## Available SDK APIs
+
+### ThunderIDBrowserClient
+- `initialize(config)` - Initialize the client with configuration
+- `signIn()` - Redirect to {{productName}} sign-in page
+- `signOut()` - Sign out and clear session
+- `isSignedIn()` - Check if user is signed in (returns Promise)
+- `getUser()` - Get authenticated user profile (returns Promise)
+- `getAccessToken()` - Get current access token
+- `getIdToken()` - Get ID token
+- `getDecodedIdToken()` - Get decoded ID token claims
+- `httpRequest(config)` - Make authenticated HTTP request
+- `on(hook, callback)` - Register event callbacks (Hooks.SignIn, Hooks.SignOut, etc.)
+
+### User Object Properties
+- `displayName` - User's display name
+- `username` - Username
+- `email` - Email address
+- `given_name` - First name
+- `family_name` - Last name
+- `picture` - Profile picture URL
+
+## Implementation Steps
+1. Create a vanilla JS app using Vite by running: `npm create vite@latest js-demo -- --template vanilla`
+2. Navigate into the project directory: `cd js-demo`
+3. Install dependencies: `npm install`
+4. Install @thunderid/browser package: `npm install @thunderid/browser`
+5. Create src/auth.js to initialize ThunderIDBrowserClient with clientId and baseUrl
+6. Update src/main.js to check isSignedIn(), show sign-in button or user profile
+7. Add event listeners for sign-in and sign-out buttons
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID Browser SDK.
diff --git a/docs/content/getting-started/connect-your-application/prompts/express/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/express/embedded.txt
new file mode 100644
index 0000000000..9ca1b63f34
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/express/embedded.txt
@@ -0,0 +1,47 @@
+# Integrate {{productName}} Authentication in an Express Application (Custom UI / Embedded Mode)
+
+## Context
+I have an Express application and I want to integrate {{productName}} authentication using the ThunderID Express SDK in embedded (app-native) mode, rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use `@thunderid/express` in a Node.js + Express application
+- Use `cookie-parser` and `express.json()` middleware
+- Configure ThunderID middleware with `baseUrl`, `applicationId`, and `mode: 'embedded'`
+- Add a `/flow/sign-in` route using `handleFlow()` that proxies each step of the flow to the Flow Execution API
+- Serve a `/login` page with a plain HTML form that POSTs to `/flow/sign-in` and re-renders whatever fields the flow response asks for next
+- Add `/logout` with `handleSignOut()`
+- Protect a route (`/protected`) using `protect((res) => res.redirect('/login'))`
+- Add a `/me` route that returns the authenticated user profile as JSON
+- Keep the code minimal, production-lean, and fully runnable
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Sign-In Page**: `/login`
+- **Flow Route**: `/flow/sign-in`
+- **Logout Callback Route**: `/logout`
+- **SDK**: @thunderid/express
+
+## Important Rules
+- Use CommonJS syntax (`require`) in `index.js`
+- `@thunderid/express` has NO bundled sign-in UI component — this is a server-side SDK. `mode: 'embedded'` only changes what `handleFlow()` proxies to; you must write the actual HTML/JS form yourself
+- Set `mode: 'embedded'` in the `thunderID({...})` middleware config (defaults to `'redirect'` if omitted)
+- `handleFlow()` calls the Flow Execution API on each POST and returns the flow's JSON step (`executionId`, `challengeToken`, `authId`, `components`) — render whatever `components` the response contains as form fields, and POST the user's input back to the same route with the returned `executionId`
+- Do not invent unsupported SDK APIs or custom wrappers
+- Do not use `clientId`/`clientSecret` in this mode — embedded mode is driven by `applicationId`, not an OAuth2 client
+
+## Implementation Steps
+1. Create a new project and install `express` and `cookie-parser`
+2. Install `@thunderid/express`
+3. Add `index.js` with `thunderID({ applicationId, baseUrl, mode: 'embedded' })` middleware
+4. Add `/login` route serving a minimal HTML form, and `/flow/sign-in` using `handleFlow()`
+5. Add `/logout`, `/protected`, and `/me` routes
+6. Start the server with `node index.js`
+7. Validate the flow by opening `/login`, submitting the form, then `/protected` and `/me`
+
+Please provide:
+- The exact terminal commands
+- A complete `index.js` file
+- A minimal HTML sign-in form driven by the flow's `components` response (not a redirect)
+- A short verification checklist for sign-in, sign-out, and protected route behavior
diff --git a/docs/content/getting-started/connect-your-application/prompts/express/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/express/redirect-based.txt
new file mode 100644
index 0000000000..e2aeaea4d6
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/express/redirect-based.txt
@@ -0,0 +1,42 @@
+# Integrate {{productName}} Authentication in an Express Application (Inbuilt Mode)
+
+## Context
+I have an Express application and I want to integrate {{productName}} authentication using the ThunderID Express SDK with {{productName}}-hosted sign-in pages.
+
+## Requirements
+- Use `@thunderid/express` in a Node.js + Express application
+- Use `cookie-parser` and `express.json()` middleware
+- Configure ThunderID middleware with `baseUrl`, `clientId`, `clientSecret`, `afterSignInUrl`, and `afterSignOutUrl`
+- Implement `/login` with `handleSignIn()` and `/logout` with `handleSignOut()`
+- Protect a route (`/protected`) using `protect((res) => res.redirect('/login'))`
+- Add a `/me` route that returns the authenticated user profile as JSON
+- Keep the code minimal, production-lean, and fully runnable
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Client Secret**: ``
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Login Callback Route**: `/login`
+- **Logout Callback Route**: `/logout`
+- **SDK**: @thunderid/express
+
+## Important Rules
+- Use CommonJS syntax (`require`) in `index.js`
+- Use these SDK imports exactly: `thunderID`, `handleSignIn`, `handleSignOut`, `protect`
+- Ensure redirect URL alignment: the app callback URL must be `http://localhost:3000/login`, and the post-logout redirect URL must be `http://localhost:3000/logout`
+- Do not invent unsupported SDK APIs or custom wrappers
+- Keep route names and behavior exactly as specified
+
+## Implementation Steps
+1. Create a new project and install `express` and `cookie-parser`
+2. Install `@thunderid/express`
+3. Add `index.js` with ThunderID middleware and auth routes
+4. Add `/protected` and `/me` routes with `protect()`
+5. Start the server with `node index.js`
+6. Validate the flow by opening `/protected`, then `/me`
+
+Please provide:
+- The exact terminal commands
+- A complete `index.js` file
+- A short verification checklist for sign-in, sign-out, and protected route behavior
diff --git a/docs/content/getting-started/connect-your-application/prompts/flutter/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/flutter/redirect-based.txt
new file mode 100644
index 0000000000..bb4da78d3e
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/flutter/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in Flutter Application
+
+## Context
+I have a Flutter application (Dart) and I want to integrate {{productName}}'s authentication system using the `thunderid_flutter` package, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `thunderid_flutter` package for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` widgets
+- Route between an auth screen and a home screen based on `thunder.isSignedIn`
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` widget
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: thunderid_flutter
+
+## IMPORTANT Configuration Rules
+- Wrap your root widget with `ThunderIDProvider`, configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via `ThunderIDProvider.of(context)`, not a separate hook or singleton
+- Required scopes: at minimum `'openid'`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Flutter project: `flutter create my_app`
+2. Add `thunderid_flutter` to `pubspec.yaml` and run `flutter pub get`
+3. Wrap your root widget with `ThunderIDProvider` in `lib/main.dart`, configured with `baseUrl`, `scopes`, and `applicationId`
+4. Build a root screen that checks `thunder.initialized`/`thunder.isLoading` and routes to an auth or home screen based on `thunder.isSignedIn`
+5. Build an auth screen using the `SignIn`/`SignUp` widgets, passing the `applicationId`
+6. Build a home screen that reads `thunder.user` to display the signed-in user's name and email, with a `SignOutButton`
+7. Run the app on an iOS simulator or Android device: `flutter run`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the `thunderid_flutter` package.
diff --git a/docs/content/getting-started/connect-your-application/prompts/ios/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/ios/redirect-based.txt
new file mode 100644
index 0000000000..755f828913
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/ios/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in iOS Application
+
+## Context
+I have an iOS application (Swift, SwiftUI) and I want to integrate {{productName}}'s authentication system using the {{productName}} iOS SDK, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `ThunderIDSwiftUI` Swift package for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` views
+- Guard content with the `SignedIn`/`SignedOut` views
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` view
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: ThunderIDSwiftUI (Swift Package Manager) or ThunderIDSwiftUI CocoaPod
+
+## IMPORTANT Configuration Rules
+- Apply the `.thunderIDProvider(config:)` modifier to the root view in your app's entry point (the type conforming to `App`), configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via an injected `ThunderIDState` `@EnvironmentObject`, not a separate hook or singleton
+- Required scopes: at minimum `"openid"`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Xcode project using the iOS > App template with SwiftUI and Swift
+2. Add the `ThunderIDSwiftUI` package via File > Add Package Dependencies (or the `ThunderIDSwiftUI` CocoaPod)
+3. Apply `.thunderIDProvider(config:)` to your root view, configured with `baseUrl`, `scopes`, and `applicationId`
+4. Build a root view that checks `state.isInitialized` and renders a `SignedIn`/fallback split
+5. Build an auth view using the `SignIn` view, passing the `applicationId`
+6. Build a home view that reads `state.user` to display the signed-in user's name and email, with a `SignOutButton`
+7. Run the app on an iOS 16+ simulator or device
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} iOS SDK for SwiftUI.
diff --git a/docs/content/getting-started/connect-your-application/prompts/nextjs/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/nextjs/embedded.txt
new file mode 100644
index 0000000000..cb7860e1cc
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/nextjs/embedded.txt
@@ -0,0 +1,41 @@
+# Integrate {{productName}} Authentication in Next.js Application (Custom UI Mode)
+
+## Context
+I have a Next.js application (App Router) and I want to integrate {{productName}}'s authentication system using the {{productName}} Next.js SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/nextjs SDK for authentication
+- Render sign-in on a custom route (not a redirect to {{productName}}-hosted pages)
+- Use the App Router (not Pages Router)
+- Implement sign-in and sign-out with the SDK's own components
+- Add middleware for route protection and automatic token refresh
+- Display signed-in user's profile information
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Route**: /signin
+- **SDK**: @thunderid/nextjs
+
+## IMPORTANT Configuration Rules
+- Use environment variables for configuration (NOT props on the provider)
+- Required env vars: NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+- Import ThunderIDProvider from '@thunderid/nextjs/server' (NOT from '@thunderid/nextjs')
+- Import middleware utilities from '@thunderid/nextjs/server'
+- Import UI components (`SignedIn`, `SignedOut`, `SignIn`, `SignOutButton`, `UserDropdown`) from '@thunderid/nextjs'
+- The `` component drives the Flow Execution API directly (not an OAuth redirect) — render it on your custom `app/signin/page.tsx`
+- `` accepts an `onSuccess` callback (e.g. `router.push('/')`) and an `onError` callback
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Next.js app: npx create-next-app@latest nextjs-demo
+2. Navigate into the project: cd nextjs-demo
+3. Install @thunderid/nextjs: npm install @thunderid/nextjs
+4. Create .env.local with NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+5. Wrap root layout with from '@thunderid/nextjs/server'
+6. Create app/signin/page.tsx rendering ` router.push('/')} />` and `` for the already-authenticated case
+7. Create proxy.ts with thunderIDProxy and createRouteMatcher from '@thunderid/nextjs/server' for route protection
+8. Add SignedIn, UserDropdown, SignedOut, SignOutButton components to other pages
+9. Run: npm run dev
+
+Please provide complete, working code for {{productName}} authentication using the {{productName}} Next.js SDK, with a custom sign-in page rendering `` instead of a hosted-page redirect.
diff --git a/docs/content/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt
new file mode 100644
index 0000000000..4b5badc202
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt
@@ -0,0 +1,37 @@
+# Integrate {{productName}} Authentication in Next.js Application
+
+## Context
+I have a Next.js application (App Router) and I want to integrate {{productName}}'s authentication system using the {{productName}} Next.js SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/nextjs SDK for authentication
+- Configure {{productName}}-hosted login pages (not custom/embedded)
+- Use the App Router (not Pages Router)
+- Implement sign-in and sign-out with prebuilt components
+- Add middleware for route protection and automatic token refresh
+- Display signed-in user's profile information
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/nextjs
+
+## IMPORTANT Configuration Rules
+- Use environment variables for configuration (NOT props on the provider)
+- Required env vars: NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+- Import ThunderIDProvider from '@thunderid/nextjs/server' (NOT from '@thunderid/nextjs')
+- Import middleware utilities from '@thunderid/nextjs/server'
+- Import UI components from '@thunderid/nextjs'
+- The ThunderIDProvider handles the OAuth callback automatically — no manual callback route is needed
+
+## Implementation Steps
+1. Create a Next.js app: npx create-next-app@latest nextjs-demo
+2. Navigate into the project: cd nextjs-demo
+3. Install @thunderid/nextjs: npm install @thunderid/nextjs
+4. Create .env.local with NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+5. Wrap root layout with from '@thunderid/nextjs/server'
+6. Create proxy.ts with thunderIDProxy and createRouteMatcher from '@thunderid/nextjs/server' for route protection
+7. Add SignedIn, UserDropdown, SignedOut, SignInButton components to pages
+8. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} Next.js SDK.
diff --git a/docs/content/getting-started/connect-your-application/prompts/node/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/node/embedded.txt
new file mode 100644
index 0000000000..5b4a8e0c31
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/node/embedded.txt
@@ -0,0 +1,46 @@
+# Integrate {{productName}} Authentication in a Node.js Application (Custom UI / Embedded Mode)
+
+## Context
+I have a Node.js application and I want to integrate {{productName}} authentication using the @thunderid/node SDK's embedded flow functions and the built-in http module — no framework required — rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/node SDK with the built-in Node.js http module
+- Use `executeEmbeddedSignInFlow` (re-exported by @thunderid/node from the core @thunderid/javascript client) to drive the sign-in flow directly, instead of `ThunderIDNodeClient.signIn()` (which is redirect-only)
+- Implement a `/login` route that serves a minimal HTML form
+- Implement a `/flow/sign-in` route that calls `executeEmbeddedSignInFlow` on each POST and returns/renders whatever `components` the flow response asks for next
+- Implement a `/logout` route to sign out and clear the session cookie
+- Protect the `/profile` route using `isSignedIn()` and display user info with `getUser()`
+- Manage sessions using a session ID stored in an HttpOnly cookie
+- Keep code minimal and fully runnable with CommonJS `require()`
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Sign-In Page**: /login
+- **Flow Route**: /flow/sign-in
+- **SDK**: @thunderid/node
+
+## Important Rules
+- Use CommonJS syntax (require) in index.js
+- `ThunderIDNodeClient` itself only implements the redirect flow — for embedded mode, call `executeEmbeddedSignInFlow({applicationId, flowType: 'AUTHENTICATION', ...})` directly, imported from `@thunderid/node` (or `@thunderid/javascript`)
+- @thunderid/node has NO bundled sign-in UI — write the HTML/JS form yourself, rendering whatever fields the flow response's `components` array specifies
+- Each flow step returns an `executionId`; POST the next step's user input back to `/flow/sign-in` along with that `executionId` until the flow completes
+- Store the session ID in a cookie named 'tid_session' with HttpOnly and SameSite=Lax flags
+- Use randomUUID() from the built-in 'crypto' module to generate session IDs
+- Use isSignedIn(sessionId) to guard protected routes
+- Use getUser(sessionId) to retrieve the authenticated user profile
+- Use signOut(sessionId) to clear the local session
+
+## Implementation Steps
+1. Create a new project: mkdir my-node-app && cd my-node-app && npm init -y
+2. Install @thunderid/node: npm install @thunderid/node
+3. Create index.js importing `executeEmbeddedSignInFlow` from @thunderid/node
+4. Add /login route serving a minimal HTML form
+5. Add /flow/sign-in route: call executeEmbeddedSignInFlow with the applicationId and the user's submitted inputs, and set the session cookie once the flow completes
+6. Add /logout route: clear the session cookie
+7. Add / route: show sign-in or profile link based on isSignedIn()
+8. Add /profile route: guard with isSignedIn(), display user info from getUser()
+9. Start the server: node index.js
+
+Please provide a complete, working index.js file with all routes, driven by the embedded flow functions (not a redirect).
diff --git a/docs/content/getting-started/connect-your-application/prompts/node/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/node/redirect-based.txt
new file mode 100644
index 0000000000..2e178b0b9a
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/node/redirect-based.txt
@@ -0,0 +1,45 @@
+# Integrate {{productName}} Authentication in a Node.js Application
+
+## Context
+I have a Node.js application and I want to integrate {{productName}} authentication using the @thunderid/node SDK and the built-in http module — no framework required.
+
+## Requirements
+- Use @thunderid/node SDK with the built-in Node.js http module
+- Initialize ThunderIDNodeClient with clientId, clientSecret, baseUrl, afterSignInUrl, afterSignOutUrl
+- Implement /login route to start the sign-in flow (redirects to {{productName}})
+- Implement /callback route to handle the OAuth authorization code exchange
+- Implement /logout route to sign out and clear the session cookie
+- Protect the /profile route using isSignedIn() and display user info with getUser()
+- Manage sessions using a session ID stored in an HttpOnly cookie
+- Keep code minimal and fully runnable with CommonJS require()
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Client Secret**: ``
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Callback Route**: /callback
+- **SDK**: @thunderid/node
+
+## Important Rules
+- Use CommonJS syntax (require) in index.js
+- Initialize ThunderIDNodeClient with auth.initialize({...}) before starting the server
+- signIn() works in two phases: first call redirects the user (authUrlCallback), second call (with code+state) exchanges the token
+- Store the session ID in a cookie named 'tid_session' with HttpOnly and SameSite=Lax flags
+- Use randomUUID() from the built-in 'crypto' module to generate session IDs
+- Use isSignedIn(sessionId) to guard protected routes
+- Use getUser(sessionId) to retrieve the authenticated user profile
+- Use signOut(sessionId) to get the OIDC end-session URL, then clear the local cookie and redirect
+
+## Implementation Steps
+1. Create a new project: mkdir my-node-app && cd my-node-app && npm init -y
+2. Install @thunderid/node: npm install @thunderid/node
+3. Create index.js with ThunderIDNodeClient initialization
+4. Add /login route: generate session ID cookie and redirect to {{productName}} auth URL
+5. Add /callback route: exchange authorization code for tokens using signIn()
+6. Add /logout route: call signOut() to get end-session URL, clear cookie, redirect
+7. Add / route: show sign-in or profile link based on isSignedIn()
+8. Add /profile route: guard with isSignedIn(), display user info from getUser()
+9. Start the server: node index.js
+
+Please provide a complete, working index.js file with all routes and the ThunderIDNodeClient wired up correctly.
diff --git a/docs/content/getting-started/connect-your-application/prompts/nuxt/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/nuxt/embedded.txt
new file mode 100644
index 0000000000..2ef19c0dae
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/nuxt/embedded.txt
@@ -0,0 +1,44 @@
+# Integrate {{productName}} Authentication in Nuxt 3 Application (Custom UI Mode)
+
+## Context
+I have a Nuxt 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/nuxt module with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/nuxt module for authentication
+- Register the module in nuxt.config.ts
+- Configure via environment variables (no inline config)
+- Wrap app.vue content with
+- Render sign-in on a custom page using the module's own SignIn component (not a redirect to {{productName}}-hosted pages)
+- Display signed-in user's profile information
+- Optionally protect pages with the built-in thunderIDMiddleware
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Page**: /signin (custom page)
+- **SDK**: @thunderid/nuxt
+
+## IMPORTANT Configuration Rules
+- Add '@thunderid/nuxt' to the modules array in nuxt.config.ts — no other config needed there
+- All configuration is read from environment variables with NUXT_PUBLIC_ prefix for public values
+- Required env vars: NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SESSION_SECRET (without the NUXT_PUBLIC_ prefix)
+- Wrap with in app.vue
+- All components (`SignedIn`, `SignedOut`, `SignIn`, `SignOutButton`, `User`) and composables are auto-imported — no manual import needed
+- The `` component mirrors the Vue SDK's `SignIn`/`BaseSignIn` and drives the embedded (app-native) Flow Execution API directly — it replaces `window.location` navigation with Nuxt's `navigateTo` internally, so no manual redirect handling is needed
+- Render `` inside `` on pages/signin.vue
+- Protect pages by adding definePageMeta({ middleware: ['thunderIDMiddleware'] })
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Nuxt 3 app: npx nuxi@latest init my-nuxt-app
+2. Navigate into the project: cd my-nuxt-app && npm install
+3. Install @thunderid/nuxt: npm install @thunderid/nuxt
+4. Add '@thunderid/nuxt' to modules in nuxt.config.ts
+5. Create .env with NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SESSION_SECRET
+6. Wrap with in app.vue
+7. Create pages/signin.vue rendering ``
+8. Create pages/index.vue with SignedIn, SignOutButton, and User components
+9. Optionally add definePageMeta({ middleware: ['thunderIDMiddleware'] }) to protected pages
+10. Run: npm run dev
+
+Please provide complete, working code for {{productName}} authentication using the @thunderid/nuxt module, with a custom sign-in page rendering `` instead of a hosted-page redirect.
diff --git a/docs/content/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt
new file mode 100644
index 0000000000..dff4a6bed7
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt
@@ -0,0 +1,42 @@
+# Integrate {{productName}} Authentication in Nuxt 3 Application
+
+## Context
+I have a Nuxt 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/nuxt module with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/nuxt module for authentication
+- Register the module in nuxt.config.ts
+- Configure via environment variables (no inline config)
+- Wrap app.vue content with
+- Implement sign-in and sign-out with auto-imported components
+- Display signed-in user's profile information
+- Optionally protect pages with the built-in thunderIDMiddleware
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Callback URL**: http://localhost:3000/api/auth/callback (auto-registered by the module)
+- **SDK**: @thunderid/nuxt
+
+## IMPORTANT Configuration Rules
+- Add '@thunderid/nuxt' to the modules array in nuxt.config.ts — no other config needed there
+- All configuration is read from environment variables with NUXT_PUBLIC_ prefix for public values
+- Required env vars: NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET
+- THUNDERID_CLIENT_SECRET and THUNDERID_SESSION_SECRET must NOT have the NUXT_PUBLIC_ prefix
+- The /api/auth/callback route is auto-registered by the module — do NOT create it manually
+- Wrap with in app.vue
+- All components (SignedIn, SignedOut, SignInButton, SignOutButton, User) and composables are auto-imported
+- Protect pages by adding definePageMeta({ middleware: ['thunderIDMiddleware'] })
+
+## Implementation Steps
+1. Create a Nuxt 3 app: npx nuxi@latest init my-nuxt-app
+2. Navigate into the project: cd my-nuxt-app && npm install
+3. Install @thunderid/nuxt: npm install @thunderid/nuxt
+4. Add '@thunderid/nuxt' to modules in nuxt.config.ts
+5. Create .env with NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET
+6. Wrap with in app.vue
+7. Create pages/index.vue with SignInButton, SignOutButton, SignedIn, SignedOut, and User components
+8. Optionally add definePageMeta({ middleware: ['thunderIDMiddleware'] }) to protected pages
+9. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the @thunderid/nuxt module.
diff --git a/docs/content/getting-started/connect-your-application/prompts/react/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/react/embedded.txt
new file mode 100644
index 0000000000..af5c0ed6d2
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/react/embedded.txt
@@ -0,0 +1,45 @@
+# Integrate {{productName}} Authentication in React Application (Custom UI Mode)
+
+## Context
+I have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/react SDK for authentication
+- Render sign-in on a custom route (not a redirect to {{productName}}-hosted pages)
+- Use react-router for routing to the custom sign-in page
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+- Handle authentication state automatically
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In URL**: http://localhost:5173/signin (custom route)
+- **SDK**: @thunderid/react
+- **Router**: react-router
+
+## IMPORTANT Configuration Rules
+- DO NOT create a separate config object - pass all configuration as individual props directly to
+- Required props: `baseUrl`, `signInUrl`, and `applicationId`
+- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes`
+- Example:
+- The `` component drives the Flow Execution API directly (not a redirect) — render it on your custom sign-in route
+- `` accepts `onSuccess`/`onError` callbacks, and optionally a render-prop `children` function receiving `{components, onSubmit, isLoading, error, isInitialized}` for a fully custom form
+- Access auth state via `useThunderID()` (ONLY hook available: exposes `isSignedIn`, `user`, `signIn`, `signOut`, `signUp`) — no other hooks like `useAuth`/`useSession`/`useUser` exist
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`
+2. Navigate into the project directory: `cd my-react-app`
+3. Install dependencies: `npm install`
+4. Install react-router package: `npm install react-router`
+5. Install @thunderid/react package: `npm install @thunderid/react`
+6. Wrap your app with and configure baseUrl, signInUrl, and applicationId
+7. Set up React Router with BrowserRouter, create a /signin route rendering ``, and use , , , , and for the rest of the authentication UI
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code with:
+- Proper routing configuration
+- A custom sign-in page rendering `` (not a redirect)
+- Proper integration with the ThunderID React SDK
+- Use of the `useThunderID` hook if programmatic access to auth state is needed
diff --git a/docs/content/getting-started/connect-your-application/prompts/react/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/react/redirect-based.txt
new file mode 100644
index 0000000000..8c4958ff6a
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/react/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in React Application (Inbuilt Mode)
+
+## Context
+I have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/react SDK for authentication
+- Configure {{productName}}-hosted login, registration, and account management UIs
+- Implement sign-in and sign-out functionality using prebuilt components
+- Display signed-in user's profile information
+- Handle authentication state automatically
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/react
+
+## IMPORTANT Configuration Rules
+- DO NOT create a separate config object - pass all configuration as individual props directly to
+- Required props: `clientId` and `baseUrl`
+- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array)
+- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`
+- Example:
+
+## Implementation Steps
+1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`
+2. Navigate into the project directory: `cd my-react-app`
+3. Install dependencies: `npm install`
+4. Install @thunderid/react package: `npm install @thunderid/react`
+5. Wrap your app with and configure clientId and baseUrl
+6. Build with ThunderID components: use , , , and to control what signed-in and signed-out users see
+7. Run the development server: `npm run dev`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID React SDK.
diff --git a/docs/content/getting-started/connect-your-application/prompts/vue/embedded.txt b/docs/content/getting-started/connect-your-application/prompts/vue/embedded.txt
new file mode 100644
index 0000000000..17e76530ec
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/vue/embedded.txt
@@ -0,0 +1,40 @@
+# Integrate {{productName}} Authentication in Vue 3 Application (Custom UI Mode)
+
+## Context
+I have a Vue 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/vue SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/vue SDK for authentication
+- Register the ThunderIDPlugin in main.js
+- Render sign-in on a custom route using the SDK's own SignIn component (not a redirect to {{productName}}-hosted pages)
+- Use vue-router for routing to the custom sign-in page
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Route**: /signin (custom route)
+- **SDK**: @thunderid/vue
+
+## IMPORTANT Configuration Rules
+- Register ThunderIDPlugin via `app.use(ThunderIDPlugin)` in src/main.js
+- Import ThunderIDPlugin from '@thunderid/vue'
+- Wrap the app root in App.vue with ``
+- Pass configuration as kebab-case attributes: `application-id` and `base-url`
+- The `` component drives the Flow Execution API directly (not a redirect) — render it on your custom /signin route, inside ``
+- `` emits `@success`/`@error` events
+- Access auth state via the `useThunderID()` composable (`isSignedIn`, `user`, `signIn`, `signOut`, `signUp`)
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Vue 3 app: npm create vite@latest my-vue-app -- --template vue
+2. Navigate into the project: cd my-vue-app && npm install
+3. Install @thunderid/vue and vue-router: npm install @thunderid/vue vue-router
+4. Register ThunderIDPlugin in src/main.js with app.use(ThunderIDPlugin)
+5. Wrap app content with in src/App.vue
+6. Set up vue-router with a /signin route rendering ``
+7. Use , , and for the rest of the authentication UI
+8. Run: npm run dev
+
+Please provide complete, working code with proper routing, a custom sign-in page rendering `` (not a redirect), and use of the `useThunderID()` composable where programmatic access to auth state is needed.
diff --git a/docs/content/getting-started/connect-your-application/prompts/vue/redirect-based.txt b/docs/content/getting-started/connect-your-application/prompts/vue/redirect-based.txt
new file mode 100644
index 0000000000..80600c78e2
--- /dev/null
+++ b/docs/content/getting-started/connect-your-application/prompts/vue/redirect-based.txt
@@ -0,0 +1,35 @@
+# Integrate {{productName}} Authentication in Vue 3 Application
+
+## Context
+I have a Vue 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/vue SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/vue SDK for authentication
+- Register the ThunderIDPlugin in main.js
+- Wrap the app with ThunderIDProvider in App.vue
+- Implement sign-in and sign-out with prebuilt components
+- Display signed-in user's profile using the UserDropdown component
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/vue
+
+## IMPORTANT Configuration Rules
+- Register ThunderIDPlugin via app.use(ThunderIDPlugin) in src/main.js
+- Import ThunderIDPlugin from '@thunderid/vue'
+- Wrap the app root in App.vue with
+- Pass configuration as kebab-case attributes: `client-id` and `base-url`
+- Import UI components (SignInButton, SignOutButton, SignedIn, SignedOut, UserDropdown) from '@thunderid/vue'
+- Use inside to display the signed-in user's profile and sign-out option
+
+## Implementation Steps
+1. Create a Vue 3 app: npm create vite@latest my-vue-app -- --template vue
+2. Navigate into the project: cd my-vue-app && npm install
+3. Install @thunderid/vue: npm install @thunderid/vue
+4. Register ThunderIDPlugin in src/main.js with app.use(ThunderIDPlugin)
+5. Wrap app content with in src/App.vue
+6. Add SignInButton inside SignedOut and UserDropdown inside SignedIn conditional wrappers
+7. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the @thunderid/vue SDK.
diff --git a/docs/package.json b/docs/package.json
index 30055aafb5..9a1d1edf65 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -31,6 +31,7 @@
"generate:changelog": "node ./scripts/generate-changelog.mjs",
"generate:contributors": "node ./scripts/generate-contributors.mjs",
"generate:postman-collections": "node ./scripts/generate-postman-collections.mjs",
+ "generate:prompts": "node ./scripts/generate-prompts.mjs",
"generate:sdk-releases": "node ./scripts/generate-sdk-releases.mjs",
"setup": "node ./scripts/prebuild.mjs",
"lint": "eslint .",
@@ -51,10 +52,17 @@
"@docusaurus/theme-mermaid": "3.9.2",
"@mdx-js/react": "3.0.0",
"@scalar/api-reference-react": "0.9.38",
+ "@tanstack/react-query": "catalog:",
+ "@thunderid/components": "workspace:^",
+ "@thunderid/contexts": "workspace:^",
"@thunderid/design": "workspace:^",
"@thunderid/eslint-plugin": "workspace:^",
+ "@thunderid/hooks": "workspace:^",
+ "@thunderid/i18n": "workspace:^",
"@thunderid/logger": "workspace:^",
"@thunderid/prettier-config": "workspace:^",
+ "@thunderid/react": "catalog:",
+ "@thunderid/utils": "workspace:^",
"@wso2/oxygen-ui": "catalog:",
"@wso2/oxygen-ui-icons-react": "catalog:",
"clsx": "2.1.1",
@@ -63,7 +71,9 @@
"prism-react-renderer": "2.3.0",
"react": "19.2.3",
"react-dom": "19.2.3",
- "react-github-btn": "1.4.0"
+ "react-github-btn": "1.4.0",
+ "react-i18next": "catalog:",
+ "react-router": "catalog:"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.9.2",
diff --git a/docs/scripts/generate-prompts.mjs b/docs/scripts/generate-prompts.mjs
new file mode 100644
index 0000000000..d699dbb889
--- /dev/null
+++ b/docs/scripts/generate-prompts.mjs
@@ -0,0 +1,71 @@
+#!/usr/bin/env node
+
+// Copyright 2026 The ThunderID Authors
+// SPDX-License-Identifier: Apache-2.0
+
+import {existsSync, mkdirSync, readdirSync, copyFileSync} from 'fs';
+import {join, dirname, relative, sep} from 'path';
+import {fileURLToPath} from 'url';
+import {createLogger} from '@thunderid/logger';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const logger = createLogger('generate-prompts');
+
+// LLM prompt .txt files live alongside the docs page they belong to
+// (e.g. content/getting-started/connect-your-application/prompts/react/redirect-based.txt)
+// so they stay versioned with the docs. They're mirrored here into static/docs//...
+// so Docusaurus serves them as plain, CORS-fetchable .txt files (the console fetches them
+// directly), matching the URL scheme used for every other doc-relative link in config.js.
+const PROMPT_ROOT_SEGMENT = 'prompts';
+
+const VERSION_ROOTS = [
+ {sourceDir: join(__dirname, '..', 'content'), versionPath: 'next'},
+ {sourceDir: join(__dirname, '..', 'versioned_docs', 'version-v1.0.x'), versionPath: 'v1.0.x'},
+];
+
+const STATIC_DOCS_DIR = join(__dirname, '..', 'static', 'docs');
+
+/** Recursively collect every file living under a `prompts/` directory. */
+function findPromptFiles(dir, baseDir = dir) {
+ const files = [];
+ if (!existsSync(dir)) return files;
+
+ for (const entry of readdirSync(dir, {withFileTypes: true})) {
+ const fullPath = join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...findPromptFiles(fullPath, baseDir));
+ } else if (entry.isFile()) {
+ const relativePath = relative(baseDir, fullPath);
+ if (relativePath.split(sep).includes(PROMPT_ROOT_SEGMENT)) {
+ files.push({fullPath, relativePath});
+ }
+ }
+ }
+ return files;
+}
+
+function generatePrompts() {
+ logger.info('🔄 Copying LLM prompt files into static/docs/...');
+
+ let written = 0;
+
+ for (const {sourceDir, versionPath} of VERSION_ROOTS) {
+ for (const {fullPath, relativePath} of findPromptFiles(sourceDir)) {
+ const outputPath = join(STATIC_DOCS_DIR, versionPath, relativePath);
+ mkdirSync(dirname(outputPath), {recursive: true});
+ copyFileSync(fullPath, outputPath);
+ written++;
+ }
+ }
+
+ logger.info(`✅ Copied ${written} prompt file(s) into static/docs/{${VERSION_ROOTS.map((v) => v.versionPath).join(',')}}`);
+}
+
+try {
+ generatePrompts();
+} catch (error) {
+ logger.error('❌ Error copying prompt files:', error);
+ process.exit(1);
+}
diff --git a/docs/scripts/prebuild.mjs b/docs/scripts/prebuild.mjs
index ee71d62f9c..4b3d6b97cf 100644
--- a/docs/scripts/prebuild.mjs
+++ b/docs/scripts/prebuild.mjs
@@ -65,6 +65,9 @@ async function generateDocs() {
// Generate SDK release data
executeScript('SDK Releases Generator', join(__dirname, 'generate-sdk-releases.mjs'));
+ // Copy LLM prompt files into static/docs/
+ executeScript('Prompts Generator', join(__dirname, 'generate-prompts.mjs'));
+
logger.info('🎉 All documentation artifacts generated successfully!\n');
}
diff --git a/docs/src/components/DeveloperShortcut.tsx b/docs/src/components/DeveloperShortcut.tsx
index 8d4bdbabd4..26d9cd3d8a 100644
--- a/docs/src/components/DeveloperShortcut.tsx
+++ b/docs/src/components/DeveloperShortcut.tsx
@@ -3,12 +3,11 @@
import Link from '@docusaurus/Link';
import {useWindowSize} from '@docusaurus/theme-common';
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import {Box, Chip, Typography} from '@wso2/oxygen-ui';
import {AppWindow, Bot, Check, Download, MonitorSmartphone, Server, Zap} from '@wso2/oxygen-ui-icons-react';
import React, {useCallback} from 'react';
-import AndroidLogo from './icons/AndroidLogo';
import ExpressLogo from './icons/ExpressLogo';
-import FlutterLogo from './icons/FlutterLogo';
import IOSLogo from './icons/IOSLogo';
import JavaScriptLogo from './icons/JavaScriptLogo';
import LangChainLogo from './icons/LangChainLogo';
diff --git a/docs/src/components/Ecosystem/data.ts b/docs/src/components/Ecosystem/data.ts
index c30e64e8de..b5393cf699 100644
--- a/docs/src/components/Ecosystem/data.ts
+++ b/docs/src/components/Ecosystem/data.ts
@@ -1,8 +1,8 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import {ComponentType} from 'react';
-import AndroidLogo from '../icons/AndroidLogo';
import AngularLogo from '../icons/AngularLogo';
import AuthJsLogo from '../icons/AuthJsLogo';
import BetterAuthLogo from '../icons/BetterAuthLogo';
@@ -10,7 +10,6 @@ import BrowserLogo from '../icons/BrowserLogo';
import ClaudeLogo from '../icons/ClaudeLogo';
import CodexLogo from '../icons/CodexLogo';
import ExpressLogo from '../icons/ExpressLogo';
-import FlutterLogo from '../icons/FlutterLogo';
import GoLogo from '../icons/GoLogo';
import IOSLogo from '../icons/IOSLogo';
import JavaScriptLogo from '../icons/JavaScriptLogo';
diff --git a/docs/src/components/FloatingLogosBackground.tsx b/docs/src/components/FloatingLogosBackground.tsx
index 0a6ce727d1..a1adbb538c 100644
--- a/docs/src/components/FloatingLogosBackground.tsx
+++ b/docs/src/components/FloatingLogosBackground.tsx
@@ -1,13 +1,12 @@
// Copyright 2026 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import {Box} from '@wso2/oxygen-ui';
import {JSX} from 'react';
-import AndroidLogo from './icons/AndroidLogo';
import AngularLogo from './icons/AngularLogo';
import BrowserLogo from './icons/BrowserLogo';
import ExpressLogo from './icons/ExpressLogo';
-import FlutterLogo from './icons/FlutterLogo';
import GoLogo from './icons/GoLogo';
import IOSLogo from './icons/IOSLogo';
import NextLogo from './icons/NextLogo';
diff --git a/docs/src/components/HomePage/SDKShowcaseSection.tsx b/docs/src/components/HomePage/SDKShowcaseSection.tsx
index 31e99a2990..da460553fc 100644
--- a/docs/src/components/HomePage/SDKShowcaseSection.tsx
+++ b/docs/src/components/HomePage/SDKShowcaseSection.tsx
@@ -2,11 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import Link from '@docusaurus/Link';
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import {Box, Container, Typography} from '@wso2/oxygen-ui';
import {JSX, useState} from 'react';
-import AndroidLogo from '../icons/AndroidLogo';
import ExpressLogo from '../icons/ExpressLogo';
-import FlutterLogo from '../icons/FlutterLogo';
import IOSLogo from '../icons/IOSLogo';
import JavaScriptLogo from '../icons/JavaScriptLogo';
import NextLogo from '../icons/NextLogo';
diff --git a/docs/src/theme/DocSidebarItem/Link/index.tsx b/docs/src/theme/DocSidebarItem/Link/index.tsx
index b90635331a..175a4259d7 100644
--- a/docs/src/theme/DocSidebarItem/Link/index.tsx
+++ b/docs/src/theme/DocSidebarItem/Link/index.tsx
@@ -4,10 +4,9 @@
import Link from '@docusaurus/Link';
import {usePluginData} from '@docusaurus/useGlobalData';
import OriginalDocSidebarItemLink from '@theme-original/DocSidebarItem/Link';
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import React from 'react';
-import AndroidLogo from '@site/src/components/icons/AndroidLogo';
import ExpressLogo from '@site/src/components/icons/ExpressLogo';
-import FlutterLogo from '@site/src/components/icons/FlutterLogo';
import IOSLogo from '@site/src/components/icons/IOSLogo';
import JavaScriptLogo from '@site/src/components/icons/JavaScriptLogo';
import LangChainLogo from '@site/src/components/icons/LangChainLogo';
diff --git a/docs/src/theme/MDXComponents.tsx b/docs/src/theme/MDXComponents.tsx
index 5936477487..65c703ab09 100644
--- a/docs/src/theme/MDXComponents.tsx
+++ b/docs/src/theme/MDXComponents.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import MDXComponents from '@theme-original/MDXComponents';
+import {AndroidLogo, FlutterLogo} from '@thunderid/components';
import {
Box,
Card,
@@ -40,7 +41,6 @@ import DocsGetStarted from '@site/src/components/DocsGetStarted';
import FloatingLogosBackground from '@site/src/components/FloatingLogosBackground';
import {BuildAFlowDiagram, FlowNodeTypesRoadmap, FlowBuildingBlocksRoadmap} from '@site/src/components/FlowConcepts';
import GettingStartedJourney from '@site/src/components/GettingStartedJourney';
-import AndroidLogo from '@site/src/components/icons/AndroidLogo';
import AngularLogo from '@site/src/components/icons/AngularLogo';
import BrowserLogo from '@site/src/components/icons/BrowserLogo';
import ClaudeLogo from '@site/src/components/icons/ClaudeLogo';
@@ -48,7 +48,6 @@ import CliLogo from '@site/src/components/icons/CliLogo';
import CodexLogo from '@site/src/components/icons/CodexLogo';
import DockerLogo from '@site/src/components/icons/DockerLogo';
import ExpressLogo from '@site/src/components/icons/ExpressLogo';
-import FlutterLogo from '@site/src/components/icons/FlutterLogo';
import GoLogo from '@site/src/components/icons/GoLogo';
import Html5Logo from '@site/src/components/icons/Html5Logo';
import IOSLogo from '@site/src/components/icons/IOSLogo';
diff --git a/docs/static/data/contributors.json b/docs/static/data/contributors.json
index c4d5c43cc1..e74d9d4037 100644
--- a/docs/static/data/contributors.json
+++ b/docs/static/data/contributors.json
@@ -2,55 +2,61 @@
"contributors": [
{
"avatarUrl": "https://avatars.githubusercontent.com/u/35653110?v=4",
- "contributions": 886,
+ "contributions": 1009,
"htmlUrl": "https://github.com/ThaminduDilshan",
"login": "ThaminduDilshan"
},
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/1617810?v=4",
- "contributions": 463,
- "htmlUrl": "https://github.com/darshanasbg",
- "login": "darshanasbg"
- },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/25959096?v=4",
- "contributions": 418,
+ "contributions": 492,
"htmlUrl": "https://github.com/brionmario",
"login": "brionmario"
},
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/1617810?v=4",
+ "contributions": 464,
+ "htmlUrl": "https://github.com/darshanasbg",
+ "login": "darshanasbg"
+ },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/42619922?v=4",
- "contributions": 367,
+ "contributions": 431,
"htmlUrl": "https://github.com/DonOmalVindula",
"login": "DonOmalVindula"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/114133267?v=4",
- "contributions": 301,
+ "contributions": 360,
"htmlUrl": "https://github.com/thiva-k",
"login": "thiva-k"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/32898873?v=4",
- "contributions": 239,
+ "contributions": 338,
"htmlUrl": "https://github.com/rajithacharith",
"login": "rajithacharith"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/62582918?v=4",
- "contributions": 205,
+ "contributions": 207,
"htmlUrl": "https://github.com/KaveeshaPiumini",
"login": "KaveeshaPiumini"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/11584828?v=4",
- "contributions": 181,
+ "contributions": 204,
"htmlUrl": "https://github.com/senthalan",
"login": "senthalan"
},
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/32576163?v=4",
+ "contributions": 129,
+ "htmlUrl": "https://github.com/sahandilshan",
+ "login": "sahandilshan"
+ },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/7569427?v=4",
- "contributions": 108,
+ "contributions": 122,
"htmlUrl": "https://github.com/jeradrutnam",
"login": "jeradrutnam"
},
@@ -61,76 +67,112 @@
"login": "JeethJJ"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/32576163?v=4",
- "contributions": 87,
- "htmlUrl": "https://github.com/sahandilshan",
- "login": "sahandilshan"
+ "avatarUrl": "https://avatars.githubusercontent.com/u/79596630?v=4",
+ "contributions": 105,
+ "htmlUrl": "https://github.com/Malith-19",
+ "login": "Malith-19"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/31583807?v=4",
- "contributions": 76,
+ "contributions": 101,
"htmlUrl": "https://github.com/himeshsiriwardana",
"login": "himeshsiriwardana"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/42399179?v=4",
- "contributions": 73,
+ "contributions": 91,
"htmlUrl": "https://github.com/ThumulaPerera",
"login": "ThumulaPerera"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/79596630?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/37938529?v=4",
+ "contributions": 86,
+ "htmlUrl": "https://github.com/JayaShakthi97",
+ "login": "JayaShakthi97"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/77233788?v=4",
"contributions": 60,
- "htmlUrl": "https://github.com/Malith-19",
- "login": "Malith-19"
+ "htmlUrl": "https://github.com/Dilusha-Madushan",
+ "login": "Dilusha-Madushan"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/35717390?v=4",
- "contributions": 49,
+ "contributions": 58,
"htmlUrl": "https://github.com/sadilchamishka",
"login": "sadilchamishka"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/46132469?v=4",
- "contributions": 42,
+ "contributions": 43,
"htmlUrl": "https://github.com/hwupathum",
"login": "hwupathum"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/77233788?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/1332991?v=4",
+ "contributions": 42,
+ "htmlUrl": "https://github.com/madurangasiriwardena",
+ "login": "madurangasiriwardena"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/36252606?v=4",
"contributions": 41,
- "htmlUrl": "https://github.com/Dilusha-Madushan",
- "login": "Dilusha-Madushan"
+ "htmlUrl": "https://github.com/UdeshAthukorala",
+ "login": "UdeshAthukorala"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/37938529?v=4",
- "contributions": 39,
- "htmlUrl": "https://github.com/JayaShakthi97",
- "login": "JayaShakthi97"
+ "avatarUrl": "https://avatars.githubusercontent.com/u/119397108?v=4",
+ "contributions": 37,
+ "htmlUrl": "https://github.com/NutharaNR",
+ "login": "NutharaNR"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/154749626?v=4",
- "contributions": 21,
+ "contributions": 25,
"htmlUrl": "https://github.com/RandithaK",
"login": "RandithaK"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/119397108?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/51480027?v=4",
+ "contributions": 24,
+ "htmlUrl": "https://github.com/ayeshajay",
+ "login": "ayeshajay"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/270975584?v=4",
+ "contributions": 23,
+ "htmlUrl": "https://github.com/anushasunkada",
+ "login": "anushasunkada"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/4128062?v=4",
+ "contributions": 23,
+ "htmlUrl": "https://github.com/indeewari",
+ "login": "indeewari"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/194389180?v=4",
+ "contributions": 20,
+ "htmlUrl": "https://github.com/Yathusiga27",
+ "login": "Yathusiga27"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/124599924?v=4",
"contributions": 18,
- "htmlUrl": "https://github.com/NutharaNR",
- "login": "NutharaNR"
+ "htmlUrl": "https://github.com/Osara-B",
+ "login": "Osara-B"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/51480027?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/118417912?v=4",
"contributions": 17,
- "htmlUrl": "https://github.com/ayeshajay",
- "login": "ayeshajay"
+ "htmlUrl": "https://github.com/ImalshaD",
+ "login": "ImalshaD"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/36252606?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/86454991?v=4",
"contributions": 16,
- "htmlUrl": "https://github.com/UdeshAthukorala",
- "login": "UdeshAthukorala"
+ "htmlUrl": "https://github.com/KD23243",
+ "login": "KD23243"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/42939752?v=4",
@@ -139,10 +181,10 @@
"login": "RushanNanayakkara"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/1332991?v=4",
- "contributions": 11,
- "htmlUrl": "https://github.com/madurangasiriwardena",
- "login": "madurangasiriwardena"
+ "avatarUrl": "https://avatars.githubusercontent.com/u/74812270?v=4",
+ "contributions": 12,
+ "htmlUrl": "https://github.com/Sadeesha-Sath",
+ "login": "Sadeesha-Sath"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/45028707?v=4",
@@ -150,21 +192,21 @@
"htmlUrl": "https://github.com/ThaminduR",
"login": "ThaminduR"
},
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/86454991?v=4",
- "contributions": 10,
- "htmlUrl": "https://github.com/KD23243",
- "login": "KD23243"
- },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/17597293?v=4",
- "contributions": 8,
+ "contributions": 10,
"htmlUrl": "https://github.com/piraveena",
"login": "piraveena"
},
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/739892?v=4",
+ "contributions": 10,
+ "htmlUrl": "https://github.com/sagara-gunathunga",
+ "login": "sagara-gunathunga"
+ },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/183357006?v=4",
- "contributions": 8,
+ "contributions": 9,
"htmlUrl": "https://github.com/sacrana0",
"login": "sacrana0"
},
@@ -181,16 +223,22 @@
"login": "nandhu-kumar"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/270975584?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/50801227?v=4",
"contributions": 7,
- "htmlUrl": "https://github.com/anushasunkada",
- "login": "anushasunkada"
+ "htmlUrl": "https://github.com/ZiyamSanthosh",
+ "login": "ZiyamSanthosh"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/739892?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/25483865?v=4",
+ "contributions": 7,
+ "htmlUrl": "https://github.com/AnuradhaSK",
+ "login": "AnuradhaSK"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/61885844?v=4",
"contributions": 6,
- "htmlUrl": "https://github.com/sagara-gunathunga",
- "login": "sagara-gunathunga"
+ "htmlUrl": "https://github.com/PasinduYeshan",
+ "login": "PasinduYeshan"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/39120228?v=4",
@@ -211,10 +259,22 @@
"login": "Sithumli"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/124599924?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/30111554?v=4",
"contributions": 5,
- "htmlUrl": "https://github.com/Osara-B",
- "login": "Osara-B"
+ "htmlUrl": "https://github.com/NipuniBhagya",
+ "login": "NipuniBhagya"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/150885396?v=4",
+ "contributions": 5,
+ "htmlUrl": "https://github.com/samadhisakunika",
+ "login": "samadhisakunika"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/150885192?v=4",
+ "contributions": 5,
+ "htmlUrl": "https://github.com/ravindu439",
+ "login": "ravindu439"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/146766148?v=4",
@@ -222,12 +282,6 @@
"htmlUrl": "https://github.com/kavindadimuthu",
"login": "kavindadimuthu"
},
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/194389180?v=4",
- "contributions": 4,
- "htmlUrl": "https://github.com/Yathusiga27",
- "login": "Yathusiga27"
- },
{
"avatarUrl": "https://avatars.githubusercontent.com/u/110180949?v=4",
"contributions": 4,
@@ -235,10 +289,10 @@
"login": "ranuka-laksika"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/150885396?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/9883427?v=4",
"contributions": 4,
- "htmlUrl": "https://github.com/samadhisakunika",
- "login": "samadhisakunika"
+ "htmlUrl": "https://github.com/omindu",
+ "login": "omindu"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/150146619?v=4",
@@ -247,28 +301,16 @@
"login": "DharshanSR"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/9883427?v=4",
- "contributions": 4,
- "htmlUrl": "https://github.com/omindu",
- "login": "omindu"
- },
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/6869769?v=4",
- "contributions": 3,
- "htmlUrl": "https://github.com/maheshika",
- "login": "maheshika"
- },
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/4128062?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/130324291?v=4",
"contributions": 3,
- "htmlUrl": "https://github.com/indeewari",
- "login": "indeewari"
+ "htmlUrl": "https://github.com/HesandaLiyanage",
+ "login": "HesandaLiyanage"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/25483865?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/8939174?v=4",
"contributions": 3,
- "htmlUrl": "https://github.com/AnuradhaSK",
- "login": "AnuradhaSK"
+ "htmlUrl": "https://github.com/JKAUSHALYA",
+ "login": "JKAUSHALYA"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/145958660?v=4",
@@ -277,16 +319,10 @@
"login": "warnakulasuriya-fds-e23"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/8939174?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/6869769?v=4",
"contributions": 3,
- "htmlUrl": "https://github.com/JKAUSHALYA",
- "login": "JKAUSHALYA"
- },
- {
- "avatarUrl": "https://avatars.githubusercontent.com/u/17524063?v=4",
- "contributions": 2,
- "htmlUrl": "https://github.com/VajiraPrabuddhaka",
- "login": "VajiraPrabuddhaka"
+ "htmlUrl": "https://github.com/maheshika",
+ "login": "maheshika"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/25488962?v=4",
@@ -295,22 +331,28 @@
"login": "Thumimku"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/150885192?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/17524063?v=4",
"contributions": 2,
- "htmlUrl": "https://github.com/ravindu439",
- "login": "ravindu439"
+ "htmlUrl": "https://github.com/VajiraPrabuddhaka",
+ "login": "VajiraPrabuddhaka"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/75576423?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/5061791?v=4",
"contributions": 2,
- "htmlUrl": "https://github.com/BimsaraBodaragama",
- "login": "BimsaraBodaragama"
+ "htmlUrl": "https://github.com/ayshsandu",
+ "login": "ayshsandu"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/130324291?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/2276412?v=4",
"contributions": 2,
- "htmlUrl": "https://github.com/HesandaLiyanage",
- "login": "HesandaLiyanage"
+ "htmlUrl": "https://github.com/kavix",
+ "login": "kavix"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/123491619?v=4",
+ "contributions": 2,
+ "htmlUrl": "https://github.com/janithjay",
+ "login": "janithjay"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/175306990?v=4",
@@ -331,34 +373,46 @@
"login": "malinthaprasan"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/29277992?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/75576423?v=4",
+ "contributions": 2,
+ "htmlUrl": "https://github.com/BimsaraBodaragama",
+ "login": "BimsaraBodaragama"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/67315176?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/isala404",
- "login": "isala404"
+ "htmlUrl": "https://github.com/savindi7",
+ "login": "savindi7"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/6144466?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/79456372?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/madhuramendis",
- "login": "madhuramendis"
+ "htmlUrl": "https://github.com/prdai",
+ "login": "prdai"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/140165177?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/152188411?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/nishagii",
- "login": "nishagii"
+ "htmlUrl": "https://github.com/iff-sal",
+ "login": "iff-sal"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/34201061?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/142331771?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/PasinduRavimal",
- "login": "PasinduRavimal"
+ "htmlUrl": "https://github.com/garuka-satharasinghe",
+ "login": "garuka-satharasinghe"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/62681950?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/49331487?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/priyanshoon",
- "login": "priyanshoon"
+ "htmlUrl": "https://github.com/drsamitha",
+ "login": "drsamitha"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/55852668?v=4",
+ "contributions": 1,
+ "htmlUrl": "https://github.com/littleKitchen",
+ "login": "littleKitchen"
},
{
"avatarUrl": "https://avatars.githubusercontent.com/u/74367192?v=4",
@@ -367,49 +421,55 @@
"login": "th33k"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/65800949?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/62681950?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/HasiniSama",
- "login": "HasiniSama"
+ "htmlUrl": "https://github.com/priyanshoon",
+ "login": "priyanshoon"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/55852668?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/34201061?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/littleKitchen",
- "login": "littleKitchen"
+ "htmlUrl": "https://github.com/PasinduRavimal",
+ "login": "PasinduRavimal"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/49331487?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/140165177?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/drsamitha",
- "login": "drsamitha"
+ "htmlUrl": "https://github.com/nishagii",
+ "login": "nishagii"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/142331771?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/6144466?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/garuka-satharasinghe",
- "login": "garuka-satharasinghe"
+ "htmlUrl": "https://github.com/madhuramendis",
+ "login": "madhuramendis"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/152188411?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/29277992?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/iff-sal",
- "login": "iff-sal"
+ "htmlUrl": "https://github.com/isala404",
+ "login": "isala404"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/2276412?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/65800949?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/kavix",
- "login": "kavix"
+ "htmlUrl": "https://github.com/HasiniSama",
+ "login": "HasiniSama"
},
{
- "avatarUrl": "https://avatars.githubusercontent.com/u/67315176?v=4",
+ "avatarUrl": "https://avatars.githubusercontent.com/u/77677724?v=4",
"contributions": 1,
- "htmlUrl": "https://github.com/savindi7",
- "login": "savindi7"
+ "htmlUrl": "https://github.com/KashiwalHarsh",
+ "login": "KashiwalHarsh"
+ },
+ {
+ "avatarUrl": "https://avatars.githubusercontent.com/u/101007824?v=4",
+ "contributions": 1,
+ "htmlUrl": "https://github.com/chamals3n4",
+ "login": "chamals3n4"
}
],
- "generatedAt": "2026-06-23T17:39:55.706Z",
- "totalCommits": 3993,
- "totalContributors": 68
+ "generatedAt": "2026-08-05T08:29:36.074Z",
+ "totalCommits": 4886,
+ "totalContributors": 78
}
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/android.mdx b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/android.mdx
index d269027256..3fcc7fab5d 100644
--- a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/android.mdx
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/android.mdx
@@ -64,7 +64,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My Android App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The Android SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create an Android App
@@ -102,48 +106,6 @@ implementation 'dev.thunderid:compose:0.1.0'
```
:::
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme in two places.
-
-**1. Register the scheme in your app**
-
-Open `AndroidManifest.xml` and add an intent filter with your scheme to your launcher activity. Use `singleTask` launch mode so the redirect reuses the existing activity instance instead of creating a new one.
-
-```xml title="AndroidManifest.xml" showLineNumbers
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**2. Register the redirect URI in the console**
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-dev.thunderid.quickstart://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-dev.thunderid.quickstart://logout
-```
-
## Initialize the SDK
Open your `MainActivity` and wrap the content you pass to `setContent` with the `ThunderIDProvider` composable. This provides `ThunderIDState` to all child composables through `LocalThunderID`.
@@ -167,10 +129,7 @@ class MainActivity : ComponentActivity() {
ThunderIDProvider(
config = ThunderIDConfig(
baseUrl = "https://localhost:8090",
- clientId = "",
scopes = listOf("openid", "profile", "email"),
- afterSignInUrl = "dev.thunderid.quickstart://callback",
- afterSignOutUrl = "dev.thunderid.quickstart://logout",
applicationId = ""
)
) {
@@ -183,7 +142,7 @@ class MainActivity : ComponentActivity() {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -191,11 +150,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `"openid"` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/flutter.mdx b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/flutter.mdx
index 162fee924a..590ece4a16 100644
--- a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/flutter.mdx
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/flutter.mdx
@@ -65,7 +65,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My Flutter App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The Flutter SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create a Flutter App
@@ -95,55 +99,6 @@ Then install it:
flutter pub get
```
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme on both platforms.
-
-### iOS
-
-Open `ios/Runner/Info.plist` and add a URL scheme under **URL Types**:
-
-```xml title="ios/Runner/Info.plist"
-CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- dev.thunderid.app
-
-
-
-```
-
-### Android
-
-Open `android/app/src/main/AndroidManifest.xml` and add an intent filter inside your `` tag:
-
-```xml title="android/app/src/main/AndroidManifest.xml"
-
-
-
-
-
-
-```
-
-### Register the URIs in the console
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-dev.thunderid.app://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-dev.thunderid.app://logout
-```
-
## Initialize the SDK
Wrap your root widget with `ThunderIDProvider` in `lib/main.dart`:
@@ -157,10 +112,7 @@ void main() {
ThunderIDProvider(
config: ThunderIDConfig(
baseUrl: 'https://localhost:8090',
- clientId: '',
scopes: const ['openid', 'profile', 'email'],
- afterSignInUrl: 'dev.thunderid.app://callback',
- afterSignOutUrl: 'dev.thunderid.app://logout',
applicationId: '',
),
child: const MyApp(),
@@ -181,7 +133,7 @@ class MyApp extends StatelessWidget {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -189,11 +141,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `'openid'` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/ios.mdx b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/ios.mdx
index 8518cc6925..29d9f15e4a 100644
--- a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/ios.mdx
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/ios.mdx
@@ -65,7 +65,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
3. Under **Application Type**, select **Mobile App**.
4. Enter a name (e.g. `My iOS App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy the **Client ID** from the **General** tab. You'll add the redirect URI in the next step after configuring your URL scheme.
+5. Copy the **Application ID** from the **General** tab, under **Quick Copy**.
+
+:::info App-Native Authentication
+This quickstart uses app-native authentication through the Flow Execution API. The iOS SDK renders the sign-in and sign-up forms natively in your app, so you only need the **Application ID**, no OAuth 2.0 Client ID and no redirect URI.
+:::
## Create an iOS App
@@ -93,40 +97,6 @@ pod 'ThunderIDSwiftUI'
```
:::
-## Configure a Callback URL Scheme
-
- redirects back to your app after sign-in and sign-out using a custom URL scheme. You need to register this scheme in two places.
-
-**1. Register the scheme in your app**
-
-Open your app's `Info.plist` and add a URL scheme under **URL Types**. For example:
-
-```xml
-CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- io.thunderid.b2c
-
-
-
-```
-
-**2. Register the redirect URI in the console**
-
-In the console, open your registered application and add the following as an **Allowed Redirect URI**:
-
-```text
-io.thunderid.b2c://callback
-```
-
-Add the same value as an **Allowed Post-Logout Redirect URI**:
-
-```text
-io.thunderid.b2c://logout
-```
-
## Initialize the SDK
Open your app's entry point (the file that conforms to `App`) and apply the `.thunderIDProvider(config:)` modifier to your root view. This injects a `ThunderIDState` environment object into all child views.
@@ -142,10 +112,7 @@ struct MyApp: App {
ContentView()
.thunderIDProvider(config: ThunderIDConfig(
baseUrl: "https://localhost:8090",
- clientId: "",
scopes: ["openid", "profile", "email"],
- afterSignInUrl: "io.thunderid.b2c://callback",
- afterSignOutUrl: "io.thunderid.b2c://logout",
applicationId: ""
))
}
@@ -154,7 +121,7 @@ struct MyApp: App {
```
:::warning Configuration
-Replace `` with the **Client ID** and `` with the **Application ID** from your application settings.
+Replace `` with the **Application ID** from your application settings.
:::
### Configuration Parameters
@@ -162,11 +129,8 @@ Replace `` with the **Client ID** and `` wi
| Parameter | Description |
|-----------|-------------|
| `baseUrl` | Your instance URL. Must use HTTPS. |
-| `clientId` | The Client ID from your application |
| `scopes` | OAuth 2.0 scopes to request. Include `"openid"` at minimum. |
-| `afterSignInUrl` | The redirect URI to return to after sign-in |
-| `afterSignOutUrl` | The redirect URI to return to after sign-out |
-| `applicationId` | The Application ID used for embedded (app-native) sign-in flows |
+| `applicationId` | The Application ID used for the app-native sign-in and sign-up flows |
## Add Sign-In and Sign-Out
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/node.mdx b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/node.mdx
index d7100d7911..0875c9a4b9 100644
--- a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/node.mdx
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/node.mdx
@@ -4,7 +4,7 @@ title: Node.js Quickstart
docType: quickstart
sidebar_position: 5
persona: app
-description: Add {{ProductName}} authentication to a vanilla Node.js application using the @thunderid/node SDK.
+description: Authenticate a Node.js service to {{ProductName}} as itself using the OAuth 2.0 client_credentials grant with the @thunderid/node SDK.
---
import {
@@ -20,7 +20,7 @@ import {
# Node.js Quickstart
-Use this guide to add authentication to a vanilla Node.js application using the `@thunderid/node` SDK and the built-in `http` module. No framework required.
+Use this guide to authenticate a Node.js service to using the `@thunderid/node` SDK. Unlike the other quickstarts in this section, there's no user and no browser sign-in: the service authenticates as **itself** with the OAuth 2.0 `client_credentials` grant, then uses the resulting access token to call another piece of business logic. Use this pattern for background jobs, cron tasks, or one backend service calling another, where there's no human in the loop to redirect through a login page.
@@ -28,18 +28,22 @@ Use this guide to add authentication to a vanilla Node.js applic
}>Create a Node.js project
}>Install the @thunderid/node package
-}>Add working sign-in and sign-out routes
-}>Protect routes and display the signed-in user's profile
+}>Authenticate a service with the client_credentials grant
+}>Use the resulting access token to call business logic
## Prerequisites
-}>About 15 minutes
+}>About 10 minutes
}>Node.js 18+ installed on your system
}>npm, yarn, or pnpm
}>Your preferred code editor
+:::tip Example Source Code
+Check out the complete Node.js Quickstart Sample in the repository.
+:::
+
## Run
@@ -50,7 +54,9 @@ Start a local instance. Pick the method that works best for you:
Once it's running, the console is available at [https://localhost:8090/console](https://localhost:8090/console).
-## Create an Application
+## Create an Agent
+
+Agents are 's machine identities, distinct from user-facing applications. A background service like this one authenticates as an agent, not as an application.
1. Sign in to the Console.
@@ -58,12 +64,11 @@ Once it's running, the console is available at [https://localhost:8090/console](
If you used the default setup, sign in to the Console as `admin` with the password generated during setup and printed to the setup output (unless you supplied your own).
:::
-2. Navigate to **Applications**, and click **Add Application**.
-
-3. Under **Technology**, select **Node.js**.
-4. Enter a name (e.g. `My Node.js App`) and create an application. The rest of the settings can stay at their defaults.
-5. Copy both the **Client ID** and **Client Secret** from the window that pops up. The Client ID can also be found in the **General** tab.
-6. Under **General**, add `http://localhost:3000/callback` to the list of **Authorized Redirect URIs**.
+2. Navigate to **Agents** and click **Add Agent**.
+3. Enter an **Agent name** (e.g. `node-service-quickstart`) and select an **Owner**, then click **Create agent**.
+4. displays the agent's **Client Secret** once. Copy it now; it cannot be retrieved again.
+5. Open the agent's **Advanced Settings** tab, enable the `client_credentials` grant type, and set the **Client authentication method** to `client_secret_basic`.
+6. Copy the **Client ID** from the **General** tab.
## Create a Node.js Project
@@ -71,20 +76,20 @@ Initialize a new Node.js project:
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
npm init -y
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
yarn init -y
- mkdir my-node-app
- cd my-node-app
+ mkdir my-node-service
+ cd my-node-service
pnpm init
@@ -107,309 +112,92 @@ Install the Node.js SDK:
-## Initialize the Client
+## Authenticate as the Service
-Create an `index.js` file and initialize the `ThunderIDNodeClient` with your application credentials:
+Create an `index.mjs` file and initialize `ThunderIDNodeClient` with `grantType: 'client_credentials'`. With that set, `getAccessToken()` authenticates as the service itself, no session and no sign-in, and transparently fetches, caches, and refreshes the token.
-```js title="index.js" showLineNumbers
-const http = require('http');
-const { URL } = require('url');
-const { randomUUID } = require('crypto');
-const { ThunderIDNodeClient } = require('@thunderid/node');
+```js title="index.mjs" showLineNumbers
+import { ThunderIDNodeClient } from '@thunderid/node';
-const PORT = 3000;
-const SESSION_COOKIE = 'tid_session';
+const client = new ThunderIDNodeClient();
-const auth = new ThunderIDNodeClient();
+await client.initialize({
+ baseUrl: 'https://localhost:8090',
+ clientId: '',
+ clientSecret: '',
+ grantType: 'client_credentials',
+});
-function getSessionId(req) {
- const cookieHeader = req.headers.cookie ?? '';
- for (const part of cookieHeader.split(';')) {
- const [name, value] = part.trim().split('=');
- if (name === SESSION_COOKIE) return decodeURIComponent(value);
- }
- return null;
-}
-
-async function main() {
- await auth.initialize({
- clientId: '',
- clientSecret: '',
- baseUrl: 'https://localhost:8090',
- afterSignInUrl: 'http://localhost:3000/callback',
- afterSignOutUrl: 'http://localhost:3000',
- });
-
- const server = http.createServer(async (req, res) => {
- // routes added in the next step
- });
-
- server.listen(PORT, () => {
- console.log(`Server running on http://localhost:${PORT}`);
- });
-}
+const accessToken = await client.getAccessToken();
+const { scope } = await client.decodeJwtToken(accessToken);
-main();
+console.log(`Authenticated with scope: ${scope}`);
```
:::warning Configuration
-Replace `` and `` with the values from your application. Set the authorized redirect URL in your application settings to `http://localhost:3000/callback`.
+Replace `` and `` with the **Client ID** and **Client Secret** from your agent.
:::
### Configuration Parameters
| Parameter | Description |
|-----------|-------------|
-| `clientId` | The Client ID from your application |
-| `clientSecret` | The Client Secret from your application |
| `baseUrl` | Your instance URL (e.g., `https://localhost:8090`) |
-| `afterSignInUrl` | The callback URL redirects to after sign-in |
-| `afterSignOutUrl` | The URL to redirect to after sign-out |
-
-## Add Sign-In and Sign-Out Routes
-
-The `signIn` method works in two phases: it first redirects the user to , then handles the authorization code on the callback. Session state is tied to a session ID stored in a cookie.
-
-Replace the `// routes added in the next step` comment with:
-
-```js title="index.js" showLineNumbers
- const url = new URL(req.url, `http://localhost:${PORT}`);
-
- try {
- if (url.pathname === '/login') {
- let sessionId = getSessionId(req);
- const extraHeaders = {};
- if (!sessionId) {
- sessionId = randomUUID();
- extraHeaders['Set-Cookie'] =
- `${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
- }
- await auth.signIn((authUrl) => {
- res.writeHead(302, { ...extraHeaders, Location: authUrl });
- res.end();
- }, sessionId);
-
- } else if (url.pathname === '/callback') {
- const code = url.searchParams.get('code');
- const state = url.searchParams.get('state');
- const sessionState = url.searchParams.get('session_state');
- const sessionId = getSessionId(req);
-
- if (!sessionId || !code || !state) {
- res.writeHead(400);
- return res.end('Bad request');
- }
-
- await auth.signIn(() => {}, sessionId, code, sessionState, state);
- res.writeHead(302, { Location: '/profile' });
- res.end();
-
- } else if (url.pathname === '/logout') {
- const sessionId = getSessionId(req);
- if (!sessionId) {
- res.writeHead(302, { Location: '/' });
- return res.end();
- }
- const signOutUrl = await auth.signOut(sessionId);
- res.writeHead(302, {
- Location: signOutUrl,
- 'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
- });
- res.end();
-
- }
- } catch {
- res.writeHead(500);
- res.end('Internal server error');
- }
-```
+| `clientId` | The Client ID from your agent |
+| `clientSecret` | The Client Secret from your agent |
+| `grantType` | Set to `'client_credentials'` to authenticate the service as itself, with no user and no browser redirect |
-**How the sign-in flow works:**
-
-1. `GET /login`: generates a session ID, stores it in a cookie, and calls `signIn` with an `authUrlCallback`. The callback receives the authorization URL and redirects the user's browser there.
-2. `GET /callback`: redirects back with `code` and `state` query parameters. Calling `signIn` again with those values exchanges the code for tokens and stores the session.
-3. `GET /logout`: calls `signOut` to get the OIDC end-session URL, clears the local cookie, then redirects the browser to complete the logout at .
-
-## Protect a Route and Display User Info
-
-Use `isSignedIn` to guard routes and `getUser` to retrieve the authenticated user's profile. Add these inside the same `try` block, before the closing `}`:
-
-```js title="index.js" showLineNumbers
- if (url.pathname === '/') {
- const sessionId = getSessionId(req);
- const signedIn = sessionId && (await auth.isSignedIn(sessionId));
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(signedIn
- ? 'View profile | Sign out'
- : 'Sign in'
- );
-
- } else if (url.pathname === '/profile') {
- const sessionId = getSessionId(req);
- if (!sessionId || !(await auth.isSignedIn(sessionId))) {
- res.writeHead(302, { Location: '/login' });
- return res.end();
- }
- const user = await auth.getUser(sessionId);
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(`
-
Welcome, ${user.name || user.username}!
-
Email: ${user.email}
-
First name: ${user.given_name}
-
Last name: ${user.family_name}
- Sign out
- `);
-
- } else if (url.pathname === '/login') {
-```
+## Call Business Logic With the Token
-## Complete index.js
+A real backend keeps authentication in its own module instead of scattering it across business logic. Update `index.mjs` so the rest of the app asks for a token only when it needs one, instead of handling `getAccessToken()` and the `Authorization` header directly:
-Here is the full file for reference:
+```js title="index.mjs" showLineNumbers
+import { ThunderIDNodeClient } from '@thunderid/node';
-```js title="index.js" showLineNumbers
-const http = require('http');
-const { URL } = require('url');
-const { randomUUID } = require('crypto');
-const { ThunderIDNodeClient } = require('@thunderid/node');
+const client = new ThunderIDNodeClient();
-const PORT = 3000;
-const SESSION_COOKIE = 'tid_session';
+await client.initialize({
+ baseUrl: 'https://localhost:8090',
+ clientId: '',
+ clientSecret: '',
+ grantType: 'client_credentials',
+});
-const auth = new ThunderIDNodeClient();
+async function getStock(sku) {
+ const accessToken = await client.getAccessToken();
-function getSessionId(req) {
- const cookieHeader = req.headers.cookie ?? '';
- for (const part of cookieHeader.split(';')) {
- const [name, value] = part.trim().split('=');
- if (name === SESSION_COOKIE) return decodeURIComponent(value);
- }
- return null;
-}
+ // A real backend would attach this as `Authorization: Bearer ${accessToken}`
+ // on a request to a separate inventory service. This quickstart just proves
+ // the token was obtained before answering.
+ console.log(`Requesting ${sku} with a valid access token`);
-async function main() {
- await auth.initialize({
- clientId: '',
- clientSecret: '',
- baseUrl: 'https://localhost:8090',
- afterSignInUrl: 'http://localhost:3000/callback',
- afterSignOutUrl: 'http://localhost:3000',
- });
-
- const server = http.createServer(async (req, res) => {
- const url = new URL(req.url, `http://localhost:${PORT}`);
-
- try {
- if (url.pathname === '/') {
- const sessionId = getSessionId(req);
- const signedIn = sessionId && (await auth.isSignedIn(sessionId));
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(signedIn
- ? 'View profile | Sign out'
- : 'Sign in'
- );
-
- } else if (url.pathname === '/profile') {
- const sessionId = getSessionId(req);
- if (!sessionId || !(await auth.isSignedIn(sessionId))) {
- res.writeHead(302, { Location: '/login' });
- return res.end();
- }
- const user = await auth.getUser(sessionId);
- res.writeHead(200, { 'Content-Type': 'text/html' });
- res.end(`
-
Welcome, ${user.name || user.username}!
-
Email: ${user.email}
-
First name: ${user.given_name}
-
Last name: ${user.family_name}
- Sign out
- `);
-
- } else if (url.pathname === '/login') {
- let sessionId = getSessionId(req);
- const extraHeaders = {};
- if (!sessionId) {
- sessionId = randomUUID();
- extraHeaders['Set-Cookie'] =
- `${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
- }
- await auth.signIn((authUrl) => {
- res.writeHead(302, { ...extraHeaders, Location: authUrl });
- res.end();
- }, sessionId);
-
- } else if (url.pathname === '/callback') {
- const code = url.searchParams.get('code');
- const state = url.searchParams.get('state');
- const sessionState = url.searchParams.get('session_state');
- const sessionId = getSessionId(req);
-
- if (!sessionId || !code || !state) {
- res.writeHead(400);
- return res.end('Bad request');
- }
-
- await auth.signIn(() => {}, sessionId, code, sessionState, state);
- res.writeHead(302, { Location: '/profile' });
- res.end();
-
- } else if (url.pathname === '/logout') {
- const sessionId = getSessionId(req);
- if (!sessionId) {
- res.writeHead(302, { Location: '/' });
- return res.end();
- }
- const signOutUrl = await auth.signOut(sessionId);
- res.writeHead(302, {
- Location: signOutUrl,
- 'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
- });
- res.end();
-
- } else {
- res.writeHead(404);
- res.end('Not found');
- }
- } catch {
- res.writeHead(500);
- res.end('Internal server error');
- }
- });
-
- server.listen(PORT, () => {
- console.log(`Server running on http://localhost:${PORT}`);
- });
+ return { sku, inStock: true };
}
-main();
+const item = await getStock('SKU-100');
+console.log(item);
```
-## Run Your App
+## Run Your Service
-Start the server:
+Start the script:
- node index.js
+ node index.mjs
- yarn node index.js
+ yarn node index.mjs
- pnpm node index.js
+ pnpm node index.mjs
-Open [http://localhost:3000](http://localhost:3000).
-
-:::note Test credentials
-You'll need a user to sign in with. If you haven't created one yet, open , navigate to **Users**, and add a test user with an email and password.
-:::
-
:::tip Success
-You should see the sign-in link. Click it to be redirected to the -hosted sign-in page. After authenticating with your test user, you'll return to the `/profile` route with your user profile displayed.
+The script authenticates once, prints the scope it received, then calls `getStock()` and prints the result before exiting.
:::
@@ -418,11 +206,11 @@ You should see the sign-in link. Click it to be redirected to the
-
+
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/android/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/android/redirect-based.txt
new file mode 100644
index 0000000000..2affef1349
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/android/redirect-based.txt
@@ -0,0 +1,36 @@
+# Integrate {{productName}} Authentication in Android Application
+
+## Context
+I have an Android application (Kotlin, Jetpack Compose) and I want to integrate {{productName}}'s authentication system using the {{productName}} Android SDK, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `dev.thunderid:compose` Gradle dependency for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` composables
+- Guard content with the `SignedIn`/`SignedOut` composables
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` composable
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: dev.thunderid:compose
+
+## IMPORTANT Configuration Rules
+- Add the {{productName}} Maven repository (`https://maven.thunderid.dev/releases`) to `settings.gradle.kts` before adding the dependency
+- Wrap the content passed to `setContent` with the `ThunderIDProvider` composable, configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via `LocalThunderID.current`, not a separate hook or singleton
+- Required scopes: at minimum `"openid"`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Android Studio project using the Empty Activity template with Kotlin and Jetpack Compose
+2. Add the {{productName}} Maven repository to `settings.gradle.kts`
+3. Add the `dev.thunderid:compose` dependency to `build.gradle.kts`
+4. Wrap your `setContent` content with `ThunderIDProvider`, configured with `baseUrl`, `scopes`, and `applicationId`
+5. Build a root composable that checks `thunder.isInitialized` and renders a `SignedIn`/fallback split
+6. Build an auth screen using the `SignIn` composable, passing the `applicationId`
+7. Build a home screen that reads `LocalThunderID.current.user` to display the signed-in user's name and email, with a `SignOutButton`
+8. Run the app on an API 24+ emulator or device
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} Android SDK for Jetpack Compose.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/embedded.txt
new file mode 100644
index 0000000000..b3a5777548
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/embedded.txt
@@ -0,0 +1,36 @@
+# Integrate {{productName}} Authentication in Vanilla JavaScript Application (Custom UI Mode)
+
+## Context
+I have a vanilla JavaScript application and I want to integrate {{productName}}'s authentication system using the ThunderID Browser SDK's embedded flow functions, rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/browser SDK for authentication
+- Use `executeEmbeddedSignInFlow` (re-exported by @thunderid/browser from the core @thunderid/javascript client) to drive the sign-in flow directly, instead of `ThunderIDBrowserClient.signIn()` (which is redirect-only)
+- Render your own sign-in form driven by the flow response's `components` array
+- Display signed-in user's profile information
+- Handle authentication state
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/browser
+
+## IMPORTANT Configuration Rules
+- `@thunderid/browser` has NO bundled ``-style DOM component — it is framework-agnostic with no rendering layer. Build the form yourself from the flow response
+- Call `executeEmbeddedSignInFlow({applicationId, baseUrl, flowType: 'AUTHENTICATION'})` to start the flow
+- Each step returns `components` describing the fields to render next (e.g. identifier, password, OTP); render inputs for those fields, then call `executeEmbeddedSignInFlow` again with the same `executionId` and the user's submitted `inputs` to advance the flow
+- Repeat until the flow response indicates completion (tokens/session issued)
+- NEVER call `ThunderIDBrowserClient.signIn()` or navigate to a {{productName}}-hosted URL in this mode — that redirects the browser instead of using the embedded flow
+- Still use `ThunderIDBrowserClient.initialize(config)` for session/token management (`isSignedIn()`, `getUser()`, `getAccessToken()`, `signOut()`) once the embedded flow completes
+
+## Implementation Steps
+1. Create a vanilla JS app using Vite by running: `npm create vite@latest js-demo -- --template vanilla`
+2. Navigate into the project directory: `cd js-demo`
+3. Install dependencies: `npm install`
+4. Install @thunderid/browser package: `npm install @thunderid/browser`
+5. Create src/auth.js to initialize ThunderIDBrowserClient with applicationId and baseUrl
+6. Create a sign-in form in src/main.js; on submit, call `executeEmbeddedSignInFlow` and render whatever `components` the response specifies for the next step
+7. Once the flow completes, use `isSignedIn()`/`getUser()` to show the signed-in profile, and `signOut()` for sign-out
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code for {{productName}} authentication using the ThunderID Browser SDK's embedded flow functions, with a custom sign-in form (not a redirect).
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/redirect-based.txt
new file mode 100644
index 0000000000..f5022077b9
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/browser/redirect-based.txt
@@ -0,0 +1,57 @@
+# Integrate {{productName}} Authentication in Vanilla JavaScript Application
+
+## Context
+I have a vanilla JavaScript application and I want to integrate {{productName}}'s authentication system using the ThunderID Browser SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/browser SDK for authentication
+- Configure {{productName}}-hosted login pages
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+- Handle authentication state
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/browser
+
+## IMPORTANT Configuration Rules
+- Create a ThunderIDBrowserClient instance and call initialize() with a config object
+- Required config properties: `clientId` and `baseUrl`
+- Optional config properties: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array), `storage`
+- Storage options: 'sessionStorage' (default), 'localStorage', 'browserMemory'
+- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`
+
+## Available SDK APIs
+
+### ThunderIDBrowserClient
+- `initialize(config)` - Initialize the client with configuration
+- `signIn()` - Redirect to {{productName}} sign-in page
+- `signOut()` - Sign out and clear session
+- `isSignedIn()` - Check if user is signed in (returns Promise)
+- `getUser()` - Get authenticated user profile (returns Promise)
+- `getAccessToken()` - Get current access token
+- `getIdToken()` - Get ID token
+- `getDecodedIdToken()` - Get decoded ID token claims
+- `httpRequest(config)` - Make authenticated HTTP request
+- `on(hook, callback)` - Register event callbacks (Hooks.SignIn, Hooks.SignOut, etc.)
+
+### User Object Properties
+- `displayName` - User's display name
+- `username` - Username
+- `email` - Email address
+- `given_name` - First name
+- `family_name` - Last name
+- `picture` - Profile picture URL
+
+## Implementation Steps
+1. Create a vanilla JS app using Vite by running: `npm create vite@latest js-demo -- --template vanilla`
+2. Navigate into the project directory: `cd js-demo`
+3. Install dependencies: `npm install`
+4. Install @thunderid/browser package: `npm install @thunderid/browser`
+5. Create src/auth.js to initialize ThunderIDBrowserClient with clientId and baseUrl
+6. Update src/main.js to check isSignedIn(), show sign-in button or user profile
+7. Add event listeners for sign-in and sign-out buttons
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID Browser SDK.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/embedded.txt
new file mode 100644
index 0000000000..9ca1b63f34
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/embedded.txt
@@ -0,0 +1,47 @@
+# Integrate {{productName}} Authentication in an Express Application (Custom UI / Embedded Mode)
+
+## Context
+I have an Express application and I want to integrate {{productName}} authentication using the ThunderID Express SDK in embedded (app-native) mode, rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use `@thunderid/express` in a Node.js + Express application
+- Use `cookie-parser` and `express.json()` middleware
+- Configure ThunderID middleware with `baseUrl`, `applicationId`, and `mode: 'embedded'`
+- Add a `/flow/sign-in` route using `handleFlow()` that proxies each step of the flow to the Flow Execution API
+- Serve a `/login` page with a plain HTML form that POSTs to `/flow/sign-in` and re-renders whatever fields the flow response asks for next
+- Add `/logout` with `handleSignOut()`
+- Protect a route (`/protected`) using `protect((res) => res.redirect('/login'))`
+- Add a `/me` route that returns the authenticated user profile as JSON
+- Keep the code minimal, production-lean, and fully runnable
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Sign-In Page**: `/login`
+- **Flow Route**: `/flow/sign-in`
+- **Logout Callback Route**: `/logout`
+- **SDK**: @thunderid/express
+
+## Important Rules
+- Use CommonJS syntax (`require`) in `index.js`
+- `@thunderid/express` has NO bundled sign-in UI component — this is a server-side SDK. `mode: 'embedded'` only changes what `handleFlow()` proxies to; you must write the actual HTML/JS form yourself
+- Set `mode: 'embedded'` in the `thunderID({...})` middleware config (defaults to `'redirect'` if omitted)
+- `handleFlow()` calls the Flow Execution API on each POST and returns the flow's JSON step (`executionId`, `challengeToken`, `authId`, `components`) — render whatever `components` the response contains as form fields, and POST the user's input back to the same route with the returned `executionId`
+- Do not invent unsupported SDK APIs or custom wrappers
+- Do not use `clientId`/`clientSecret` in this mode — embedded mode is driven by `applicationId`, not an OAuth2 client
+
+## Implementation Steps
+1. Create a new project and install `express` and `cookie-parser`
+2. Install `@thunderid/express`
+3. Add `index.js` with `thunderID({ applicationId, baseUrl, mode: 'embedded' })` middleware
+4. Add `/login` route serving a minimal HTML form, and `/flow/sign-in` using `handleFlow()`
+5. Add `/logout`, `/protected`, and `/me` routes
+6. Start the server with `node index.js`
+7. Validate the flow by opening `/login`, submitting the form, then `/protected` and `/me`
+
+Please provide:
+- The exact terminal commands
+- A complete `index.js` file
+- A minimal HTML sign-in form driven by the flow's `components` response (not a redirect)
+- A short verification checklist for sign-in, sign-out, and protected route behavior
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/redirect-based.txt
new file mode 100644
index 0000000000..e2aeaea4d6
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/express/redirect-based.txt
@@ -0,0 +1,42 @@
+# Integrate {{productName}} Authentication in an Express Application (Inbuilt Mode)
+
+## Context
+I have an Express application and I want to integrate {{productName}} authentication using the ThunderID Express SDK with {{productName}}-hosted sign-in pages.
+
+## Requirements
+- Use `@thunderid/express` in a Node.js + Express application
+- Use `cookie-parser` and `express.json()` middleware
+- Configure ThunderID middleware with `baseUrl`, `clientId`, `clientSecret`, `afterSignInUrl`, and `afterSignOutUrl`
+- Implement `/login` with `handleSignIn()` and `/logout` with `handleSignOut()`
+- Protect a route (`/protected`) using `protect((res) => res.redirect('/login'))`
+- Add a `/me` route that returns the authenticated user profile as JSON
+- Keep the code minimal, production-lean, and fully runnable
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Client Secret**: ``
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Login Callback Route**: `/login`
+- **Logout Callback Route**: `/logout`
+- **SDK**: @thunderid/express
+
+## Important Rules
+- Use CommonJS syntax (`require`) in `index.js`
+- Use these SDK imports exactly: `thunderID`, `handleSignIn`, `handleSignOut`, `protect`
+- Ensure redirect URL alignment: the app callback URL must be `http://localhost:3000/login`, and the post-logout redirect URL must be `http://localhost:3000/logout`
+- Do not invent unsupported SDK APIs or custom wrappers
+- Keep route names and behavior exactly as specified
+
+## Implementation Steps
+1. Create a new project and install `express` and `cookie-parser`
+2. Install `@thunderid/express`
+3. Add `index.js` with ThunderID middleware and auth routes
+4. Add `/protected` and `/me` routes with `protect()`
+5. Start the server with `node index.js`
+6. Validate the flow by opening `/protected`, then `/me`
+
+Please provide:
+- The exact terminal commands
+- A complete `index.js` file
+- A short verification checklist for sign-in, sign-out, and protected route behavior
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/flutter/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/flutter/redirect-based.txt
new file mode 100644
index 0000000000..bb4da78d3e
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/flutter/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in Flutter Application
+
+## Context
+I have a Flutter application (Dart) and I want to integrate {{productName}}'s authentication system using the `thunderid_flutter` package, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `thunderid_flutter` package for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` widgets
+- Route between an auth screen and a home screen based on `thunder.isSignedIn`
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` widget
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: thunderid_flutter
+
+## IMPORTANT Configuration Rules
+- Wrap your root widget with `ThunderIDProvider`, configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via `ThunderIDProvider.of(context)`, not a separate hook or singleton
+- Required scopes: at minimum `'openid'`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Flutter project: `flutter create my_app`
+2. Add `thunderid_flutter` to `pubspec.yaml` and run `flutter pub get`
+3. Wrap your root widget with `ThunderIDProvider` in `lib/main.dart`, configured with `baseUrl`, `scopes`, and `applicationId`
+4. Build a root screen that checks `thunder.initialized`/`thunder.isLoading` and routes to an auth or home screen based on `thunder.isSignedIn`
+5. Build an auth screen using the `SignIn`/`SignUp` widgets, passing the `applicationId`
+6. Build a home screen that reads `thunder.user` to display the signed-in user's name and email, with a `SignOutButton`
+7. Run the app on an iOS simulator or Android device: `flutter run`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the `thunderid_flutter` package.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/ios/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/ios/redirect-based.txt
new file mode 100644
index 0000000000..755f828913
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/ios/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in iOS Application
+
+## Context
+I have an iOS application (Swift, SwiftUI) and I want to integrate {{productName}}'s authentication system using the {{productName}} iOS SDK, with sign-in and sign-up forms rendered natively in the app (app-native authentication).
+
+## Requirements
+- Use the `ThunderIDSwiftUI` Swift package for authentication
+- Use app-native authentication through the Flow Execution API (no OAuth 2.0 client ID, no redirect URI)
+- Implement sign-in and sign-up with the prebuilt `SignIn`/`SignUp` views
+- Guard content with the `SignedIn`/`SignedOut` views
+- Display the signed-in user's profile information
+- Implement sign-out with the prebuilt `SignOutButton` view
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: ThunderIDSwiftUI (Swift Package Manager) or ThunderIDSwiftUI CocoaPod
+
+## IMPORTANT Configuration Rules
+- Apply the `.thunderIDProvider(config:)` modifier to the root view in your app's entry point (the type conforming to `App`), configured with `ThunderIDConfig(baseUrl, scopes, applicationId)`
+- Access authentication state via an injected `ThunderIDState` `@EnvironmentObject`, not a separate hook or singleton
+- Required scopes: at minimum `"openid"`
+- Use `applicationId`, NOT a client ID or redirect URI — this SDK only supports app-native authentication
+
+## Implementation Steps
+1. Create a new Xcode project using the iOS > App template with SwiftUI and Swift
+2. Add the `ThunderIDSwiftUI` package via File > Add Package Dependencies (or the `ThunderIDSwiftUI` CocoaPod)
+3. Apply `.thunderIDProvider(config:)` to your root view, configured with `baseUrl`, `scopes`, and `applicationId`
+4. Build a root view that checks `state.isInitialized` and renders a `SignedIn`/fallback split
+5. Build an auth view using the `SignIn` view, passing the `applicationId`
+6. Build a home view that reads `state.user` to display the signed-in user's name and email, with a `SignOutButton`
+7. Run the app on an iOS 16+ simulator or device
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} iOS SDK for SwiftUI.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/embedded.txt
new file mode 100644
index 0000000000..cb7860e1cc
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/embedded.txt
@@ -0,0 +1,41 @@
+# Integrate {{productName}} Authentication in Next.js Application (Custom UI Mode)
+
+## Context
+I have a Next.js application (App Router) and I want to integrate {{productName}}'s authentication system using the {{productName}} Next.js SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/nextjs SDK for authentication
+- Render sign-in on a custom route (not a redirect to {{productName}}-hosted pages)
+- Use the App Router (not Pages Router)
+- Implement sign-in and sign-out with the SDK's own components
+- Add middleware for route protection and automatic token refresh
+- Display signed-in user's profile information
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Route**: /signin
+- **SDK**: @thunderid/nextjs
+
+## IMPORTANT Configuration Rules
+- Use environment variables for configuration (NOT props on the provider)
+- Required env vars: NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+- Import ThunderIDProvider from '@thunderid/nextjs/server' (NOT from '@thunderid/nextjs')
+- Import middleware utilities from '@thunderid/nextjs/server'
+- Import UI components (`SignedIn`, `SignedOut`, `SignIn`, `SignOutButton`, `UserDropdown`) from '@thunderid/nextjs'
+- The `` component drives the Flow Execution API directly (not an OAuth redirect) — render it on your custom `app/signin/page.tsx`
+- `` accepts an `onSuccess` callback (e.g. `router.push('/')`) and an `onError` callback
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Next.js app: npx create-next-app@latest nextjs-demo
+2. Navigate into the project: cd nextjs-demo
+3. Install @thunderid/nextjs: npm install @thunderid/nextjs
+4. Create .env.local with NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+5. Wrap root layout with from '@thunderid/nextjs/server'
+6. Create app/signin/page.tsx rendering ` router.push('/')} />` and `` for the already-authenticated case
+7. Create proxy.ts with thunderIDProxy and createRouteMatcher from '@thunderid/nextjs/server' for route protection
+8. Add SignedIn, UserDropdown, SignedOut, SignOutButton components to other pages
+9. Run: npm run dev
+
+Please provide complete, working code for {{productName}} authentication using the {{productName}} Next.js SDK, with a custom sign-in page rendering `` instead of a hosted-page redirect.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt
new file mode 100644
index 0000000000..4b5badc202
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nextjs/redirect-based.txt
@@ -0,0 +1,37 @@
+# Integrate {{productName}} Authentication in Next.js Application
+
+## Context
+I have a Next.js application (App Router) and I want to integrate {{productName}}'s authentication system using the {{productName}} Next.js SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/nextjs SDK for authentication
+- Configure {{productName}}-hosted login pages (not custom/embedded)
+- Use the App Router (not Pages Router)
+- Implement sign-in and sign-out with prebuilt components
+- Add middleware for route protection and automatic token refresh
+- Display signed-in user's profile information
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/nextjs
+
+## IMPORTANT Configuration Rules
+- Use environment variables for configuration (NOT props on the provider)
+- Required env vars: NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+- Import ThunderIDProvider from '@thunderid/nextjs/server' (NOT from '@thunderid/nextjs')
+- Import middleware utilities from '@thunderid/nextjs/server'
+- Import UI components from '@thunderid/nextjs'
+- The ThunderIDProvider handles the OAuth callback automatically — no manual callback route is needed
+
+## Implementation Steps
+1. Create a Next.js app: npx create-next-app@latest nextjs-demo
+2. Navigate into the project: cd nextjs-demo
+3. Install @thunderid/nextjs: npm install @thunderid/nextjs
+4. Create .env.local with NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0
+5. Wrap root layout with from '@thunderid/nextjs/server'
+6. Create proxy.ts with thunderIDProxy and createRouteMatcher from '@thunderid/nextjs/server' for route protection
+7. Add SignedIn, UserDropdown, SignedOut, SignInButton components to pages
+8. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} Next.js SDK.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/embedded.txt
new file mode 100644
index 0000000000..5b4a8e0c31
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/embedded.txt
@@ -0,0 +1,46 @@
+# Integrate {{productName}} Authentication in a Node.js Application (Custom UI / Embedded Mode)
+
+## Context
+I have a Node.js application and I want to integrate {{productName}} authentication using the @thunderid/node SDK's embedded flow functions and the built-in http module — no framework required — rendering my own sign-in form instead of redirecting to {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/node SDK with the built-in Node.js http module
+- Use `executeEmbeddedSignInFlow` (re-exported by @thunderid/node from the core @thunderid/javascript client) to drive the sign-in flow directly, instead of `ThunderIDNodeClient.signIn()` (which is redirect-only)
+- Implement a `/login` route that serves a minimal HTML form
+- Implement a `/flow/sign-in` route that calls `executeEmbeddedSignInFlow` on each POST and returns/renders whatever `components` the flow response asks for next
+- Implement a `/logout` route to sign out and clear the session cookie
+- Protect the `/profile` route using `isSignedIn()` and display user info with `getUser()`
+- Manage sessions using a session ID stored in an HttpOnly cookie
+- Keep code minimal and fully runnable with CommonJS `require()`
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Sign-In Page**: /login
+- **Flow Route**: /flow/sign-in
+- **SDK**: @thunderid/node
+
+## Important Rules
+- Use CommonJS syntax (require) in index.js
+- `ThunderIDNodeClient` itself only implements the redirect flow — for embedded mode, call `executeEmbeddedSignInFlow({applicationId, flowType: 'AUTHENTICATION', ...})` directly, imported from `@thunderid/node` (or `@thunderid/javascript`)
+- @thunderid/node has NO bundled sign-in UI — write the HTML/JS form yourself, rendering whatever fields the flow response's `components` array specifies
+- Each flow step returns an `executionId`; POST the next step's user input back to `/flow/sign-in` along with that `executionId` until the flow completes
+- Store the session ID in a cookie named 'tid_session' with HttpOnly and SameSite=Lax flags
+- Use randomUUID() from the built-in 'crypto' module to generate session IDs
+- Use isSignedIn(sessionId) to guard protected routes
+- Use getUser(sessionId) to retrieve the authenticated user profile
+- Use signOut(sessionId) to clear the local session
+
+## Implementation Steps
+1. Create a new project: mkdir my-node-app && cd my-node-app && npm init -y
+2. Install @thunderid/node: npm install @thunderid/node
+3. Create index.js importing `executeEmbeddedSignInFlow` from @thunderid/node
+4. Add /login route serving a minimal HTML form
+5. Add /flow/sign-in route: call executeEmbeddedSignInFlow with the applicationId and the user's submitted inputs, and set the session cookie once the flow completes
+6. Add /logout route: clear the session cookie
+7. Add / route: show sign-in or profile link based on isSignedIn()
+8. Add /profile route: guard with isSignedIn(), display user info from getUser()
+9. Start the server: node index.js
+
+Please provide a complete, working index.js file with all routes, driven by the embedded flow functions (not a redirect).
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/redirect-based.txt
new file mode 100644
index 0000000000..2e178b0b9a
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/node/redirect-based.txt
@@ -0,0 +1,45 @@
+# Integrate {{productName}} Authentication in a Node.js Application
+
+## Context
+I have a Node.js application and I want to integrate {{productName}} authentication using the @thunderid/node SDK and the built-in http module — no framework required.
+
+## Requirements
+- Use @thunderid/node SDK with the built-in Node.js http module
+- Initialize ThunderIDNodeClient with clientId, clientSecret, baseUrl, afterSignInUrl, afterSignOutUrl
+- Implement /login route to start the sign-in flow (redirects to {{productName}})
+- Implement /callback route to handle the OAuth authorization code exchange
+- Implement /logout route to sign out and clear the session cookie
+- Protect the /profile route using isSignedIn() and display user info with getUser()
+- Manage sessions using a session ID stored in an HttpOnly cookie
+- Keep code minimal and fully runnable with CommonJS require()
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Client Secret**: ``
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **App URL**: http://localhost:3000
+- **Callback Route**: /callback
+- **SDK**: @thunderid/node
+
+## Important Rules
+- Use CommonJS syntax (require) in index.js
+- Initialize ThunderIDNodeClient with auth.initialize({...}) before starting the server
+- signIn() works in two phases: first call redirects the user (authUrlCallback), second call (with code+state) exchanges the token
+- Store the session ID in a cookie named 'tid_session' with HttpOnly and SameSite=Lax flags
+- Use randomUUID() from the built-in 'crypto' module to generate session IDs
+- Use isSignedIn(sessionId) to guard protected routes
+- Use getUser(sessionId) to retrieve the authenticated user profile
+- Use signOut(sessionId) to get the OIDC end-session URL, then clear the local cookie and redirect
+
+## Implementation Steps
+1. Create a new project: mkdir my-node-app && cd my-node-app && npm init -y
+2. Install @thunderid/node: npm install @thunderid/node
+3. Create index.js with ThunderIDNodeClient initialization
+4. Add /login route: generate session ID cookie and redirect to {{productName}} auth URL
+5. Add /callback route: exchange authorization code for tokens using signIn()
+6. Add /logout route: call signOut() to get end-session URL, clear cookie, redirect
+7. Add / route: show sign-in or profile link based on isSignedIn()
+8. Add /profile route: guard with isSignedIn(), display user info from getUser()
+9. Start the server: node index.js
+
+Please provide a complete, working index.js file with all routes and the ThunderIDNodeClient wired up correctly.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/embedded.txt
new file mode 100644
index 0000000000..2ef19c0dae
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/embedded.txt
@@ -0,0 +1,44 @@
+# Integrate {{productName}} Authentication in Nuxt 3 Application (Custom UI Mode)
+
+## Context
+I have a Nuxt 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/nuxt module with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/nuxt module for authentication
+- Register the module in nuxt.config.ts
+- Configure via environment variables (no inline config)
+- Wrap app.vue content with
+- Render sign-in on a custom page using the module's own SignIn component (not a redirect to {{productName}}-hosted pages)
+- Display signed-in user's profile information
+- Optionally protect pages with the built-in thunderIDMiddleware
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Page**: /signin (custom page)
+- **SDK**: @thunderid/nuxt
+
+## IMPORTANT Configuration Rules
+- Add '@thunderid/nuxt' to the modules array in nuxt.config.ts — no other config needed there
+- All configuration is read from environment variables with NUXT_PUBLIC_ prefix for public values
+- Required env vars: NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SESSION_SECRET (without the NUXT_PUBLIC_ prefix)
+- Wrap with in app.vue
+- All components (`SignedIn`, `SignedOut`, `SignIn`, `SignOutButton`, `User`) and composables are auto-imported — no manual import needed
+- The `` component mirrors the Vue SDK's `SignIn`/`BaseSignIn` and drives the embedded (app-native) Flow Execution API directly — it replaces `window.location` navigation with Nuxt's `navigateTo` internally, so no manual redirect handling is needed
+- Render `` inside `` on pages/signin.vue
+- Protect pages by adding definePageMeta({ middleware: ['thunderIDMiddleware'] })
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Nuxt 3 app: npx nuxi@latest init my-nuxt-app
+2. Navigate into the project: cd my-nuxt-app && npm install
+3. Install @thunderid/nuxt: npm install @thunderid/nuxt
+4. Add '@thunderid/nuxt' to modules in nuxt.config.ts
+5. Create .env with NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_APPLICATION_ID, THUNDERID_SESSION_SECRET
+6. Wrap with in app.vue
+7. Create pages/signin.vue rendering ``
+8. Create pages/index.vue with SignedIn, SignOutButton, and User components
+9. Optionally add definePageMeta({ middleware: ['thunderIDMiddleware'] }) to protected pages
+10. Run: npm run dev
+
+Please provide complete, working code for {{productName}} authentication using the @thunderid/nuxt module, with a custom sign-in page rendering `` instead of a hosted-page redirect.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt
new file mode 100644
index 0000000000..dff4a6bed7
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/nuxt/redirect-based.txt
@@ -0,0 +1,42 @@
+# Integrate {{productName}} Authentication in Nuxt 3 Application
+
+## Context
+I have a Nuxt 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/nuxt module with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/nuxt module for authentication
+- Register the module in nuxt.config.ts
+- Configure via environment variables (no inline config)
+- Wrap app.vue content with
+- Implement sign-in and sign-out with auto-imported components
+- Display signed-in user's profile information
+- Optionally protect pages with the built-in thunderIDMiddleware
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Callback URL**: http://localhost:3000/api/auth/callback (auto-registered by the module)
+- **SDK**: @thunderid/nuxt
+
+## IMPORTANT Configuration Rules
+- Add '@thunderid/nuxt' to the modules array in nuxt.config.ts — no other config needed there
+- All configuration is read from environment variables with NUXT_PUBLIC_ prefix for public values
+- Required env vars: NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET
+- THUNDERID_CLIENT_SECRET and THUNDERID_SESSION_SECRET must NOT have the NUXT_PUBLIC_ prefix
+- The /api/auth/callback route is auto-registered by the module — do NOT create it manually
+- Wrap with in app.vue
+- All components (SignedIn, SignedOut, SignInButton, SignOutButton, User) and composables are auto-imported
+- Protect pages by adding definePageMeta({ middleware: ['thunderIDMiddleware'] })
+
+## Implementation Steps
+1. Create a Nuxt 3 app: npx nuxi@latest init my-nuxt-app
+2. Navigate into the project: cd my-nuxt-app && npm install
+3. Install @thunderid/nuxt: npm install @thunderid/nuxt
+4. Add '@thunderid/nuxt' to modules in nuxt.config.ts
+5. Create .env with NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET
+6. Wrap with in app.vue
+7. Create pages/index.vue with SignInButton, SignOutButton, SignedIn, SignedOut, and User components
+8. Optionally add definePageMeta({ middleware: ['thunderIDMiddleware'] }) to protected pages
+9. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the @thunderid/nuxt module.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/embedded.txt
new file mode 100644
index 0000000000..af5c0ed6d2
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/embedded.txt
@@ -0,0 +1,45 @@
+# Integrate {{productName}} Authentication in React Application (Custom UI Mode)
+
+## Context
+I have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/react SDK for authentication
+- Render sign-in on a custom route (not a redirect to {{productName}}-hosted pages)
+- Use react-router for routing to the custom sign-in page
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+- Handle authentication state automatically
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In URL**: http://localhost:5173/signin (custom route)
+- **SDK**: @thunderid/react
+- **Router**: react-router
+
+## IMPORTANT Configuration Rules
+- DO NOT create a separate config object - pass all configuration as individual props directly to
+- Required props: `baseUrl`, `signInUrl`, and `applicationId`
+- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes`
+- Example:
+- The `` component drives the Flow Execution API directly (not a redirect) — render it on your custom sign-in route
+- `` accepts `onSuccess`/`onError` callbacks, and optionally a render-prop `children` function receiving `{components, onSubmit, isLoading, error, isInitialized}` for a fully custom form
+- Access auth state via `useThunderID()` (ONLY hook available: exposes `isSignedIn`, `user`, `signIn`, `signOut`, `signUp`) — no other hooks like `useAuth`/`useSession`/`useUser` exist
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`
+2. Navigate into the project directory: `cd my-react-app`
+3. Install dependencies: `npm install`
+4. Install react-router package: `npm install react-router`
+5. Install @thunderid/react package: `npm install @thunderid/react`
+6. Wrap your app with and configure baseUrl, signInUrl, and applicationId
+7. Set up React Router with BrowserRouter, create a /signin route rendering ``, and use , , , , and for the rest of the authentication UI
+8. Run the development server: `npm run dev`
+
+Please provide complete, working code with:
+- Proper routing configuration
+- A custom sign-in page rendering `` (not a redirect)
+- Proper integration with the ThunderID React SDK
+- Use of the `useThunderID` hook if programmatic access to auth state is needed
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/redirect-based.txt
new file mode 100644
index 0000000000..8c4958ff6a
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/react/redirect-based.txt
@@ -0,0 +1,34 @@
+# Integrate {{productName}} Authentication in React Application (Inbuilt Mode)
+
+## Context
+I have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/react SDK for authentication
+- Configure {{productName}}-hosted login, registration, and account management UIs
+- Implement sign-in and sign-out functionality using prebuilt components
+- Display signed-in user's profile information
+- Handle authentication state automatically
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/react
+
+## IMPORTANT Configuration Rules
+- DO NOT create a separate config object - pass all configuration as individual props directly to
+- Required props: `clientId` and `baseUrl`
+- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array)
+- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`
+- Example:
+
+## Implementation Steps
+1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`
+2. Navigate into the project directory: `cd my-react-app`
+3. Install dependencies: `npm install`
+4. Install @thunderid/react package: `npm install @thunderid/react`
+5. Wrap your app with and configure clientId and baseUrl
+6. Build with ThunderID components: use , , , and to control what signed-in and signed-out users see
+7. Run the development server: `npm run dev`
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID React SDK.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/embedded.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/embedded.txt
new file mode 100644
index 0000000000..17e76530ec
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/embedded.txt
@@ -0,0 +1,40 @@
+# Integrate {{productName}} Authentication in Vue 3 Application (Custom UI Mode)
+
+## Context
+I have a Vue 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/vue SDK with a custom sign-in UI instead of {{productName}}-hosted pages.
+
+## Requirements
+- Use @thunderid/vue SDK for authentication
+- Register the ThunderIDPlugin in main.js
+- Render sign-in on a custom route using the SDK's own SignIn component (not a redirect to {{productName}}-hosted pages)
+- Use vue-router for routing to the custom sign-in page
+- Implement sign-in and sign-out functionality
+- Display signed-in user's profile information
+
+## Configuration
+- **Application ID**: {{applicationId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **Sign In Route**: /signin (custom route)
+- **SDK**: @thunderid/vue
+
+## IMPORTANT Configuration Rules
+- Register ThunderIDPlugin via `app.use(ThunderIDPlugin)` in src/main.js
+- Import ThunderIDPlugin from '@thunderid/vue'
+- Wrap the app root in App.vue with ``
+- Pass configuration as kebab-case attributes: `application-id` and `base-url`
+- The `` component drives the Flow Execution API directly (not a redirect) — render it on your custom /signin route, inside ``
+- `` emits `@success`/`@error` events
+- Access auth state via the `useThunderID()` composable (`isSignedIn`, `user`, `signIn`, `signOut`, `signUp`)
+- NEVER redirect to a {{productName}}-hosted URL for sign-in in this mode
+
+## Implementation Steps
+1. Create a Vue 3 app: npm create vite@latest my-vue-app -- --template vue
+2. Navigate into the project: cd my-vue-app && npm install
+3. Install @thunderid/vue and vue-router: npm install @thunderid/vue vue-router
+4. Register ThunderIDPlugin in src/main.js with app.use(ThunderIDPlugin)
+5. Wrap app content with in src/App.vue
+6. Set up vue-router with a /signin route rendering ``
+7. Use , , and for the rest of the authentication UI
+8. Run: npm run dev
+
+Please provide complete, working code with proper routing, a custom sign-in page rendering `` (not a redirect), and use of the `useThunderID()` composable where programmatic access to auth state is needed.
diff --git a/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/redirect-based.txt b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/redirect-based.txt
new file mode 100644
index 0000000000..80600c78e2
--- /dev/null
+++ b/docs/versioned_docs/version-v1.0.x/getting-started/connect-your-application/prompts/vue/redirect-based.txt
@@ -0,0 +1,35 @@
+# Integrate {{productName}} Authentication in Vue 3 Application
+
+## Context
+I have a Vue 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/vue SDK with {{productName}}-hosted login pages.
+
+## Requirements
+- Use @thunderid/vue SDK for authentication
+- Register the ThunderIDPlugin in main.js
+- Wrap the app with ThunderIDProvider in App.vue
+- Implement sign-in and sign-out with prebuilt components
+- Display signed-in user's profile using the UserDropdown component
+
+## Configuration
+- **Client ID**: {{clientId}}
+- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)
+- **SDK**: @thunderid/vue
+
+## IMPORTANT Configuration Rules
+- Register ThunderIDPlugin via app.use(ThunderIDPlugin) in src/main.js
+- Import ThunderIDPlugin from '@thunderid/vue'
+- Wrap the app root in App.vue with
+- Pass configuration as kebab-case attributes: `client-id` and `base-url`
+- Import UI components (SignInButton, SignOutButton, SignedIn, SignedOut, UserDropdown) from '@thunderid/vue'
+- Use inside to display the signed-in user's profile and sign-out option
+
+## Implementation Steps
+1. Create a Vue 3 app: npm create vite@latest my-vue-app -- --template vue
+2. Navigate into the project: cd my-vue-app && npm install
+3. Install @thunderid/vue: npm install @thunderid/vue
+4. Register ThunderIDPlugin in src/main.js with app.use(ThunderIDPlugin)
+5. Wrap app content with in src/App.vue
+6. Add SignInButton inside SignedOut and UserDropdown inside SignedIn conditional wrappers
+7. Run: npm run dev
+
+Please provide complete, working code with proper configuration for {{productName}} authentication using the @thunderid/vue SDK.
diff --git a/frontend/apps/console/public/config.js b/frontend/apps/console/public/config.js
index ca7c440f9e..24bfe9e681 100644
--- a/frontend/apps/console/public/config.js
+++ b/frontend/apps/console/public/config.js
@@ -10,7 +10,7 @@ window.__THUNDERID_RUNTIME_CONFIG__ = {
},
},
documentation: {
- baseUrl: 'https://thunderid.dev/docs/next',
+ baseUrl: 'https://thunderid.dev/docs/v1.0.x',
releasesUrl: 'https://thunderid.dev/data/releases.json',
links: {
users: '',
@@ -24,6 +24,65 @@ window.__THUNDERID_RUNTIME_CONFIG__ = {
'verifiableCredentials.presentations': '',
settings: '',
importExport: '',
+ 'applications.templates.react.docs': 'getting-started/connect-your-application/react/',
+ 'applications.templates.react.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/react/quickstart?file=README.md',
+ 'applications.templates.react.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/react/redirect-based.txt',
+ 'applications.templates.react.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/react/embedded.txt',
+ 'applications.templates.nextjs.docs': 'guides/getting-started/connect-your-application/nextjs/',
+ 'applications.templates.nextjs.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/nextjs/quickstart?file=README.md',
+ 'applications.templates.nextjs.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/nextjs/redirect-based.txt',
+ 'applications.templates.nextjs.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/nextjs/embedded.txt',
+ 'applications.templates.nuxt.docs': 'guides/getting-started/connect-your-application/nuxt/',
+ 'applications.templates.nuxt.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/nuxt/quickstart?file=README.md',
+ 'applications.templates.nuxt.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/nuxt/redirect-based.txt',
+ 'applications.templates.nuxt.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/nuxt/embedded.txt',
+ 'applications.templates.vue.docs': 'guides/getting-started/connect-your-application/vue/',
+ 'applications.templates.vue.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/vue/quickstart?file=README.md',
+ 'applications.templates.vue.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/vue/redirect-based.txt',
+ 'applications.templates.vue.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/vue/embedded.txt',
+ 'applications.templates.express.docs': 'guides/getting-started/connect-your-application/express/',
+ 'applications.templates.express.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/express/quickstart?file=README.md',
+ 'applications.templates.express.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/express/redirect-based.txt',
+ 'applications.templates.express.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/express/embedded.txt',
+ 'applications.templates.node.docs': 'guides/getting-started/connect-your-application/node/',
+ 'applications.templates.node.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/node/quickstart?file=README.md',
+ 'applications.templates.node.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/node/redirect-based.txt',
+ 'applications.templates.node.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/node/embedded.txt',
+ 'applications.templates.browser.docs': 'guides/getting-started/connect-your-application/browser/',
+ 'applications.templates.browser.playground':
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/browser/quickstart?file=README.md',
+ 'applications.templates.browser.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/browser/redirect-based.txt',
+ 'applications.templates.browser.llmPrompt.embedded':
+ 'getting-started/connect-your-application/prompts/browser/embedded.txt',
+ 'applications.templates.android.docs': 'guides/getting-started/connect-your-application/android/',
+ 'applications.templates.android.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/android/redirect-based.txt',
+ 'applications.templates.ios.docs': 'guides/getting-started/connect-your-application/ios/',
+ 'applications.templates.ios.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/ios/redirect-based.txt',
+ 'applications.templates.flutter.docs': 'guides/getting-started/connect-your-application/flutter/',
+ 'applications.templates.flutter.llmPrompt.redirectBased':
+ 'getting-started/connect-your-application/prompts/flutter/redirect-based.txt',
+ 'applications.templates.mcpClient.docs': 'getting-started/connect-your-mcp/python/',
},
},
client: {
diff --git a/frontend/apps/console/src/components/GatePreview/GatePreview.tsx b/frontend/apps/console/src/components/GatePreview/GatePreview.tsx
index 5ea767ed9f..710b55782a 100644
--- a/frontend/apps/console/src/components/GatePreview/GatePreview.tsx
+++ b/frontend/apps/console/src/components/GatePreview/GatePreview.tsx
@@ -94,6 +94,13 @@ export interface GatePreviewProps {
* its container edge to edge. Useful when the host provides its own window chrome.
*/
frameless?: boolean;
+ /**
+ * The device chrome drawn around the preview. `'browser'` (default) renders a desktop browser
+ * window (traffic-light dots + fake address bar). `'phone'` renders a dark rounded phone bezel
+ * with a status-bar notch instead, for previews of app-native (embedded) sign-in flows. Has no
+ * effect when `frameless` is set.
+ */
+ frameStyle?: 'browser' | 'phone';
/** Base theme the resolved design is merged over. Defaults to Acrylic Orange. */
baseTheme?: Theme;
/**
@@ -140,6 +147,7 @@ export default function GatePreview({
onComponentHover = undefined,
additionalData = undefined,
frameless = false,
+ frameStyle = 'browser',
baseTheme = undefined,
themelessBranding = false,
toolbarStart = undefined,
@@ -312,23 +320,30 @@ export default function GatePreview({
overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
- alignItems: 'flex-start',
+ alignItems: 'center',
p: frameless ? 0 : 2,
}}
>
{/* Browser chrome */}
- {!frameless && (
+ {!frameless && frameStyle === 'browser' && (
)}
- {/* Canvas — fills the browser chrome frame like a real viewport */}
+ {/* Phone chrome — a status-bar notch instead of a browser address bar */}
+ {!frameless && frameStyle === 'phone' && (
+
+
+
+ )}
+
+ {/* Canvas — fills the chrome frame like a real viewport */}
{!frameless && (
diff --git a/frontend/apps/console/src/features/applications/components/common/CopyableField.tsx b/frontend/apps/console/src/features/applications/components/common/CopyableField.tsx
index bd391a8623..e28be12fb3 100644
--- a/frontend/apps/console/src/features/applications/components/common/CopyableField.tsx
+++ b/frontend/apps/console/src/features/applications/components/common/CopyableField.tsx
@@ -34,10 +34,9 @@ export interface CopyableFieldProps {
}
/**
- * Read-only monospace field with a copy-to-clipboard affordance, matching
- * `QuickCopySection`'s field pattern. Shared by the mcp-client template's create-flow
- * Connect completion screen and edit-page Connect tab for the Application ID, Client ID,
- * client secret, and discovery endpoint fields.
+ * Read-only monospace field with a copy-to-clipboard affordance. Shared by the mcp-client
+ * template's create-flow Connect completion screen and edit-page Connect tab for the
+ * Application ID, Client ID, client secret, and discovery endpoint fields.
*
* @param props - The component props
* @param props.id - The `id`/`htmlFor` used to associate the field's label with its input
diff --git a/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx b/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
index 21b7b80c65..0aca1888e4 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/ConfigureDesign.tsx
@@ -306,14 +306,14 @@ export default function ConfigureDesign({
redirect-only and have no choice to make here. */}
{showApproachSection &&
(() => {
- const isInbuiltSelected = selectedApproach === ApplicationCreateFlowSignInApproach.INBUILT;
+ const isInbuiltSelected = selectedApproach === ApplicationCreateFlowSignInApproach.REDIRECT_BASED;
const isEmbeddedSelected = selectedApproach === ApplicationCreateFlowSignInApproach.EMBEDDED;
const hostedPagesCard = (
onApproachChange(ApplicationCreateFlowSignInApproach.INBUILT)}
+ onClick={() => onApproachChange(ApplicationCreateFlowSignInApproach.REDIRECT_BASED)}
sx={{borderRadius: '14px'}}
>
}
label=""
sx={{m: 0, mt: 0.25}}
diff --git a/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDesign.test.tsx b/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDesign.test.tsx
index 9260049478..04eb4ea055 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDesign.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDesign.test.tsx
@@ -26,7 +26,7 @@ describe('ConfigureDesign', () => {
const defaultProps: ConfigureDesignProps = {
onThemeSelect: mockOnThemeSelect,
onLayoutSelect: mockOnLayoutSelect,
- selectedApproach: ApplicationCreateFlowSignInApproach.INBUILT,
+ selectedApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
onApproachChange: vi.fn(),
showApproachSection: false,
};
@@ -102,7 +102,7 @@ describe('ConfigureDesign', () => {
renderComponent({
showApproachSection: true,
- selectedApproach: ApplicationCreateFlowSignInApproach.INBUILT,
+ selectedApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
onApproachChange,
});
@@ -115,7 +115,7 @@ describe('ConfigureDesign', () => {
renderComponent({
showApproachSection: true,
allowEmbeddedApproach: false,
- selectedApproach: ApplicationCreateFlowSignInApproach.INBUILT,
+ selectedApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
});
expect(screen.getByText(/Redirect to .* Gate/)).toBeInTheDocument();
diff --git a/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDetails.test.tsx b/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDetails.test.tsx
index 6b5bafb38c..23f64a5a68 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDetails.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/__tests__/ConfigureDetails.test.tsx
@@ -67,7 +67,7 @@ const defaultProps: Parameters[0] = {
onHostingUrlChange: vi.fn(),
onCallbackUrlChange: vi.fn(),
onReadyChange: vi.fn(),
- selectedApproach: ApplicationCreateFlowSignInApproach.INBUILT,
+ selectedApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
};
const renderWithContext = (
@@ -895,7 +895,7 @@ describe('ConfigureDetails', () => {
{
technology: TechnologyApplicationTemplate.REACT,
platform: PlatformApplicationTemplate.BROWSER,
- selectedApproach: ApplicationCreateFlowSignInApproach.INBUILT,
+ selectedApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
},
{selectedTemplateConfig: template},
);
diff --git a/frontend/apps/console/src/features/applications/components/create-application/__tests__/IntegrationGuide.test.tsx b/frontend/apps/console/src/features/applications/components/create-application/__tests__/IntegrationGuide.test.tsx
index 2d2d7d9941..afa7d493d8 100644
--- a/frontend/apps/console/src/features/applications/components/create-application/__tests__/IntegrationGuide.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/create-application/__tests__/IntegrationGuide.test.tsx
@@ -93,16 +93,8 @@ describe('IntegrationGuide', () => {
integrationGuides: {
react: {
llm_prompt: {
- id: 'test-guide',
- title: 'Test Guide',
- description: 'Test description',
- type: 'llm' as const,
- icon: 'test-icon',
- overview: 'Test overview',
- prerequisites: [],
- steps: [],
+ docsUrl: 'https://example.com/docs/test-guide',
},
- manual_steps: [],
},
},
};
@@ -453,16 +445,8 @@ describe('IntegrationGuide', () => {
integrationGuides: {
react: {
llm_prompt: {
- id: 'test-guide',
- title: 'Test Guide',
- description: 'Test description',
- type: 'llm' as const,
- icon: 'test-icon',
- overview: 'Test overview',
- prerequisites: [],
- steps: [],
+ docsUrl: 'https://example.com/docs/test-guide',
},
- manual_steps: [],
},
},
};
@@ -495,16 +479,8 @@ describe('IntegrationGuide', () => {
integrationGuides: {
react: {
llm_prompt: {
- id: 'test-guide',
- title: 'Test Guide',
- description: 'Test description',
- type: 'llm' as const,
- icon: 'test-icon',
- overview: 'Test overview',
- prerequisites: [],
- steps: [],
+ docsUrl: 'https://example.com/docs/test-guide',
},
- manual_steps: [],
},
},
};
diff --git a/frontend/apps/console/src/features/applications/components/edit-application/general-settings/EditGeneralSettings.tsx b/frontend/apps/console/src/features/applications/components/edit-application/general-settings/EditGeneralSettings.tsx
index 231809a483..fd6bda24c9 100644
--- a/frontend/apps/console/src/features/applications/components/edit-application/general-settings/EditGeneralSettings.tsx
+++ b/frontend/apps/console/src/features/applications/components/edit-application/general-settings/EditGeneralSettings.tsx
@@ -10,7 +10,6 @@ import type {JSX} from 'react';
import {useTranslation} from 'react-i18next';
import AccessSection from './AccessSection';
import DangerZoneSection from './DangerZoneSection';
-import QuickCopySection from './QuickCopySection';
import resolveApplicationType, {isClientCredentialsOnlyGrantSet} from '../../../utils/resolveApplicationType';
import ApplicationDeleteDialog from '../../ApplicationDeleteDialog';
import ClientSecretSuccessDialog from '../../ClientSecretSuccessDialog';
@@ -44,16 +43,6 @@ interface EditGeneralSettingsProps {
* redirect URI list state.
*/
sectionResetKey?: number;
- /**
- * The name of the field that was recently copied to clipboard
- */
- copiedField: string | null;
- /**
- * Callback function to copy text to clipboard
- * @param text - The text to copy
- * @param fieldName - The name of the field being copied
- */
- onCopyToClipboard: (text: string, fieldName: string) => Promise;
/**
* Callback invoked after the application is successfully deleted
*/
@@ -74,7 +63,6 @@ interface EditGeneralSettingsProps {
* Container component for general application settings.
*
* Displays sections for:
- * - Quick copy of application credentials (ID, Client ID)
* - Access configuration (URL, redirect URIs, allowed user types)
* - Danger zone (regenerate client secret)
*
@@ -87,8 +75,6 @@ export default function EditGeneralSettings({
onFieldChange,
oauth2Config = undefined,
sectionResetKey = 0,
- copiedField,
- onCopyToClipboard,
onDeleteSuccess = undefined,
onValidationChange = undefined,
showUserAccessConfig = true,
@@ -153,12 +139,6 @@ export default function EditGeneralSettings({
return (
<>
- Promise;
-}
-
-/**
- * Section component for quickly copying application credentials.
- *
- * Displays read-only text fields with copy buttons for:
- * - Application ID
- * - OAuth2 Client ID
- *
- * Provides visual feedback when values are copied.
- *
- * @param props - Component props
- * @returns Quick copy UI within a SettingsCard
- */
-export default function QuickCopySection({
- application,
- oauth2Config = undefined,
- copiedField,
- onCopyToClipboard,
-}: QuickCopySectionProps) {
- const {t} = useTranslation();
-
- return (
-
-
-
- {t('applications:edit.general.labels.applicationId')}
-
-
- {
- onCopyToClipboard(application.id, 'app_id').catch(() => null);
- }}
- edge="end"
- >
- {copiedField === 'app_id' ? : }
-
-
-
- ),
- }}
- helperText={t('applications:edit.general.applicationId.hint')}
- sx={{
- '& input': {
- fontFamily: 'monospace',
- fontSize: '0.875rem',
- },
- }}
- />
-
-
- {oauth2Config?.clientId && (
-
- {t('applications:edit.general.labels.clientId')}
-
-
- {
- if (oauth2Config?.clientId) {
- onCopyToClipboard(oauth2Config.clientId, 'clientId').catch(() => null);
- }
- }}
- edge="end"
- >
- {copiedField === 'clientId' ? : }
-
-
-
- ),
- }}
- helperText={t('applications:edit.general.clientId.hint')}
- sx={{
- '& input': {
- fontFamily: 'monospace',
- fontSize: '0.875rem',
- },
- }}
- />
-
- )}
-
-
- );
-}
diff --git a/frontend/apps/console/src/features/applications/components/edit-application/general-settings/__tests__/EditGeneralSettings.test.tsx b/frontend/apps/console/src/features/applications/components/edit-application/general-settings/__tests__/EditGeneralSettings.test.tsx
index c54c1db6a5..e679f35d19 100644
--- a/frontend/apps/console/src/features/applications/components/edit-application/general-settings/__tests__/EditGeneralSettings.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/edit-application/general-settings/__tests__/EditGeneralSettings.test.tsx
@@ -18,23 +18,6 @@ vi.mock('@thunderid/contexts', () => ({
}));
// Mock the child components
-vi.mock('../QuickCopySection', () => ({
- default: ({
- application,
- oauth2Config,
- copiedField,
- }: {
- application: Application;
- oauth2Config?: OAuth2Config;
- copiedField: string | null;
- }) => (
-
),
+vi.mock('../../../../../flows/api/useGetFlowById', () => ({
+ default: () => ({data: undefined, isLoading: false}),
}));
-const mockApplication: Application = {
+vi.mock('../../../../../../components/GatePreview/GatePreview', () => ({
+ default: () => ,
+}));
+
+const mockWriteText = vi.fn();
+const mockFetch = vi.fn();
+
+const promptFixtures: Record = {
+ 'https://thunderid.dev/prompts/react/redirect-based.txt': 'Integrate {{productName}} with clientId: {{clientId}}',
+ 'https://thunderid.dev/prompts/nextjs/redirect-based.txt': 'Integrate {{productName}} with Next.js',
+};
+
+const renderWithProviders = (component: React.ReactElement) =>
+ render({component});
+
+const reactApplication: Application = {
id: 'app-123',
- name: 'Test Application',
+ name: 'Bifrost',
template: 'react',
+ type: 'browser',
description: 'Test description',
allowedUserTypes: ['admin', 'user'],
};
-const mockOAuth2Config: OAuth2Config = {
+const oauth2Config: OAuth2Config = {
clientId: 'client-123',
clientSecret: 'secret-456',
grantTypes: ['authorization_code'],
@@ -36,151 +81,256 @@ const mockOAuth2Config: OAuth2Config = {
redirectUris: ['https://example.com/callback'],
};
-const mockIntegrationGuides = {
- INBUILT: {
- llm_prompt: {
- id: 'llm-1',
- title: 'Use AI to integrate',
- description: 'Copy prompt for AI',
- type: 'llm' as const,
- icon: 'sparkles',
- content: 'LLM prompt content',
- },
- manual_steps: [
- {
- step: 1,
- title: 'Install dependencies',
- description: 'Install required packages',
- code: {
- language: 'bash',
- content: 'npm install',
- },
- },
- ],
- },
-};
-
describe('IntegrationGuides', () => {
+ const originalClipboard = navigator.clipboard;
+
beforeEach(() => {
- vi.clearAllMocks();
+ vi.useFakeTimers({shouldAdvanceTime: true});
+ mockWriteText.mockReset().mockResolvedValue(undefined);
+ mockGetDocumentationLink.mockImplementation((key: string) => documentationLinks[key]);
+ mockFetch.mockReset().mockImplementation((url: string) =>
+ Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => Promise.resolve(promptFixtures[url] ?? ''),
+ }),
+ );
+ vi.stubGlobal('fetch', mockFetch);
+ Object.defineProperty(navigator, 'clipboard', {
+ value: {writeText: mockWriteText},
+ writable: true,
+ configurable: true,
+ });
});
- describe('Rendering', () => {
- it('should render integration guide when guides are available', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ afterEach(() => {
+ vi.runOnlyPendingTimers();
+ vi.useRealTimers();
+ vi.unstubAllGlobals();
+ Object.defineProperty(navigator, 'clipboard', {value: originalClipboard, writable: true, configurable: true});
+ });
- render();
+ it('always shows application details, even for a template with no quickstart', () => {
+ renderWithProviders();
- expect(screen.getByTestId('integration-guide')).toBeInTheDocument();
- });
+ expect(screen.getByText('Application details')).toBeInTheDocument();
+ expect(screen.getByText('app-123')).toBeInTheDocument();
+ expect(screen.queryByRole('link', {name: /Open on StackBlitz/i})).not.toBeInTheDocument();
+ });
- it('should render fallback message when no guides are available', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(null);
+ it('shows the OIDC endpoints and sign-in preview from the canonical type before the OAuth2 config resolves', () => {
+ // oauth2Config isn't always resolved on first paint; the canonical application type (always
+ // present) is enough to know a 'browser' app is OAuth2-based and user-facing.
+ renderWithProviders();
- render();
+ expect(screen.getByText('app-123')).toBeInTheDocument();
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.getByText('Preview')).toBeInTheDocument();
+ expect(screen.getByRole('link', {name: /Open on StackBlitz/i})).toBeInTheDocument();
+ });
- expect(screen.getByText('No integration guides available for this application type.')).toBeInTheDocument();
- });
+ it('hides the OIDC endpoints and sign-in preview for a custom application with no OAuth2 config', () => {
+ renderWithProviders();
+
+ expect(screen.queryByText('OIDC endpoints')).not.toBeInTheDocument();
+ expect(screen.queryByText('Preview')).not.toBeInTheDocument();
});
- describe('Props Propagation', () => {
- it('should pass clientId from oauth2Config to IntegrationGuide', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ it('hides only the sign-in preview for a machine-to-machine application with no OAuth2 config yet', () => {
+ renderWithProviders();
- render();
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.queryByText('Preview')).not.toBeInTheDocument();
+ });
- expect(IntegrationGuide).toHaveBeenCalledWith(
- {
- clientId: 'client-123',
- applicationId: 'app-123',
- integrationGuides: mockIntegrationGuides,
- templateId: 'react',
- },
- undefined,
- );
- });
+ it('shows the OIDC endpoints (but not the Client ID row) when the OAuth2 config has no clientId yet', () => {
+ renderWithProviders(
+ ,
+ );
- it('should pass empty clientId when oauth2Config is not provided', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/authorize')).toBeInTheDocument();
+ expect(screen.queryByText('Client ID')).not.toBeInTheDocument();
+ });
- render();
+ it('renders the StackBlitz quickstart card for a template with a quickstart sample', () => {
+ renderWithProviders();
- expect(IntegrationGuide).toHaveBeenCalledWith(
- {
- clientId: '',
- applicationId: 'app-123',
- integrationGuides: mockIntegrationGuides,
- templateId: 'react',
- },
- undefined,
- );
- });
+ const stackblitzLink = screen.getByRole('link', {name: /Open on StackBlitz/i});
+ expect(stackblitzLink).toHaveAttribute(
+ 'href',
+ 'https://stackblitz.com/fork/github/thunder-id/javascript-sdks/tree/main/samples/react/quickstart?file=README.md',
+ );
+ });
- it('should pass applicationId to IntegrationGuide', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ it('hides the read-the-quickstart-guide card when the docs link is not configured', () => {
+ const documentationLinksWithoutDocs = {...documentationLinks};
+ delete documentationLinksWithoutDocs['applications.templates.react.docs'];
+ mockGetDocumentationLink.mockImplementation((key: string) => documentationLinksWithoutDocs[key]);
- render();
+ renderWithProviders();
- expect(IntegrationGuide).toHaveBeenCalledWith(
- {
- clientId: '',
- applicationId: 'app-123',
- integrationGuides: mockIntegrationGuides,
- templateId: 'react',
- },
- undefined,
- );
- });
+ expect(screen.queryByText('Read the quickstart guide')).not.toBeInTheDocument();
+ expect(screen.getByRole('link', {name: /Open on StackBlitz/i})).toBeInTheDocument();
+ });
- it('should pass integrationGuides to IntegrationGuide', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ it('shows the leaving-console confirmation before opening the docs guide', () => {
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null);
+ renderWithProviders();
- render();
+ fireEvent.click(screen.getByRole('button', {name: /Open quickstart/i}));
+ expect(screen.getByText('You are leaving ThunderID')).toBeInTheDocument();
- expect(IntegrationGuide).toHaveBeenCalledWith(
- {
- clientId: '',
- applicationId: 'app-123',
- integrationGuides: mockIntegrationGuides,
- templateId: 'react',
- },
- undefined,
- );
- });
+ fireEvent.click(screen.getByRole('button', {name: 'Continue'}));
+ expect(openSpy).toHaveBeenCalledWith(
+ 'https://thunderid.dev/docs/next/guides/getting-started/connect-your-application/react/',
+ '_blank',
+ 'noopener,noreferrer',
+ );
- it('should pass templateId from application to IntegrationGuide', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ openSpy.mockRestore();
+ });
- render();
+ it('copies the coding agent prompt with placeholders replaced', async () => {
+ renderWithProviders();
- expect(IntegrationGuide).toHaveBeenCalledWith(
- {
- clientId: '',
- applicationId: 'app-123',
- integrationGuides: mockIntegrationGuides,
- templateId: 'react',
- },
- undefined,
- );
+ fireEvent.click(screen.getByRole('button', {name: /Copy prompt/i}));
+
+ await waitFor(() => {
+ expect(mockWriteText).toHaveBeenCalledTimes(1);
});
+ const copiedText = mockWriteText.mock.calls[0][0] as string;
+ expect(copiedText).toContain('client-123');
+ expect(copiedText).toContain('ThunderID');
+ expect(copiedText).not.toContain('{{clientId}}');
+ });
+
+ it('renders application identifiers and OIDC endpoints using the configured server URL', () => {
+ renderWithProviders();
+
+ expect(screen.getByText('app-123')).toBeInTheDocument();
+ expect(screen.getByText('client-123')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/.well-known/openid-configuration')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/authorize')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/token')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/userinfo')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/jwks')).toBeInTheDocument();
+ });
+
+ it('navigates to the Flows and Customization tabs via the sign-in preview links', () => {
+ const onGoToFlows = vi.fn();
+ const onGoToCustomization = vi.fn();
+ renderWithProviders(
+ ,
+ );
+
+ fireEvent.click(screen.getByRole('button', {name: 'Edit in Flows'}));
+ expect(onGoToFlows).toHaveBeenCalledTimes(1);
+
+ fireEvent.click(screen.getByRole('button', {name: 'Edit in Customization'}));
+ expect(onGoToCustomization).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not show the sign-in preview for a machine-to-machine (client-credentials only) application', () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.queryByText('Preview')).not.toBeInTheDocument();
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
});
- describe('Template Utility Integration', () => {
- it('should call getIntegrationGuidesForTemplate with template from application', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(mockIntegrationGuides);
+ it('shows the coding agent prompt for a template whose default EMBEDDED variant has no guide content', () => {
+ // Android defaults to the EMBEDDED sign-in approach (it's always app-native) but only has
+ // REDIRECT_BASED guide content authored; every technology template should still get a
+ // coding-agent prompt card.
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByRole('button', {name: /Copy prompt/i})).toBeInTheDocument();
+ });
+
+ describe('native (mobile) applications', () => {
+ const mobileApplication: Application = {
+ ...reactApplication,
+ template: 'mobile',
+ type: 'mobile',
+ };
+
+ it('shows one quickstart guide card per platform and no StackBlitz banner', () => {
+ renderWithProviders();
+
+ expect(screen.queryByRole('link', {name: /Open on StackBlitz/i})).not.toBeInTheDocument();
+ expect(screen.getByText('iOS quickstart guide')).toBeInTheDocument();
+ expect(screen.getByText('Android quickstart guide')).toBeInTheDocument();
+ expect(screen.getByText('Flutter quickstart guide')).toBeInTheDocument();
+ });
+
+ it('shows App Native flow endpoints instead of the OAuth2/OIDC endpoints', () => {
+ renderWithProviders();
+
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.queryByText('OIDC endpoints')).not.toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/execute')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/meta')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/register/passkey/start')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/register/passkey/finish')).toBeInTheDocument();
+ expect(screen.queryByText('https://localhost:8090/oauth2/authorize')).not.toBeInTheDocument();
+ expect(screen.queryByText('https://localhost:8090/oauth2/token')).not.toBeInTheDocument();
+ });
- render();
+ it('shows the standard OAuth2/OIDC endpoints (not App Native ones) for a pure browser SPA', () => {
+ renderWithProviders();
- expect(getIntegrationGuidesForTemplate).toHaveBeenCalledWith('react');
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/authorize')).toBeInTheDocument();
+ expect(screen.queryByText('https://localhost:8090/flow/execute')).not.toBeInTheDocument();
+ expect(screen.queryByText('https://localhost:8090/flow/meta')).not.toBeInTheDocument();
});
- it('should call getIntegrationGuidesForTemplate with empty string when template is not defined', () => {
- vi.mocked(getIntegrationGuidesForTemplate).mockReturnValue(null);
- const appWithoutTemplate = {...mockApplication, template: undefined};
+ it('shows both OAuth2/OIDC and App Native endpoints for the Custom template', () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/oauth2/authorize')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/execute')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/meta')).toBeInTheDocument();
+ });
+
+ it('also shows App Native flow endpoints for a fullstack application (e.g. Next.js)', () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText('Useful Endpoints')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/execute')).toBeInTheDocument();
+ expect(screen.getByText('https://localhost:8090/flow/meta')).toBeInTheDocument();
+ });
- render();
+ it('renders the sign-in preview in a phone-style frame', () => {
+ renderWithProviders();
- expect(getIntegrationGuidesForTemplate).toHaveBeenCalledWith('');
+ expect(screen.getByTestId('gate-preview')).toBeInTheDocument();
});
});
});
diff --git a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/TechnologyGuide.test.tsx b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/TechnologyGuide.test.tsx
index ea0f079c54..c3f9ade580 100644
--- a/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/TechnologyGuide.test.tsx
+++ b/frontend/apps/console/src/features/applications/components/edit-application/integration-guides/__tests__/TechnologyGuide.test.tsx
@@ -18,65 +18,34 @@ vi.mock('@thunderid/contexts', async (importOriginal) => {
favicon: {light: 'assets/images/favicon.ico', dark: 'assets/images/favicon-inverted.ico'},
},
},
+ getDocumentationLink: () => undefined,
}),
};
});
+const REDIRECT_BASED_PROMPT_URL = 'https://thunderid.dev/prompts/react/redirect-based.txt';
+const EMBEDDED_PROMPT_URL = 'https://thunderid.dev/prompts/react/embedded.txt';
+
const mockIntegrationGuides: IntegrationGuides = {
- INBUILT: {
+ REDIRECT_BASED: {
llm_prompt: {
- id: 'llm-1',
- title: 'Use AI Assistant',
- description: 'Get AI-powered integration guidance',
- type: 'llm' as const,
- icon: 'sparkles',
- content: 'Integrate with clientId: {{clientId}} and applicationId: {{applicationId}}',
+ docsUrl: REDIRECT_BASED_PROMPT_URL,
},
- manual_steps: [
- {
- step: 1,
- title: 'Install dependencies',
- description: 'Install required packages for your application',
- subDescription: 'Run the following command in your terminal',
- bullets: ['npm for Node Package Manager', 'yarn for Yarn Package Manager'],
- code: {
- language: 'bash',
- filename: 'terminal',
- content: 'npm install @awesome-product/sdk',
- },
- },
- {
- step: 2,
- title: 'Configure client',
- description: 'Set up your application with the client ID',
- code: {
- language: 'typescript',
- filename: 'config.ts',
- content: 'const clientId = "{{clientId}}";',
- },
- },
- ],
},
EMBEDDED: {
llm_prompt: {
- id: 'llm-2',
- title: 'Embedded Integration',
- description: 'Custom login UI integration',
- type: 'llm' as const,
- icon: 'sparkles',
- content: 'Embedded integration prompt',
+ docsUrl: EMBEDDED_PROMPT_URL,
},
- manual_steps: [
- {
- step: 1,
- title: 'Setup custom UI',
- description: 'Create your custom login form',
- },
- ],
},
};
+const promptFixtures: Record = {
+ [REDIRECT_BASED_PROMPT_URL]: 'Integrate with clientId: {{clientId}} and applicationId: {{applicationId}}',
+ [EMBEDDED_PROMPT_URL]: 'Embedded integration prompt',
+};
+
const mockWriteText = vi.fn();
+const mockFetch = vi.fn();
const renderWithProviders = (component: React.ReactElement) =>
render({component});
@@ -88,6 +57,14 @@ describe('TechnologyGuide', () => {
vi.useFakeTimers({shouldAdvanceTime: true});
vi.clearAllMocks();
mockWriteText.mockResolvedValue(undefined);
+ mockFetch.mockImplementation((url: string) =>
+ Promise.resolve({
+ ok: true,
+ status: 200,
+ text: () => Promise.resolve(promptFixtures[url] ?? ''),
+ }),
+ );
+ vi.stubGlobal('fetch', mockFetch);
Object.defineProperty(navigator, 'clipboard', {
value: {
writeText: mockWriteText,
@@ -100,6 +77,7 @@ describe('TechnologyGuide', () => {
afterEach(() => {
vi.runOnlyPendingTimers();
vi.useRealTimers();
+ vi.unstubAllGlobals();
Object.defineProperty(navigator, 'clipboard', {
value: originalClipboard,
writable: true,
@@ -115,32 +93,45 @@ describe('TechnologyGuide', () => {
});
it('should return null when selected guide is not found', () => {
- const guidesWithoutInbuilt: IntegrationGuides = {
- OTHER: mockIntegrationGuides.INBUILT,
+ const guidesWithoutRedirectBased: IntegrationGuides = {
+ OTHER: mockIntegrationGuides.REDIRECT_BASED,
};
- const {container} = renderWithProviders();
+ const {container} = renderWithProviders(
+ ,
+ );
expect(container.firstChild?.firstChild).toBeFalsy();
});
- it('should render inbuilt guide for non-embedded template', () => {
+ it('should render redirect-based guide for non-embedded template', async () => {
renderWithProviders();
- expect(screen.getByText('Use AI Assistant')).toBeInTheDocument();
- expect(screen.getByText('Get AI-powered integration guidance')).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('copy-prompt-button'));
+
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledWith(REDIRECT_BASED_PROMPT_URL);
+ expect(mockWriteText).toHaveBeenCalledWith(
+ 'Integrate with clientId: {{clientId}} and applicationId: {{applicationId}}',
+ );
+ });
});
- it('should render embedded guide for embedded template', () => {
+ it('should render embedded guide for embedded template', async () => {
renderWithProviders();
- expect(screen.getByText('Embedded Integration')).toBeInTheDocument();
+ fireEvent.click(screen.getByTestId('copy-prompt-button'));
+
+ await waitFor(() => {
+ expect(mockFetch).toHaveBeenCalledWith(EMBEDDED_PROMPT_URL);
+ expect(mockWriteText).toHaveBeenCalledWith('Embedded integration prompt');
+ });
});
- it('should default to inbuilt guide when templateId is null', () => {
+ it('should default to redirect-based guide when templateId is null', () => {
renderWithProviders();
- expect(screen.getByText('Use AI Assistant')).toBeInTheDocument();
+ expect(screen.getByText('Integrate with a coding agent')).toBeInTheDocument();
});
});
@@ -148,8 +139,8 @@ describe('TechnologyGuide', () => {
it('should render LLM prompt card with title and description', () => {
renderWithProviders();
- expect(screen.getByText('Use AI Assistant')).toBeInTheDocument();
- expect(screen.getByText('Get AI-powered integration guidance')).toBeInTheDocument();
+ expect(screen.getByText('Integrate with a coding agent')).toBeInTheDocument();
+ expect(screen.getByText('Copy a ready-made prompt for Claude, Cursor, or any agent.')).toBeInTheDocument();
});
it('should render copy prompt button', () => {
@@ -160,140 +151,21 @@ describe('TechnologyGuide', () => {
});
});
- describe('Manual Steps Section', () => {
- it('should render divider with "or" text', () => {
- renderWithProviders();
-
- expect(screen.getByText('or')).toBeInTheDocument();
- });
-
- it('should render all manual steps', () => {
- renderWithProviders();
-
- expect(screen.getByText('Install dependencies')).toBeInTheDocument();
- expect(screen.getByText('Configure client')).toBeInTheDocument();
- });
-
- it('should render step numbers', () => {
- renderWithProviders();
-
- expect(screen.getByText('1')).toBeInTheDocument();
- expect(screen.getByText('2')).toBeInTheDocument();
- });
-
- it('should render step descriptions', () => {
- renderWithProviders();
-
- expect(screen.getByText('Install required packages for your application')).toBeInTheDocument();
- expect(screen.getByText('Set up your application with the client ID')).toBeInTheDocument();
- });
-
- it('should render sub-descriptions when provided', () => {
- renderWithProviders();
-
- expect(screen.getByText('Run the following command in your terminal')).toBeInTheDocument();
- });
-
- it('should render bullet points when provided', () => {
- renderWithProviders();
-
- expect(screen.getByText('npm for Node Package Manager')).toBeInTheDocument();
- expect(screen.getByText('yarn for Yarn Package Manager')).toBeInTheDocument();
- });
- });
-
- describe('Code Blocks', () => {
- it('should render code blocks for steps with code', () => {
- const {container} = renderWithProviders();
-
- // Check that code blocks exist
- const codeBlocks = container.querySelectorAll('pre');
- expect(codeBlocks).toHaveLength(2);
-
- // Check code content is present
- expect(container.textContent).toContain('npm install @awesome-product/sdk');
- expect(container.textContent).toContain('const clientId = "{{clientId}}";');
- });
-
- it('should render filenames when provided', () => {
- renderWithProviders();
-
- expect(screen.getByText('terminal')).toBeInTheDocument();
- expect(screen.getByText('config.ts')).toBeInTheDocument();
- });
-
- it('should render copy buttons for each code block', () => {
- renderWithProviders();
-
- const copyButtons = screen.getAllByTestId(/copy-code-button-/);
- expect(copyButtons).toHaveLength(2);
- });
- });
-
describe('Empty States', () => {
- it('should not render code block when step has no code', () => {
- const guidesWithoutCode: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'No code step',
- description: 'This step has no code',
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const codeBlocks = container.querySelectorAll('pre');
- expect(codeBlocks).toHaveLength(0);
- });
-
- it('should not render manual steps section when manual_steps is empty', () => {
- const guidesWithoutSteps: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [],
- },
- };
-
- renderWithProviders();
-
- expect(screen.queryByText('or')).not.toBeInTheDocument();
- });
-
- it('should not render copy prompt button when llm_prompt has no content', () => {
- const guidesWithoutContent: IntegrationGuides = {
- INBUILT: {
- llm_prompt: {
- id: 'llm-1',
- title: 'Use AI Assistant',
- description: 'Get AI-powered integration guidance',
- type: 'llm' as const,
- icon: 'sparkles',
- },
- manual_steps: [],
+ it('should not render copy prompt button when llm_prompt has no docsUrl', () => {
+ const guidesWithoutDocsUrl: IntegrationGuides = {
+ REDIRECT_BASED: {
+ llm_prompt: {},
},
};
- renderWithProviders();
+ renderWithProviders();
expect(screen.queryByTestId('copy-prompt-button')).not.toBeInTheDocument();
});
});
describe('Placeholder Replacement', () => {
- it('should replace {{clientId}} placeholder in code blocks', () => {
- const {container} = renderWithProviders(
- ,
- );
-
- expect(container.textContent).toContain('const clientId = "my-client-id";');
- expect(container.textContent).not.toContain('{{clientId}}');
- });
-
it('should replace {{applicationId}} placeholder in LLM prompt when copied', async () => {
renderWithProviders(
{
});
});
- it('should not replace placeholders when clientId is empty', () => {
- const {container} = renderWithProviders(
- ,
- );
-
- expect(container.textContent).toContain('{{clientId}}');
- });
-
it('should not replace applicationId placeholder when applicationId is empty', async () => {
renderWithProviders(
,
@@ -364,63 +228,34 @@ describe('TechnologyGuide', () => {
});
});
- it('should copy code to clipboard when copy code button is clicked', async () => {
- renderWithProviders(
- ,
- );
+ it('should not call fetch when prompt has no docsUrl', () => {
+ const guidesWithoutDocsUrl: IntegrationGuides = {
+ REDIRECT_BASED: {
+ llm_prompt: {
+ docsUrl: '',
+ },
+ },
+ };
- const copyCodeButton = screen.getByTestId('copy-code-button-1');
- fireEvent.click(copyCodeButton);
+ renderWithProviders();
- await waitFor(() => {
- expect(mockWriteText).toHaveBeenCalledWith('npm install @awesome-product/sdk');
- });
+ // Button should not render when docsUrl is an empty string
+ expect(screen.queryByTestId('copy-prompt-button')).toBeNull();
+ expect(mockFetch).not.toHaveBeenCalled();
+ expect(mockWriteText).not.toHaveBeenCalled();
});
- it('should show copied feedback after copying code', async () => {
- renderWithProviders();
-
- const copyCodeButton = screen.getByTestId('copy-code-button-1');
- fireEvent.click(copyCodeButton);
+ it('should log an error and not copy when fetching the prompt fails', async () => {
+ mockFetch.mockResolvedValue({ok: false, status: 500, text: () => Promise.resolve('')});
- // The copied feedback is shown as translated text
- await waitFor(() => {
- expect(screen.getByText('Copied to clipboard')).toBeInTheDocument();
- });
- });
-
- it('should replace placeholders in copied code', async () => {
- renderWithProviders(
- ,
- );
+ renderWithProviders();
- const copyCodeButton = screen.getByTestId('copy-code-button-2');
- fireEvent.click(copyCodeButton);
+ const copyButton = screen.getByTestId('copy-prompt-button');
+ fireEvent.click(copyButton);
await waitFor(() => {
- expect(mockWriteText).toHaveBeenCalledWith('const clientId = "replaced-client-id";');
+ expect(mockFetch).toHaveBeenCalledWith(REDIRECT_BASED_PROMPT_URL);
});
- });
-
- it('should not call clipboard when prompt has no content', () => {
- const guidesWithEmptyContent: IntegrationGuides = {
- INBUILT: {
- llm_prompt: {
- id: 'llm-1',
- title: 'Use AI Assistant',
- description: 'Get AI-powered integration guidance',
- type: 'llm' as const,
- icon: 'sparkles',
- content: '',
- },
- manual_steps: [],
- },
- };
-
- renderWithProviders();
-
- // Button should not render when content is empty string
- expect(screen.queryByTestId('copy-prompt-button')).toBeNull();
expect(mockWriteText).not.toHaveBeenCalled();
});
@@ -441,22 +276,6 @@ describe('TechnologyGuide', () => {
});
});
- it('should use fallback method when clipboard API fails for code', async () => {
- mockWriteText.mockRejectedValue(new Error('Clipboard API failed'));
-
- const mockExecCommand = vi.fn().mockReturnValue(true);
- document.execCommand = mockExecCommand;
-
- renderWithProviders();
-
- const copyCodeButton = screen.getByTestId('copy-code-button-1');
- fireEvent.click(copyCodeButton);
-
- await waitFor(() => {
- expect(mockExecCommand).toHaveBeenCalledWith('copy');
- });
- });
-
it('should handle fallback failure gracefully for prompt', () => {
mockWriteText.mockRejectedValue(new Error('Clipboard API failed'));
@@ -472,169 +291,6 @@ describe('TechnologyGuide', () => {
// Should not throw - component handles error gracefully
expect(() => fireEvent.click(copyButton)).not.toThrow();
});
-
- it('should handle fallback failure gracefully for code', () => {
- mockWriteText.mockRejectedValue(new Error('Clipboard API failed'));
-
- const mockExecCommand = vi.fn().mockImplementation(() => {
- throw new Error('execCommand failed');
- });
- document.execCommand = mockExecCommand;
-
- renderWithProviders();
-
- const copyCodeButton = screen.getByTestId('copy-code-button-1');
-
- // Should not throw - component handles error gracefully
- expect(() => fireEvent.click(copyCodeButton)).not.toThrow();
- });
- });
- });
-
- describe('Code Block Language Mapping', () => {
- it('should map terminal language to bash', () => {
- const guidesWithTerminal: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Run command',
- description: 'Execute this command',
- code: {
- language: 'terminal',
- content: 'npm install',
- },
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const codeBlock = container.querySelector('pre');
- expect(codeBlock).toBeInTheDocument();
- });
-
- it('should map .env language to properties', () => {
- const guidesWithEnv: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Configure env',
- description: 'Set environment variables',
- code: {
- language: '.env',
- filename: '.env',
- content: 'API_KEY=your-key',
- },
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const codeBlock = container.querySelector('pre');
- expect(codeBlock).toBeInTheDocument();
- });
-
- it('should map typescript language to tsx', () => {
- const guidesWithTs: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Add code',
- description: 'Add TypeScript code',
- code: {
- language: 'typescript',
- content: 'const x: string = "test";',
- },
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const codeBlock = container.querySelector('pre');
- expect(codeBlock).toBeInTheDocument();
- });
-
- it('should pass through unknown languages unchanged', () => {
- const guidesWithPython: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Python code',
- description: 'Add Python code',
- code: {
- language: 'python',
- content: 'print("hello")',
- },
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const codeBlock = container.querySelector('pre');
- expect(codeBlock).toBeInTheDocument();
- });
-
- it('should render code block without filename header when filename is not provided', () => {
- const guidesWithoutFilename: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Run command',
- description: 'Execute this command',
- code: {
- language: 'bash',
- content: 'npm install',
- },
- },
- ],
- },
- };
-
- renderWithProviders();
-
- // Should not have a filename displayed
- expect(screen.queryByText('terminal')).not.toBeInTheDocument();
- expect(screen.queryByText('config.ts')).not.toBeInTheDocument();
- });
- });
-
- describe('Bullets Rendering', () => {
- it('should not render bullets section when bullets array is empty', () => {
- const guidesWithEmptyBullets: IntegrationGuides = {
- INBUILT: {
- llm_prompt: mockIntegrationGuides.INBUILT.llm_prompt,
- manual_steps: [
- {
- step: 1,
- title: 'Step without bullets',
- description: 'This step has empty bullets array',
- bullets: [],
- },
- ],
- },
- };
-
- const {container} = renderWithProviders();
-
- const bulletLists = container.querySelectorAll('ul');
- expect(bulletLists).toHaveLength(0);
});
});
});
diff --git a/frontend/apps/console/src/features/applications/config/TechnologyBasedApplicationTemplateMetadata.tsx b/frontend/apps/console/src/features/applications/config/TechnologyBasedApplicationTemplateMetadata.tsx
index 30b85a5e9c..097d4e3fd2 100644
--- a/frontend/apps/console/src/features/applications/config/TechnologyBasedApplicationTemplateMetadata.tsx
+++ b/frontend/apps/console/src/features/applications/config/TechnologyBasedApplicationTemplateMetadata.tsx
@@ -1,8 +1,12 @@
// Copyright 2025 The ThunderID Authors
// SPDX-License-Identifier: Apache-2.0
+import {AndroidLogo, AppleIcon, FlutterLogo} from '@thunderid/components';
import {MCP} from '@wso2/oxygen-ui-icons-react';
+import AndroidTemplate from '../data/application-templates/technology-based/android.json';
import ExpressTemplate from '../data/application-templates/technology-based/express.json';
+import FlutterTemplate from '../data/application-templates/technology-based/flutter.json';
+import IOSTemplate from '../data/application-templates/technology-based/ios.json';
import MCPClientTemplate from '../data/application-templates/technology-based/mcp-client.json';
import NextJSTemplate from '../data/application-templates/technology-based/nextjs.json';
import NodeTemplate from '../data/application-templates/technology-based/node.json';
@@ -165,6 +169,30 @@ const TechnologyBasedApplicationTemplateMetadata: ApplicationTemplateMetadata,
+ titleKey: 'applications:onboarding.configure.stack.technology.ios.title',
+ descriptionKey: 'applications:onboarding.configure.stack.technology.ios.description',
+ template: IOSTemplate as ApplicationTemplate,
+ categories: ['mobile'],
+ },
+ {
+ value: TechnologyApplicationTemplate.ANDROID,
+ icon: ,
+ titleKey: 'applications:onboarding.configure.stack.technology.android.title',
+ descriptionKey: 'applications:onboarding.configure.stack.technology.android.description',
+ template: AndroidTemplate as ApplicationTemplate,
+ categories: ['mobile'],
+ },
+ {
+ value: TechnologyApplicationTemplate.FLUTTER,
+ icon: ,
+ titleKey: 'applications:onboarding.configure.stack.technology.flutter.title',
+ descriptionKey: 'applications:onboarding.configure.stack.technology.flutter.description',
+ template: FlutterTemplate as ApplicationTemplate,
+ categories: ['mobile'],
+ },
{
value: TechnologyApplicationTemplate.MCP_CLIENT,
icon: ,
diff --git a/frontend/apps/console/src/features/applications/constants/template-constants.ts b/frontend/apps/console/src/features/applications/constants/template-constants.ts
index 8e9cdc64f4..dce68df24b 100644
--- a/frontend/apps/console/src/features/applications/constants/template-constants.ts
+++ b/frontend/apps/console/src/features/applications/constants/template-constants.ts
@@ -7,7 +7,7 @@
const TemplateConstants = {
/**
* Template modifier suffix for embedded (inbuilt) approach.
- * Appended to technology template IDs when INBUILT approach is selected.
+ * Appended to technology template IDs when REDIRECT_BASED approach is selected.
*/
EMBEDDED_SUFFIX: '-embedded',
diff --git a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateContext.tsx b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateContext.tsx
index 84edd2734b..fdd77e343f 100644
--- a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateContext.tsx
+++ b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateContext.tsx
@@ -176,7 +176,7 @@ export interface ApplicationCreateContextType {
setSmsOtpSenderId: (senderId: string) => void;
/**
- * The selected sign-in approach (INBUILT or CUSTOM).
+ * The selected sign-in approach (REDIRECT_BASED or CUSTOM).
* @remark Needed for step 04: Configure Approach.
*/
signInApproach: ApplicationCreateFlowSignInApproach;
diff --git a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateProvider.tsx b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateProvider.tsx
index debaa388ee..d889c381c7 100644
--- a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateProvider.tsx
+++ b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/ApplicationCreateProvider.tsx
@@ -101,7 +101,7 @@ const INITIAL_STATE: {
isSmsOtpMfaEnabled: false,
smsOtpSenderId: '',
selectedAuthFlow: null,
- signInApproach: ApplicationCreateFlowSignInApproach.INBUILT as ApplicationCreateFlowSignInApproach,
+ signInApproach: ApplicationCreateFlowSignInApproach.REDIRECT_BASED as ApplicationCreateFlowSignInApproach,
registrationFlowId: null,
isRegistrationFlowEnabled: false,
recoveryFlowId: null,
diff --git a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateContext.test.tsx b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateContext.test.tsx
index 89638656ef..1ed52e4dbc 100644
--- a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateContext.test.tsx
+++ b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateContext.test.tsx
@@ -58,7 +58,7 @@ function TestWithMockValue() {
setSmsOtpSenderId: vi.fn(),
selectedAuthFlow: null,
setSelectedAuthFlow: vi.fn(),
- signInApproach: 'INBUILT',
+ signInApproach: 'REDIRECT_BASED',
setSignInApproach: vi.fn(),
registrationFlowId: null,
setRegistrationFlowId: vi.fn(),
@@ -172,7 +172,7 @@ describe('ApplicationCreateContext', () => {
setSmsOtpSenderId: () => null,
selectedAuthFlow: null,
setSelectedAuthFlow: () => null,
- signInApproach: 'INBUILT',
+ signInApproach: 'REDIRECT_BASED',
setSignInApproach: () => null,
registrationFlowId: null,
setRegistrationFlowId: () => null,
@@ -262,7 +262,7 @@ describe('ApplicationCreateContext', () => {
setSmsOtpSenderId: () => null,
selectedAuthFlow: null, // Should allow null
setSelectedAuthFlow: () => null,
- signInApproach: 'INBUILT',
+ signInApproach: 'REDIRECT_BASED',
setSignInApproach: () => null,
registrationFlowId: null,
setRegistrationFlowId: () => null,
diff --git a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateProvider.test.tsx b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateProvider.test.tsx
index 67f6bba07d..ec62179bf5 100644
--- a/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateProvider.test.tsx
+++ b/frontend/apps/console/src/features/applications/contexts/ApplicationCreate/__tests__/ApplicationCreateProvider.test.tsx
@@ -155,7 +155,9 @@ describe('ApplicationCreateProvider', () => {
expect(screen.getByTestId('email-otp-mfa')).toHaveTextContent('false');
expect(screen.getByTestId('sms-otp-mfa')).toHaveTextContent('false');
expect(screen.getByTestId('sms-otp-sender-id')).toHaveTextContent('');
- expect(screen.getByTestId('sign-in-approach')).toHaveTextContent(ApplicationCreateFlowSignInApproach.INBUILT);
+ expect(screen.getByTestId('sign-in-approach')).toHaveTextContent(
+ ApplicationCreateFlowSignInApproach.REDIRECT_BASED,
+ );
expect(screen.getByTestId('selected-technology')).toHaveTextContent('null');
expect(screen.getByTestId('selected-platform')).toHaveTextContent('null');
expect(screen.getByTestId('mcp-client-type')).toHaveTextContent('userDelegated');
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/backend.json b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/backend.json
index 85953b3647..e0ac62c421 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/backend.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/backend.json
@@ -3,6 +3,12 @@
"type": "m2m",
"displayName": "Backend",
"description": "Machine-to-machine backend service",
+ "quickstarts": [
+ {
+ "label": "Node.js",
+ "docsUrl": "{{applications.templates.node.docs}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "COMPLETE"],
"previewSteps": []
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/browser.json b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/browser.json
index 9f567d6b0a..70594cb8cc 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/browser.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/browser.json
@@ -3,6 +3,19 @@
"type": "browser",
"displayName": "Browser",
"description": "Web application running in browser",
+ "quickstarts": [
+ {
+ "label": "JavaScript",
+ "docsUrl": "{{applications.templates.browser.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "JavaScript",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.browser.playground}}"
+ }
+ ],
"capabilities": {
"cors": true
},
@@ -29,9 +42,18 @@
},
"fieldConstraints": {
"oauth2": {
- "publicClient": {"readOnly": true, "value": true},
- "pkceRequired": {"readOnly": true, "value": true},
- "tokenEndpointAuthMethod": {"readOnly": true, "value": "none"}
+ "publicClient": {
+ "readOnly": true,
+ "value": true
+ },
+ "pkceRequired": {
+ "readOnly": true,
+ "value": true
+ },
+ "tokenEndpointAuthMethod": {
+ "readOnly": true,
+ "value": "none"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/full-stack.json b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/full-stack.json
index e0e2b1734d..d94cfefa5c 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/full-stack.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/full-stack.json
@@ -3,6 +3,46 @@
"type": "fullstack",
"displayName": "Full-Stack",
"description": "Apps with both server and client code",
+ "quickstarts": [
+ {
+ "label": "Next.js",
+ "docsUrl": "{{applications.templates.nextjs.docs}}"
+ },
+ {
+ "label": "Nuxt",
+ "docsUrl": "{{applications.templates.nuxt.docs}}"
+ },
+ {
+ "label": "Express",
+ "docsUrl": "{{applications.templates.express.docs}}"
+ },
+ {
+ "label": "Node.js",
+ "docsUrl": "{{applications.templates.node.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "Next.js",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.nextjs.playground}}"
+ },
+ {
+ "label": "Nuxt",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.nuxt.playground}}"
+ },
+ {
+ "label": "Express",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.express.playground}}"
+ },
+ {
+ "label": "Node.js",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.node.playground}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
"previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/mobile.json b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/mobile.json
index 04a7e4fa06..ac60a3a625 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/mobile.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/mobile.json
@@ -3,12 +3,28 @@
"type": "mobile",
"displayName": "Mobile",
"description": "Native mobile application",
+ "previewDevice": "mobile",
+ "quickstarts": [
+ {
+ "label": "iOS",
+ "docsUrl": "{{applications.templates.ios.docs}}"
+ },
+ {
+ "label": "Android",
+ "docsUrl": "{{applications.templates.android.docs}}"
+ },
+ {
+ "label": "Flutter",
+ "docsUrl": "{{applications.templates.flutter.docs}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
"previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
},
"defaults": {
"name": "Mobile Application",
+ "signInApproach": "EMBEDDED",
"inboundAuthConfig": [
{
"type": "oauth2",
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/wallet.json b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/wallet.json
index bf5c4bffd4..2add0168e6 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/platform-based/wallet.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/platform-based/wallet.json
@@ -3,6 +3,20 @@
"type": "mobile",
"displayName": "Digital Wallet",
"description": "OpenID4VCI wallet that requests verifiable credentials",
+ "quickstarts": [
+ {
+ "label": "iOS",
+ "docsUrl": "{{applications.templates.ios.docs}}"
+ },
+ {
+ "label": "Android",
+ "docsUrl": "{{applications.templates.android.docs}}"
+ },
+ {
+ "label": "Flutter",
+ "docsUrl": "{{applications.templates.flutter.docs}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
"previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/android.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/android.json
new file mode 100644
index 0000000000..328859e0f3
--- /dev/null
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/android.json
@@ -0,0 +1,52 @@
+{
+ "id": "android",
+ "type": "mobile",
+ "displayName": "Android",
+ "description": "Native Android application built with Jetpack Compose",
+ "previewDevice": "mobile",
+ "quickstarts": [
+ {
+ "label": "Android",
+ "docsUrl": "{{applications.templates.android.docs}}"
+ }
+ ],
+ "creationFlow": {
+ "steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
+ "previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
+ },
+ "defaults": {
+ "name": "Android Application",
+ "signInApproach": "EMBEDDED",
+ "inboundAuthConfig": [
+ {
+ "type": "oauth2",
+ "config": {
+ "grantTypes": ["authorization_code", "refresh_token"],
+ "responseTypes": ["code"],
+ "redirectUris": [],
+ "pkceRequired": true,
+ "tokenEndpointAuthMethod": "none",
+ "publicClient": true
+ }
+ }
+ ],
+ "allowedUserTypes": []
+ },
+ "fieldConstraints": {
+ "oauth2": {
+ "publicClient": {"readOnly": true, "value": true},
+ "pkceRequired": {"readOnly": true, "value": true},
+ "tokenEndpointAuthMethod": {"readOnly": true, "value": "none"}
+ }
+ },
+ "capabilities": {
+ "attestation": true
+ },
+ "integrationGuides": {
+ "REDIRECT_BASED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.android.llmPrompt.redirectBased}}"
+ }
+ }
+ }
+}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/express.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/express.json
index a2393115d9..ff2aedead2 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/express.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/express.json
@@ -3,6 +3,19 @@
"type": "fullstack",
"displayName": "Express",
"description": "Server-side Node.js application built with Express",
+ "quickstarts": [
+ {
+ "label": "Express",
+ "docsUrl": "{{applications.templates.express.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "Express",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.express.playground}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
"previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
@@ -37,76 +50,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your Express application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in an Express Application (Inbuilt Mode)\n\n## Context\nI have an Express application and I want to integrate {{productName}} authentication using the ThunderID Express SDK with {{productName}}-hosted sign-in pages.\n\n## Requirements\n- Use `@thunderid/express` in a Node.js + Express application\n- Use `cookie-parser` and `express.json()` middleware\n- Configure ThunderID middleware with `baseUrl`, `clientId`, `clientSecret`, `afterSignInUrl`, and `afterSignOutUrl`\n- Implement `/login` with `handleSignIn()` and `/logout` with `handleSignOut()`\n- Protect a route (`/protected`) using `protect((res) => res.redirect('/login'))`\n- Add a `/me` route that returns the authenticated user profile as JSON\n- Keep the code minimal, production-lean, and fully runnable\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Client Secret**: ``\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **App URL**: http://localhost:3000\n- **Login Callback Route**: `/login`\n- **Logout Callback Route**: `/logout`\n- **SDK**: @thunderid/express\n\n## Important Rules\n- Use CommonJS syntax (`require`) in `index.js`\n- Use these SDK imports exactly: `thunderID`, `handleSignIn`, `handleSignOut`, `protect`\n- Ensure redirect URL alignment: the app callback URL must be `http://localhost:3000/login`, and the post-logout redirect URL must be `http://localhost:3000/logout`\n- Do not invent unsupported SDK APIs or custom wrappers\n- Keep route names and behavior exactly as specified\n\n## Implementation Steps\n1. Create a new project and install `express` and `cookie-parser`\n2. Install `@thunderid/express`\n3. Add `index.js` with ThunderID middleware and auth routes\n4. Add `/protected` and `/me` routes with `protect()`\n5. Start the server with `node index.js`\n6. Validate the flow by opening `/protected`, then `/me`\n\nPlease provide:\n- The exact terminal commands\n- A complete `index.js` file\n- A short verification checklist for sign-in, sign-out, and protected route behavior"
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create an Express app",
- "description": "Create your project and install the base dependencies:",
- "code": {
- "language": "terminal",
- "content": "mkdir my-express-app\ncd my-express-app\nnpm init -y\nnpm install express cookie-parser"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/express",
- "description": "Install the ThunderID Express SDK package:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/express"
- }
- },
- {
- "step": 3,
- "title": "Add ThunderID middleware and authentication routes",
- "description": "Create an index.js file with middleware and auth route handlers:",
- "code": {
- "language": "javascript",
- "filename": "index.js",
- "content": "const express = require('express');\nconst cookieParser = require('cookie-parser');\nconst {thunderID, handleSignIn, handleSignOut, protect} = require('@thunderid/express');\n\nconst app = express();\nconst port = 3000;\n\napp.use(cookieParser());\napp.use(express.json());\n\napp.use(\n thunderID({\n baseUrl: 'https://localhost:8090',\n clientId: '{{clientId}}',\n clientSecret: '',\n afterSignInUrl: 'http://localhost:3000/login',\n afterSignOutUrl: 'http://localhost:3000/logout',\n }),\n);\n\napp.get('/', (_req, res) => {\n res.send('Go to protected page');\n});\n\napp.get('/login', handleSignIn());\napp.get('/logout', handleSignOut());\n\napp.get(\n '/protected',\n protect((res) => res.redirect('/login')),\n (_req, res) => {\n res.send('You are signed in and can access this protected route.');\n },\n);\n\napp.get('/me', protect(), async (req, res) => {\n const user = await req.thunderIDAuth.getUserFromRequest(req);\n res.json(user);\n});\n\napp.listen(port, () => {\n console.log(`Server running on http://localhost:${port}`);\n});"
- }
- },
- {
- "step": 4,
- "title": "Update credentials",
- "description": "Replace the placeholders with your actual application credentials from {{productName}}:",
- "bullets": [
- "Replace `{{clientId}}` with your Client ID",
- "Replace `` with your Client Secret",
- "Ensure your authorized redirect URL is `http://localhost:3000/login`",
- "Ensure your allowed post-logout redirect URL is `http://localhost:3000/logout`"
- ]
- },
- {
- "step": 5,
- "title": "Run the app",
- "description": "Start your Express server:",
- "code": {
- "language": "terminal",
- "content": "node index.js"
- }
- },
- {
- "step": 6,
- "title": "Verify authentication flow",
- "description": "Test the integration end-to-end:",
- "bullets": [
- "Open `http://localhost:3000/protected` and verify redirect to {{productName}} sign-in",
- "Sign in and confirm access to the protected route",
- "Open `http://localhost:3000/me` to inspect the signed-in user profile JSON",
- "Open `http://localhost:3000/logout` while signed in and verify sign-out"
- ]
- }
- ]
+ "docsUrl": "{{applications.templates.express.llmPrompt.redirectBased}}"
+ }
+ },
+ "EMBEDDED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.express.llmPrompt.embedded}}"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/flutter.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/flutter.json
new file mode 100644
index 0000000000..720025b9cf
--- /dev/null
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/flutter.json
@@ -0,0 +1,52 @@
+{
+ "id": "flutter",
+ "type": "mobile",
+ "displayName": "Flutter",
+ "description": "Cross-platform mobile application built with Flutter",
+ "previewDevice": "mobile",
+ "quickstarts": [
+ {
+ "label": "Flutter",
+ "docsUrl": "{{applications.templates.flutter.docs}}"
+ }
+ ],
+ "creationFlow": {
+ "steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
+ "previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
+ },
+ "defaults": {
+ "name": "Flutter Application",
+ "signInApproach": "EMBEDDED",
+ "inboundAuthConfig": [
+ {
+ "type": "oauth2",
+ "config": {
+ "grantTypes": ["authorization_code", "refresh_token"],
+ "responseTypes": ["code"],
+ "redirectUris": [],
+ "pkceRequired": true,
+ "tokenEndpointAuthMethod": "none",
+ "publicClient": true
+ }
+ }
+ ],
+ "allowedUserTypes": []
+ },
+ "fieldConstraints": {
+ "oauth2": {
+ "publicClient": {"readOnly": true, "value": true},
+ "pkceRequired": {"readOnly": true, "value": true},
+ "tokenEndpointAuthMethod": {"readOnly": true, "value": "none"}
+ }
+ },
+ "capabilities": {
+ "attestation": true
+ },
+ "integrationGuides": {
+ "REDIRECT_BASED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.flutter.llmPrompt.redirectBased}}"
+ }
+ }
+ }
+}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/ios.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/ios.json
new file mode 100644
index 0000000000..e9b31618f5
--- /dev/null
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/ios.json
@@ -0,0 +1,52 @@
+{
+ "id": "ios",
+ "type": "mobile",
+ "displayName": "iOS",
+ "description": "Native iOS application built with SwiftUI",
+ "previewDevice": "mobile",
+ "quickstarts": [
+ {
+ "label": "iOS",
+ "docsUrl": "{{applications.templates.ios.docs}}"
+ }
+ ],
+ "creationFlow": {
+ "steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
+ "previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
+ },
+ "defaults": {
+ "name": "iOS Application",
+ "signInApproach": "EMBEDDED",
+ "inboundAuthConfig": [
+ {
+ "type": "oauth2",
+ "config": {
+ "grantTypes": ["authorization_code", "refresh_token"],
+ "responseTypes": ["code"],
+ "redirectUris": [],
+ "pkceRequired": true,
+ "tokenEndpointAuthMethod": "none",
+ "publicClient": true
+ }
+ }
+ ],
+ "allowedUserTypes": []
+ },
+ "fieldConstraints": {
+ "oauth2": {
+ "publicClient": {"readOnly": true, "value": true},
+ "pkceRequired": {"readOnly": true, "value": true},
+ "tokenEndpointAuthMethod": {"readOnly": true, "value": "none"}
+ }
+ },
+ "capabilities": {
+ "attestation": true
+ },
+ "integrationGuides": {
+ "REDIRECT_BASED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.ios.llmPrompt.redirectBased}}"
+ }
+ }
+ }
+}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/mcp-client.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/mcp-client.json
index e03762fa4e..d532801796 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/mcp-client.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/mcp-client.json
@@ -3,6 +3,12 @@
"type": "mcp",
"displayName": "MCP Client",
"description": "AI application that connects to MCP servers using the Model Context Protocol",
+ "quickstarts": [
+ {
+ "label": "Python",
+ "docsUrl": "{{applications.templates.mcpClient.docs}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "CLIENT_TYPE", "COMPLETE"],
"previewSteps": []
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nextjs.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nextjs.json
index 9593788691..f2c077c1d9 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nextjs.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nextjs.json
@@ -3,6 +3,19 @@
"type": "fullstack",
"displayName": "Next.js",
"description": "Server-side rendered application built with Next.js",
+ "quickstarts": [
+ {
+ "label": "Next.js",
+ "docsUrl": "{{applications.templates.nextjs.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "Next.js",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.nextjs.playground}}"
+ }
+ ],
"devServer": {
"id": "nextjs",
"label": "Create Next App",
@@ -46,85 +59,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your Next.js application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in Next.js Application\n\n## Context\nI have a Next.js application (App Router) and I want to integrate {{productName}}'s authentication system using the {{productName}} Next.js SDK with {{productName}}-hosted login pages.\n\n## Requirements\n- Use @thunderid/nextjs SDK for authentication\n- Configure {{productName}}-hosted login pages (not custom/embedded)\n- Use the App Router (not Pages Router)\n- Implement sign-in and sign-out with prebuilt components\n- Add middleware for route protection and automatic token refresh\n- Display signed-in user's profile information\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **SDK**: @thunderid/nextjs\n\n## IMPORTANT Configuration Rules\n- Use environment variables for configuration (NOT props on the provider)\n- Required env vars: NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0\n- Import ThunderIDProvider from '@thunderid/nextjs/server' (NOT from '@thunderid/nextjs')\n- Import middleware utilities from '@thunderid/nextjs/server'\n- Import UI components from '@thunderid/nextjs'\n- The ThunderIDProvider handles the OAuth callback automatically — no manual callback route is needed\n\n## Implementation Steps\n1. Create a Next.js app: npx create-next-app@latest nextjs-demo\n2. Navigate into the project: cd nextjs-demo\n3. Install @thunderid/nextjs: npm install @thunderid/nextjs\n4. Create .env.local with NEXT_PUBLIC_THUNDERID_BASE_URL, NEXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SECRET, NODE_TLS_REJECT_UNAUTHORIZED=0\n5. Wrap root layout with from '@thunderid/nextjs/server'\n6. Create proxy.ts with thunderIDProxy and createRouteMatcher from '@thunderid/nextjs/server' for route protection\n7. Add SignedIn, UserDropdown, SignedOut, SignInButton components to pages\n8. Run: npm run dev\n\nPlease provide complete, working code with proper configuration for {{productName}} authentication using the {{productName}} Next.js SDK."
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a Next.js app",
- "description": "Run the following command to create a new Next.js app:",
- "code": {
- "language": "terminal",
- "content": "npx create-next-app@latest my-nextjs-app\ncd my-nextjs-app"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/nextjs",
- "description": "The {{productName}} Next.js SDK provides server-side authentication, middleware, and prebuilt UI components.",
- "subDescription": "Run the following command to install the SDK:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/nextjs"
- }
- },
- {
- "step": 3,
- "title": "Set environment variables",
- "description": "Create a .env.local file in your project root with the following configuration:",
- "code": {
- "language": "bash",
- "filename": ".env.local",
- "content": "NEXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090\nNEXT_PUBLIC_THUNDERID_CLIENT_ID={{clientId}}\nTHUNDERID_CLIENT_SECRET=\nTHUNDERID_SECRET=\n# DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production.\nNODE_TLS_REJECT_UNAUTHORIZED=0"
- }
- },
- {
- "step": 4,
- "title": "Add to your layout",
- "description": "Wrap your root layout with the ThunderIDProvider from the server export:",
- "code": {
- "language": "typescript",
- "filename": "app/layout.tsx",
- "content": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport { ThunderIDProvider } from '@thunderid/nextjs/server'\nimport \"./globals.css\";\n\nconst geistSans = Geist({\n variable: \"--font-geist-sans\",\n subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n variable: \"--font-geist-mono\",\n subsets: [\"latin\"],\n});\n\nexport const metadata: Metadata = {\n title: \"Create Next App\",\n description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n children,\n}: Readonly<{\n children: React.ReactNode;\n}>) {\n return (\n \n \n {children}\n \n \n );\n}"
- }
- },
- {
- "step": 5,
- "title": "Add the ThunderID proxy",
- "description": "Create a proxy.ts file at your project root to proxy requests through ThunderID and protect routes:",
- "code": {
- "language": "typescript",
- "filename": "proxy.ts",
- "content": "import {\n thunderIDProxy,\n createRouteMatcher,\n} from '@thunderid/nextjs/server'\n\nconst isProtectedRoute = createRouteMatcher([\n // Add the paths you want to protect, e.g. '/dashboard(.*)'\n])\n\nexport default thunderIDProxy(async (thunderid, request) => {\n if (isProtectedRoute(request)) {\n await thunderid.protectRoute()\n }\n})\n\nexport const config = {\n matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],\n}"
- }
- },
- {
- "step": 6,
- "title": "Build with ThunderID components",
- "description": "Update your home page with ThunderID authentication components:",
- "code": {
- "language": "typescript",
- "filename": "app/page.tsx",
- "content": "import { SignedIn, UserDropdown, SignedOut, SignInButton } from \"@thunderid/nextjs\";\n\nexport default function Home() {\n return (\n \n \n \n \n \n Sign In\n \n \n );\n}"
- }
- },
- {
- "step": 7,
- "title": "Run the app",
- "description": "Start your development server and test the authentication flow:",
- "code": {
- "language": "terminal",
- "content": "npm run dev"
- }
- }
- ]
+ "docsUrl": "{{applications.templates.nextjs.llmPrompt.redirectBased}}"
+ }
+ },
+ "EMBEDDED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.nextjs.llmPrompt.embedded}}"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/node.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/node.json
index 32934e8de8..7c0e13ae79 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/node.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/node.json
@@ -3,6 +3,19 @@
"type": "fullstack",
"displayName": "Node.js",
"description": "Backend service built with Node.js",
+ "quickstarts": [
+ {
+ "label": "Node.js",
+ "docsUrl": "{{applications.templates.node.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "Node.js",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.node.playground}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "COMPLETE"],
"previewSteps": []
@@ -37,96 +50,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your Node.js application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in a Node.js Application\n\n## Context\nI have a Node.js application and I want to integrate {{productName}} authentication using the @thunderid/node SDK and the built-in http module — no framework required.\n\n## Requirements\n- Use @thunderid/node SDK with the built-in Node.js http module\n- Initialize ThunderIDNodeClient with clientId, clientSecret, baseUrl, afterSignInUrl, afterSignOutUrl\n- Implement /login route to start the sign-in flow (redirects to {{productName}})\n- Implement /callback route to handle the OAuth authorization code exchange\n- Implement /logout route to sign out and clear the session cookie\n- Protect the /profile route using isSignedIn() and display user info with getUser()\n- Manage sessions using a session ID stored in an HttpOnly cookie\n- Keep code minimal and fully runnable with CommonJS require()\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Client Secret**: ``\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **App URL**: http://localhost:3000\n- **Callback Route**: /callback\n- **SDK**: @thunderid/node\n\n## Important Rules\n- Use CommonJS syntax (require) in index.js\n- Initialize ThunderIDNodeClient with auth.initialize({...}) before starting the server\n- signIn() works in two phases: first call redirects the user (authUrlCallback), second call (with code+state) exchanges the token\n- Store the session ID in a cookie named 'tid_session' with HttpOnly and SameSite=Lax flags\n- Use randomUUID() from the built-in 'crypto' module to generate session IDs\n- Use isSignedIn(sessionId) to guard protected routes\n- Use getUser(sessionId) to retrieve the authenticated user profile\n- Use signOut(sessionId) to get the OIDC end-session URL, then clear the local cookie and redirect\n\n## Implementation Steps\n1. Create a new project: mkdir my-node-app && cd my-node-app && npm init -y\n2. Install @thunderid/node: npm install @thunderid/node\n3. Create index.js with ThunderIDNodeClient initialization\n4. Add /login route: generate session ID cookie and redirect to {{productName}} auth URL\n5. Add /callback route: exchange authorization code for tokens using signIn()\n6. Add /logout route: call signOut() to get end-session URL, clear cookie, redirect\n7. Add / route: show sign-in or profile link based on isSignedIn()\n8. Add /profile route: guard with isSignedIn(), display user info from getUser()\n9. Start the server: node index.js\n\nPlease provide a complete, working index.js file with all routes and the ThunderIDNodeClient wired up correctly."
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a Node.js project",
- "description": "Initialize a new Node.js project:",
- "code": {
- "language": "terminal",
- "content": "mkdir my-node-app\ncd my-node-app\nnpm init -y"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/node",
- "description": "The {{productName}} Node.js SDK provides a framework-agnostic authentication client for server-side Node.js applications.",
- "subDescription": "Run the following command to install the SDK:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/node"
- }
- },
- {
- "step": 3,
- "title": "Initialize the client",
- "description": "Create an index.js file and initialize the ThunderIDNodeClient with your application credentials:",
- "code": {
- "language": "javascript",
- "filename": "index.js",
- "content": "const http = require('http');\nconst { URL } = require('url');\nconst { randomUUID } = require('crypto');\nconst { ThunderIDNodeClient } = require('@thunderid/node');\n\nconst PORT = 3000;\nconst SESSION_COOKIE = 'tid_session';\n\nconst auth = new ThunderIDNodeClient();\n\nfunction getSessionId(req) {\n const cookieHeader = req.headers.cookie ?? '';\n for (const part of cookieHeader.split(';')) {\n const [name, value] = part.trim().split('=');\n if (name === SESSION_COOKIE) return decodeURIComponent(value);\n }\n return null;\n}\n\nasync function main() {\n await auth.initialize({\n clientId: '{{clientId}}',\n clientSecret: '',\n baseUrl: 'https://localhost:8090',\n afterSignInUrl: 'http://localhost:3000/callback',\n afterSignOutUrl: 'http://localhost:3000',\n });\n\n const server = http.createServer(async (req, res) => {\n // routes added in the next step\n });\n\n server.listen(PORT, () => {\n console.log(`Server running on http://localhost:${PORT}`);\n });\n}\n\nmain();"
- }
- },
- {
- "step": 4,
- "title": "Add sign-in, callback, and sign-out routes",
- "description": "Replace the routes comment in index.js with sign-in, callback, and sign-out handling:",
- "code": {
- "language": "javascript",
- "filename": "index.js",
- "content": " const url = new URL(req.url, `http://localhost:${PORT}`);\n\n try {\n if (url.pathname === '/login') {\n let sessionId = getSessionId(req);\n const extraHeaders = {};\n if (!sessionId) {\n sessionId = randomUUID();\n extraHeaders['Set-Cookie'] =\n `${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;\n }\n await auth.signIn((authUrl) => {\n res.writeHead(302, { ...extraHeaders, Location: authUrl });\n res.end();\n }, sessionId);\n\n } else if (url.pathname === '/callback') {\n const code = url.searchParams.get('code');\n const state = url.searchParams.get('state');\n const sessionState = url.searchParams.get('session_state');\n const sessionId = getSessionId(req);\n\n if (!sessionId || !code || !state) {\n res.writeHead(400);\n return res.end('Bad request');\n }\n\n await auth.signIn(() => {}, sessionId, code, sessionState, state);\n res.writeHead(302, { Location: '/profile' });\n res.end();\n\n } else if (url.pathname === '/logout') {\n const sessionId = getSessionId(req);\n if (!sessionId) {\n res.writeHead(302, { Location: '/' });\n return res.end();\n }\n const signOutUrl = await auth.signOut(sessionId);\n res.writeHead(302, {\n Location: signOutUrl,\n 'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,\n });\n res.end();\n }\n } catch {\n res.writeHead(500);\n res.end('Internal server error');\n }"
- }
- },
- {
- "step": 5,
- "title": "Add protected profile route",
- "description": "Add the home and profile routes inside the same try block, before the closing }:",
- "code": {
- "language": "javascript",
- "filename": "index.js",
- "content": " if (url.pathname === '/') {\n const sessionId = getSessionId(req);\n const signedIn = sessionId && (await auth.isSignedIn(sessionId));\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(signedIn\n ? 'View profile | Sign out'\n : 'Sign in'\n );\n\n } else if (url.pathname === '/profile') {\n const sessionId = getSessionId(req);\n if (!sessionId || !(await auth.isSignedIn(sessionId))) {\n res.writeHead(302, { Location: '/login' });\n return res.end();\n }\n const user = await auth.getUser(sessionId);\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n
Welcome, ${user.name || user.username}!
\n
Email: ${user.email}
\n
First name: ${user.given_name}
\n
Last name: ${user.family_name}
\n Sign out\n `);\n }"
- }
- },
- {
- "step": 6,
- "title": "Update credentials",
- "description": "Replace the placeholders with your actual application credentials from {{productName}}:",
- "bullets": [
- "Replace `{{clientId}}` with your Client ID",
- "Replace `` with your Client Secret",
- "Ensure your authorized redirect URL is `http://localhost:3000/callback`"
- ]
- },
- {
- "step": 7,
- "title": "Run the app",
- "description": "Start your Node.js server:",
- "code": {
- "language": "terminal",
- "content": "node index.js"
- }
- },
- {
- "step": 8,
- "title": "Verify authentication flow",
- "description": "Test the integration end-to-end:",
- "bullets": [
- "Open `http://localhost:3000` and click Sign in to be redirected to {{productName}}",
- "Authenticate with your test user and confirm redirect back to `/profile`",
- "Verify user profile details are displayed on the profile page",
- "Click Sign out and verify the session is cleared"
- ]
- }
- ]
+ "docsUrl": "{{applications.templates.node.llmPrompt.redirectBased}}"
+ }
+ },
+ "EMBEDDED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.node.llmPrompt.embedded}}"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nuxt.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nuxt.json
index 3d27c28bfc..ef0751c12e 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nuxt.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/nuxt.json
@@ -3,6 +3,19 @@
"type": "fullstack",
"displayName": "Nuxt",
"description": "Full-stack Vue framework with server-side rendering",
+ "quickstarts": [
+ {
+ "label": "Nuxt",
+ "docsUrl": "{{applications.templates.nuxt.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "Nuxt",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.nuxt.playground}}"
+ }
+ ],
"devServer": {
"id": "nuxt",
"label": "Nuxt",
@@ -45,85 +58,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your Nuxt 3 application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in Nuxt 3 Application\n\n## Context\nI have a Nuxt 3 application and I want to integrate {{productName}}'s authentication system using the @thunderid/nuxt module with {{productName}}-hosted login pages.\n\n## Requirements\n- Use @thunderid/nuxt module for authentication\n- Register the module in nuxt.config.ts\n- Configure via environment variables (no inline config)\n- Wrap app.vue content with \n- Implement sign-in and sign-out with auto-imported components\n- Display signed-in user's profile information\n- Optionally protect pages with the built-in thunderIDMiddleware\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **Callback URL**: http://localhost:3000/api/auth/callback (auto-registered by the module)\n- **SDK**: @thunderid/nuxt\n\n## IMPORTANT Configuration Rules\n- Add '@thunderid/nuxt' to the modules array in nuxt.config.ts — no other config needed there\n- All configuration is read from environment variables with NUXT_PUBLIC_ prefix for public values\n- Required env vars: NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET\n- THUNDERID_CLIENT_SECRET and THUNDERID_SESSION_SECRET must NOT have the NUXT_PUBLIC_ prefix\n- The /api/auth/callback route is auto-registered by the module — do NOT create it manually\n- Wrap with in app.vue\n- All components (SignedIn, SignedOut, SignInButton, SignOutButton, User) and composables are auto-imported\n- Protect pages by adding definePageMeta({ middleware: ['thunderIDMiddleware'] })\n\n## Implementation Steps\n1. Create a Nuxt 3 app: npx nuxi@latest init my-nuxt-app\n2. Navigate into the project: cd my-nuxt-app && npm install\n3. Install @thunderid/nuxt: npm install @thunderid/nuxt\n4. Add '@thunderid/nuxt' to modules in nuxt.config.ts\n5. Create .env with NUXT_PUBLIC_THUNDERID_BASE_URL, NUXT_PUBLIC_THUNDERID_CLIENT_ID, THUNDERID_CLIENT_SECRET, THUNDERID_SESSION_SECRET\n6. Wrap with in app.vue\n7. Create pages/index.vue with SignInButton, SignOutButton, SignedIn, SignedOut, and User components\n8. Optionally add definePageMeta({ middleware: ['thunderIDMiddleware'] }) to protected pages\n9. Run: npm run dev\n\nPlease provide complete, working code with proper configuration for {{productName}} authentication using the @thunderid/nuxt module."
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a Nuxt 3 app",
- "description": "Run the following command to create a new Nuxt 3 app:",
- "code": {
- "language": "terminal",
- "content": "npm create nuxt@latest my-nuxt-app\ncd my-nuxt-app"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/nuxt",
- "description": "The {{productName}} Nuxt module provides server-side auth, auto-imported components, and route middleware.",
- "subDescription": "Run the following command to install the module:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/nuxt"
- }
- },
- {
- "step": 3,
- "title": "Register the module",
- "description": "Add @thunderid/nuxt to the modules array in nuxt.config.ts:",
- "code": {
- "language": "typescript",
- "filename": "nuxt.config.ts",
- "content": "export default defineNuxtConfig({\n compatibilityDate: '2025-07-15',\n devtools: { enabled: true },\n modules: ['@thunderid/nuxt'],\n})"
- }
- },
- {
- "step": 4,
- "title": "Set environment variables",
- "description": "Create a .env file in your project root with the following configuration:",
- "code": {
- "language": "bash",
- "filename": ".env",
- "content": "NUXT_PUBLIC_THUNDERID_BASE_URL=https://localhost:8090\nNUXT_PUBLIC_THUNDERID_CLIENT_ID={{clientId}}\nTHUNDERID_CLIENT_SECRET=\nTHUNDERID_SESSION_SECRET=\n# DANGER: Disables ALL TLS verification. Only for local development with self-signed certs. NEVER use in production.\nNODE_TLS_REJECT_UNAUTHORIZED=0"
- }
- },
- {
- "step": 5,
- "title": "Wrap your app with ",
- "description": "Update app.vue to wrap your application content with the ThunderIDRoot component:",
- "code": {
- "language": "vue",
- "filename": "app.vue",
- "content": "\n \n \n \n"
- }
- },
- {
- "step": 6,
- "title": "Build with ThunderID components",
- "description": "Update your home page with ThunderID authentication components (all auto-imported by the module):",
- "code": {
- "language": "vue",
- "filename": "app/pages/index.vue",
- "content": "\n \n \n \n \n \n Sign In\n \n \n"
- }
- },
- {
- "step": 7,
- "title": "Run the app",
- "description": "Start your development server and test the authentication flow:",
- "code": {
- "language": "terminal",
- "content": "npm run dev"
- }
- }
- ]
+ "docsUrl": "{{applications.templates.nuxt.llmPrompt.redirectBased}}"
+ }
+ },
+ "EMBEDDED": {
+ "llm_prompt": {
+ "docsUrl": "{{applications.templates.nuxt.llmPrompt.embedded}}"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/other.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/other.json
index 473b4b34db..43f1b613c7 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/other.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/other.json
@@ -3,6 +3,19 @@
"type": "browser",
"displayName": "Other",
"description": "Custom application with standard OAuth2 configuration",
+ "quickstarts": [
+ {
+ "label": "JavaScript",
+ "docsUrl": "{{applications.templates.browser.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "JavaScript",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.browser.playground}}"
+ }
+ ],
"creationFlow": {
"steps": ["ORGANIZATION_UNIT", "DETAILS", "SECURITY", "DESIGN", "CONFIGURE", "COMPLETE"],
"previewSteps": ["DETAILS", "SECURITY", "DESIGN"]
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/react.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/react.json
index 19ccf37685..eb8321a552 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/react.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/react.json
@@ -3,6 +3,19 @@
"type": "browser",
"displayName": "React",
"description": "Single Page Application built with React",
+ "quickstarts": [
+ {
+ "label": "React",
+ "docsUrl": "{{applications.templates.react.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "React",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.react.playground}}"
+ }
+ ],
"capabilities": {
"cors": true
},
@@ -49,133 +62,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your React application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in React Application (Inbuilt Mode)\n\n## Context\nI have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with {{productName}}-hosted login pages.\n\n## Requirements\n- Use @thunderid/react SDK for authentication\n- Configure {{productName}}-hosted login, registration, and account management UIs\n- Implement sign-in and sign-out functionality using prebuilt components\n- Display signed-in user's profile information\n- Handle authentication state automatically\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **SDK**: @thunderid/react\n\n## IMPORTANT Configuration Rules\n- DO NOT create a separate config object - pass all configuration as individual props directly to \n- Required props: `clientId` and `baseUrl`\n- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array)\n- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`\n- Example: \n\n## Implementation Steps\n1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`\n2. Navigate into the project directory: `cd my-react-app`\n3. Install dependencies: `npm install`\n4. Install @thunderid/react package: `npm install @thunderid/react`\n5. Wrap your app with and configure clientId and baseUrl\n6. Build with ThunderID components: use , , , and to control what signed-in and signed-out users see\n7. Run the development server: `npm run dev`\n\nPlease provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID React SDK."
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a React app using Vite",
- "description": "Run the following command to create a new React app with Vite:",
- "code": {
- "language": "terminal",
- "content": "npm create vite@latest my-react-app -- --template react\ncd my-react-app\nnpm install"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/react",
- "description": "The ThunderID React SDK provides prebuilt components, hooks, and helpers for {{productName}} authentication.",
- "subDescription": "Run the following command to install the SDK:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/react"
- }
- },
- {
- "step": 3,
- "title": "Add to your app",
- "description": "In your main.jsx or index.jsx, wrap your application with the ThunderIDProvider component:",
- "code": {
- "language": "javascript",
- "filename": "src/main.jsx",
- "content": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport { ThunderIDProvider } from '@thunderid/react'\nimport App from './App.jsx'\nimport './index.css'\n\ncreateRoot(document.getElementById('root')).render(\n \n \n \n \n \n)"
- }
- },
- {
- "step": 4,
- "title": "Build with ThunderID components",
- "description": "You can control which content signed-in and signed-out users can see with the prebuilt control components. Replace the existing content of App.jsx with the following:",
- "code": {
- "language": "javascript",
- "filename": "src/App.jsx",
- "content": "import {\n SignedIn,\n SignedOut,\n SignInButton,\n UserDropdown\n} from '@thunderid/react'\nimport './App.css'\n\nfunction App() {\n return (\n <>\n \n \n \n \n \n Sign In\n \n \n >\n )\n}\n\nexport default App"
- }
- },
- {
- "step": 5,
- "title": "Run the app",
- "description": "Start your development server and test the authentication flow:",
- "code": {
- "language": "terminal",
- "content": "npm run dev"
- }
- }
- ]
+ "docsUrl": "{{applications.templates.react.llmPrompt.redirectBased}}"
+ }
},
"EMBEDDED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your React application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in React Application (Custom Mode)\n\n## Context\nI have a React application and I want to integrate {{productName}}'s authentication system using the ThunderID React SDK with a custom login UI instead of {{productName}}-hosted pages.\n\n## Requirements\n- Use @thunderid/react SDK for authentication\n- Build custom login UI with the component\n- Use react-router for routing to the custom sign-in page\n- Configure custom signInUrl in ThunderIDProvider\n- Implement sign-in and sign-out functionality\n- Display signed-in user's profile information\n- Handle authentication state automatically\n\n## Configuration\n- **Application ID**: {{applicationId}}\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **Sign In URL**: http://localhost:5173/signin (custom route)\n- **SDK**: @thunderid/react\n- **Router**: react-router\n\n## IMPORTANT Configuration Rules\n- DO NOT create a separate config object - pass all configuration as individual props directly to \n- Required props: `baseUrl`, `signInUrl`, and `applicationId`\n- Optional props: `afterSignInUrl`, `afterSignOutUrl`, `scopes`\n- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`\n- Example: \n\n## Available SDK APIs\n\n### Hook: useThunderID (ONLY hook available)\n- The SDK provides ONLY ONE hook: `useThunderID`\n- No other hooks like useAuth, useSession, useUser, etc. exist\n- Use this hook to access authentication state and methods\n\nExample:\n```javascript\nimport { useThunderID } from '@thunderid/react';\n\nconst MyComponent = () => {\n const { isSignedIn, user, signIn, signOut } = useThunderID();\n\n return (\n
\n {isSignedIn ? (\n <>\n
Welcome, {user.displayName}!
\n \n >\n ) : (\n \n )}\n
\n );\n};\n```\n\n### User Data Components (use if needed)\n\n1. **User Component** - Render props for user data:\n```javascript\nimport { User } from '@thunderid/react'\n\nPlease sign in
}>\n {(user) => (\n
\n
Welcome, {user?.displayName}!
\n
Email: {user?.email}
\n
\n )}\n\n```\n\n2. **UserProfile Component** - Pre-built user profile UI:\n```javascript\nimport { UserProfile } from '@thunderid/react'\n\n\n```\n\n## Implementation Steps\n1. Create a React app using Vite by running: `npm create vite@latest my-react-app -- --template react`\n2. Navigate into the project directory: `cd my-react-app`\n3. Install dependencies: `npm install`\n4. Install react-router package: `npm install react-router`\n5. Install @thunderid/react package: `npm install @thunderid/react`\n6. Wrap your app with and configure baseUrl, signInUrl, and applicationId\n7. Build with ThunderID components: set up React Router with BrowserRouter, create a /signin route with the component, and use , , , , and for authentication UI\n8. Run the development server: `npm run dev`\n\nPlease provide complete, working code with:\n- Proper routing configuration\n- Custom sign-in page integration\n- Proper integration with the ThunderID React SDK\n- Use of useThunderID hook if programmatic access to auth state is needed"
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a React app using Vite",
- "description": "Run the following command to create a new React app with Vite:",
- "code": {
- "language": "terminal",
- "content": "npm create vite@latest my-react-app -- --template react\ncd my-react-app\nnpm install"
- }
- },
- {
- "step": 2,
- "title": "Install react-router",
- "description": "Install react-router for routing to the custom sign-in page:",
- "code": {
- "language": "terminal",
- "content": "npm install react-router"
- }
- },
- {
- "step": 3,
- "title": "Install @thunderid/react",
- "description": "The ThunderID React SDK provides prebuilt components, hooks, and helpers for {{productName}} authentication:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/react"
- }
- },
- {
- "step": 4,
- "title": "Add to your app",
- "description": "In your main.jsx or index.jsx, wrap your application with the ThunderIDProvider component and configure the signInUrl:",
- "code": {
- "language": "javascript",
- "filename": "src/main.jsx",
- "content": "import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport './index.css'\nimport App from './App.jsx'\nimport { ThunderIDProvider } from '@thunderid/react'\n\ncreateRoot(document.getElementById('root')).render(\n \n \n \n \n \n)"
- }
- },
- {
- "step": 5,
- "title": "Build with ThunderID components",
- "description": "You can control which content signed-in and signed-out users can see with the prebuilt control components. Replace the existing content of App.jsx with the following. This sets up routing, a dedicated sign-in page using the component, and uses for the signed-in user:",
- "code": {
- "language": "javascript",
- "filename": "src/App.jsx",
- "content": "import { BrowserRouter as Router, Routes, Route } from 'react-router'\nimport { SignIn, SignedIn, SignedOut, SignInButton, SignOutButton, UserDropdown } from '@thunderid/react'\nimport './App.css'\n\nfunction App() {\n return (\n \n \n \n \n \n \n \n \n \n \n \n } />\n \n \n )\n}\n\nexport default App"
- }
- },
- {
- "step": 6,
- "title": "Run the app",
- "description": "Start your development server and test the authentication flow with custom sign-in page:",
- "code": {
- "language": "terminal",
- "content": "npm run dev"
- }
- }
- ]
+ "docsUrl": "{{applications.templates.react.llmPrompt.embedded}}"
+ }
}
}
}
diff --git a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/vanilla-js.json b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/vanilla-js.json
index d7a66ad507..e49a31bcad 100644
--- a/frontend/apps/console/src/features/applications/data/application-templates/technology-based/vanilla-js.json
+++ b/frontend/apps/console/src/features/applications/data/application-templates/technology-based/vanilla-js.json
@@ -3,6 +3,19 @@
"type": "browser",
"displayName": "JavaScript",
"description": "Browser application built with vanilla JavaScript",
+ "quickstarts": [
+ {
+ "label": "JavaScript",
+ "docsUrl": "{{applications.templates.browser.docs}}"
+ }
+ ],
+ "playgrounds": [
+ {
+ "label": "JavaScript",
+ "environment": "stackblitz",
+ "url": "{{applications.templates.browser.playground}}"
+ }
+ ],
"capabilities": {
"cors": true
},
@@ -49,75 +62,15 @@
}
},
"integrationGuides": {
- "INBUILT": {
+ "REDIRECT_BASED": {
"llm_prompt": {
- "id": "llm-prompt",
- "title": "Integrate with a Coding Agent Prompt",
- "description": "Use AI to generate integration code for your JavaScript application",
- "type": "llm",
- "icon": "sparkles",
- "content": "# Integrate {{productName}} Authentication in Vanilla JavaScript Application\n\n## Context\nI have a vanilla JavaScript application and I want to integrate {{productName}}'s authentication system using the ThunderID Browser SDK with {{productName}}-hosted login pages.\n\n## Requirements\n- Use @thunderid/browser SDK for authentication\n- Configure {{productName}}-hosted login pages\n- Implement sign-in and sign-out functionality\n- Display signed-in user's profile information\n- Handle authentication state\n\n## Configuration\n- **Client ID**: {{clientId}}\n- **Base URL**: https://localhost:8090 (or your {{productName}} instance URL)\n- **SDK**: @thunderid/browser\n\n## IMPORTANT Configuration Rules\n- Create a ThunderIDBrowserClient instance and call initialize() with a config object\n- Required config properties: `clientId` and `baseUrl`\n- Optional config properties: `afterSignInUrl`, `afterSignOutUrl`, `scopes` (string array), `storage`\n- Storage options: 'sessionStorage' (default), 'localStorage', 'browserMemory'\n- NEVER use property names like `signInRedirectURL` or `signOutRedirectURL`\n\n## Available SDK APIs\n\n### ThunderIDBrowserClient\n- `initialize(config)` - Initialize the client with configuration\n- `signIn()` - Redirect to {{productName}} sign-in page\n- `signOut()` - Sign out and clear session\n- `isSignedIn()` - Check if user is signed in (returns Promise)\n- `getUser()` - Get authenticated user profile (returns Promise)\n- `getAccessToken()` - Get current access token\n- `getIdToken()` - Get ID token\n- `getDecodedIdToken()` - Get decoded ID token claims\n- `httpRequest(config)` - Make authenticated HTTP request\n- `on(hook, callback)` - Register event callbacks (Hooks.SignIn, Hooks.SignOut, etc.)\n\n### User Object Properties\n- `displayName` - User's display name\n- `username` - Username\n- `email` - Email address\n- `given_name` - First name\n- `family_name` - Last name\n- `picture` - Profile picture URL\n\n## Implementation Steps\n1. Create a vanilla JS app using Vite by running: `npm create vite@latest js-demo -- --template vanilla`\n2. Navigate into the project directory: `cd js-demo`\n3. Install dependencies: `npm install`\n4. Install @thunderid/browser package: `npm install @thunderid/browser`\n5. Create src/auth.js to initialize ThunderIDBrowserClient with clientId and baseUrl\n6. Update src/main.js to check isSignedIn(), show sign-in button or user profile\n7. Add event listeners for sign-in and sign-out buttons\n8. Run the development server: `npm run dev`\n\nPlease provide complete, working code with proper configuration for {{productName}} authentication using the ThunderID Browser SDK."
- },
- "manual_steps": [
- {
- "step": 1,
- "title": "Create a JavaScript app using Vite",
- "description": "Run the following command to create a new vanilla JavaScript app with Vite:",
- "code": {
- "language": "terminal",
- "content": "npm create vite@latest js-demo -- --template vanilla\ncd js-demo\nnpm install"
- }
- },
- {
- "step": 2,
- "title": "Install @thunderid/browser",
- "description": "The ThunderID Browser SDK provides a framework-agnostic authentication client for browser applications.",
- "subDescription": "Run the following command to install the SDK:",
- "code": {
- "language": "terminal",
- "content": "npm install @thunderid/browser"
- }
- },
- {
- "step": 3,
- "title": "Initialize the SDK",
- "description": "Create a new file src/auth.js to initialize and export the ThunderIDBrowserClient:",
- "code": {
- "language": "javascript",
- "filename": "src/auth.js",
- "content": "import { ThunderIDBrowserClient } from '@thunderid/browser'\n\nconst auth = new ThunderIDBrowserClient()\n\nawait auth.initialize({\n clientId: '{{clientId}}',\n baseUrl: 'https://localhost:8090',\n afterSignInUrl: window.location.origin,\n afterSignOutUrl: window.location.origin,\n})\n\nexport default auth"
- }
- },
- {
- "step": 4,
- "title": "Add sign-in and sign-out",
- "description": "Replace the content of src/main.js with the following to add authentication:",
- "code": {
- "language": "javascript",
- "filename": "src/main.js",
- "content": "import './style.css'\nimport auth from './auth.js'\n\nasync function renderApp() {\n const isSignedIn = await auth.isSignedIn()\n\n if (isSignedIn) {\n const user = await auth.getUser()\n\n document.querySelector('#app').innerHTML = `\n