Scrydon
PlatformIdentity Provider (IdP)

Flagship demo: Sign in with Scrydon + Chat API

Build a tiny React app that signs users in with Scrydon as OIDC provider, then calls a chat deployment using the access token. End-to-end proof that the IdP, scopes, and resource APIs work together.

This demo is the one to run when you want to show somebody — a prospect, an internal stakeholder, a new developer on your team — that the Scrydon IdP actually does something useful. It covers the whole loop:

  1. A user hits an external app (not Scrydon).
  2. The app kicks them to Scrydon to sign in.
  3. Scrydon issues an access token scoped to chat.
  4. The app uses that token to talk to a Scrydon chat deployment on behalf of the signed-in user, with full tenant + workspace isolation.

If this works, the IdP, consent flow, scope enforcement, workspace binding, and the Chat resource server are all correctly wired.

What you'll build

A single-page React app with three screens:

  • Sign in — one button that starts the OIDC flow.
  • Signed-in dashboard — shows the user's name and email from the ID token.
  • Chat panel — streams responses from a Scrydon chat deployment using the user's access token.

The app lives on http://localhost:3000 during development. You can ship it to any static host later.

Prerequisites

  • A Scrydon workspace with at least one chat deployment published. If you don't have one, create it at apps → chat → New Chat and publish the deployment.
  • A Registered Mini App with the chat scope enabled and redirect URI set to http://localhost:3000/callback. See Register an OAuth client.
  • Node.js 20+ and a package manager (bun / pnpm / npm).

Option A — Use the built-in boilerplate prompt

When you click Register Mini App, Scrydon's "Get Started" panel offers you a ready-made prompt you can paste into Lovable, v0, or Bolt to scaffold a working app. This is the fastest path — the prompt already includes the correct client ID, redirect URI, scopes, and base URL.

Open the Get Started panel

Right after you click Register App, the dialog switches to the "Get Started" panel with your new client IDs and a tabbed Boilerplate Prompt.

Pick your tool

Lovable, v0, Bolt, or Manual. Copy the prompt.

Paste it into the tool

The generated app will include the OIDC sign-in flow, token handling, and a working chat UI wired to your workspace. You'll have a running demo in minutes.

Option B — Manual scaffold (reference implementation)

If you want to understand what the boilerplate does — or you're integrating into an existing app — here is the minimum viable implementation.

1. Install dependencies

bun create vite@latest scrydon-demo --template react-ts
cd scrydon-demo
bun add oidc-client-ts

oidc-client-ts is a well-maintained OIDC library that handles PKCE, silent refresh, and state storage for you. Any other OIDC library (e.g. openid-client for Node backends) works equivalently.

2. Configure the OIDC client

// src/auth.ts
import { UserManager, WebStorageStateStore } from "oidc-client-ts";

const AUTH_URL = import.meta.env.VITE_AUTH_URL; // e.g. https://staging.api-platform.scrydon.com
const CLIENT_ID = import.meta.env.VITE_CLIENT_ID;

export const userManager = new UserManager({
  authority: AUTH_URL + "/api/auth",
  client_id: CLIENT_ID,
  redirect_uri: "http://localhost:3000/callback",
  response_type: "code",
  scope: "openid profile email chat",
  userStore: new WebStorageStateStore({ store: window.sessionStorage }),
  // PKCE is the default for public clients
});

The authority URL is the base Scrydon auth path — the library appends .well-known/openid-configuration itself.

3. Wire the sign-in + callback routes

// src/App.tsx
import { useEffect, useState } from "react";
import { userManager } from "./auth";
import type { User } from "oidc-client-ts";
import { ChatPanel } from "./ChatPanel";

export function App() {
  const [user, setUser] = useState<User | null>(null);

  useEffect(() => {
    // Handle the OAuth callback on /callback
    if (window.location.pathname === "/callback") {
      userManager.signinRedirectCallback().then((u) => {
        setUser(u);
        window.history.replaceState({}, "", "/");
      });
      return;
    }
    userManager.getUser().then(setUser);
  }, []);

  if (!user) {
    return (
      <button onClick={() => userManager.signinRedirect()}>
        Sign in with Scrydon
      </button>
    );
  }

  return (
    <div>
      <header>
        <p>Signed in as {user.profile.email}</p>
        <button onClick={() => userManager.signoutRedirect()}>Sign out</button>
      </header>
      <ChatPanel accessToken={user.access_token} />
    </div>
  );
}

4. Call the Chat API with the access token

// src/ChatPanel.tsx
import { useState } from "react";

const AGENTIC_URL = import.meta.env.VITE_AGENTIC_URL; // e.g. https://staging.agentic.scrydon.com
const DEPLOYMENT_ID = import.meta.env.VITE_DEPLOYMENT_ID;

export function ChatPanel({ accessToken }: { accessToken: string }) {
  const [messages, setMessages] = useState<
    Array<{ role: "user" | "assistant"; content: string }>
  >([]);
  const [input, setInput] = useState("");

  async function send() {
    const next = [...messages, { role: "user" as const, content: input }];
    setMessages(next);
    setInput("");

    const res = await fetch(
      `${AGENTIC_URL}/api/chat/deployments/${DEPLOYMENT_ID}/messages`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${accessToken}`,
        },
        body: JSON.stringify({ messages: next }),
      },
    );
    const data = await res.json();
    setMessages([...next, { role: "assistant", content: data.reply }]);
  }

  return (
    <div>
      <ul>
        {messages.map((m, i) => (
          <li key={i}>
            <strong>{m.role}:</strong> {m.content}
          </li>
        ))}
      </ul>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && send()}
      />
      <button onClick={send}>Send</button>
    </div>
  );
}

The chat API path and request shape shown here is illustrative. Your specific deployment may expose a streaming variant, tool-calling metadata, or different field names — check the app details in Scrydon or use the TypeScript SDK (@scrydon/sdk) for a typed experience.

5. Environment variables

Create .env.local:

VITE_AUTH_URL=https://staging.api-platform.scrydon.com
VITE_AGENTIC_URL=https://staging.agentic.scrydon.com
VITE_CLIENT_ID=paste-from-registered-apps
VITE_DEPLOYMENT_ID=paste-from-your-chat-deployment

Copy the URLs from the Identity tab on Settings → Platform → Identity, the client ID from the Registered Apps "Get Started" panel, and the deployment ID from the chat deployment's detail page.

6. Run it

bun dev

Open http://localhost:3000, click Sign in with Scrydon, complete the flow, and try a chat message.

What to show in a live demo

MomentWhat you demonstrate
Click Sign in with ScrydonExternal app is not Scrydon — the user is about to be federated in.
Redirect to Scrydon sign-inThe auth host (staging.api-platform.scrydon.com or equivalent) serves the branded sign-in page.
Consent screen (for untrusted clients)Explicit, user-visible scope grant — a compliance bullet point you can point to.
Back to the demo app with tokensPKCE-secured authorization code flow. Open devtools → Network tab to show the /oauth2/token exchange.
Decode the ID tokenjwt.io shows email, name, org_id, workspace_id, environment_id — proves tenant binding.
Send a chat messageDemonstrates scope enforcement on the resource server.
Open a second workspace or environment, switch the client IDSame user, same IdP, but different environment_id claim → different chat responses. Proves isolation.

Story-line for an external prospect

"Your support portal lets customers sign in with their Scrydon identity. Because we're the IdP, you don't store passwords, you don't manage MFA, and you don't keep your own user table. Every token we issue carries the workspace and environment the user belongs to, so the chat they talk to is exactly the one their tenant admin configured — no cross-tenant leakage, enforced at the API boundary."

Troubleshooting

The user signs in but the app can't call the Chat API

  • Make sure the chat scope is on your Mini App's Allowed Scopes list and that your OIDC client requested it. openid profile email on its own is not enough to call resource APIs.
  • Decode the access token (if it's a JWT) or hit /oauth2/introspect to confirm chat is present in the scope claim.

401 from the chat deployment

  • The Authorization: Bearer ... header must use the access token, not the ID token. The ID token identifies the user to the client; the access token authorizes calls to resource APIs.

CORS error calling the agentic app

  • The agentic app's CORS allow-list is derived from registered redirect URIs. Your local origin (http://localhost:3000) must be present on your Mini App; if it isn't, add it to the redirect URIs list (the origin portion is what CORS enforces).
  • That's expected for non-trusted clients. Only the first-party Scrydon agentic app is in the trusted-client set by default. If you want your own Mini App to skip consent (for example, a corporate internal tool), contact Scrydon support to add it to the trusted set for your tenant.
On this page

On this page