Sign in with your existing session
Introduction
Section titled “Introduction”You will connect the chat to a SaaS app that already has its own login. The browser sends the app’s session token, and the server asks your /auth/me endpoint who it belongs to, caching the answer for a minute.
What you need
Section titled “What you need”- A login that issues a bearer token, and a
GET /auth/meendpoint that returns the current user. - The server from The server and a React client from Add chat to a React app.
1. Send your token from the client
Section titled “1. Send your token from the client”getToken runs before every request and every socket (re)connection, so it must read the live token, not a copy.
import { ChatProvider } from '@pilotoo/ui/chat';
import { getAccessToken } from '../auth/session';
export function Assistant({ children }: { children: React.ReactNode }) { return ( <ChatProvider baseUrl={import.meta.env.VITE_ASSISTANT_URL} getToken={() => getAccessToken() ?? undefined}> {children} </ChatProvider> );}2. Resolve the identity on the server
Section titled “2. Resolve the identity on the server”createHttpIdentityResolver forwards the caller’s Authorization header to your endpoint and reads the answer. map turns your response into the kit’s identity: { subject, tenant, roles }.
import { createHttpIdentityResolver, type IdentityResolver } from '@pilotoo/agent-runtime';
type Me = { user?: { id: string; permissions?: string[] }; tenant?: { id: string };};
export function createAppIdentityResolver(apiUrl: string): IdentityResolver { return createHttpIdentityResolver({ url: `${apiUrl}/auth/me`, timeoutMs: 3000, map: (body) => { const me = body as Me; return { subject: me.user?.id, tenant: me.tenant?.id, // The kit matches role names, so each permission becomes a role. roles: (me.user?.permissions ?? []).map((code) => `permission:${code}`), }; }, });}A 401 or 403 from your endpoint means “not signed in”. A body that does not map to an identity is an error, and the request fails.
3. Cache the answer
Section titled “3. Cache the answer”The identity is asked for on every request and every connection. Wrap the resolver to avoid calling your API each time.
import { createHash } from 'node:crypto';
import type { Credentials, Identity, IdentityResolver } from '@pilotoo/agent-runtime';
export function cachedIdentity(inner: IdentityResolver, ttlMs = 60_000): IdentityResolver { const entries = new Map<string, { identity: Identity | undefined; expires: number }>();
return { async resolve(credentials: Credentials) { const key = createHash('sha256') .update(JSON.stringify([credentials.authorization, credentials.headers])) .digest('hex'); const hit = entries.get(key); if (hit && hit.expires > Date.now()) return hit.identity;
// A thrown error is not cached: the next request asks again. const identity = await inner.resolve(credentials); entries.set(key, { identity, expires: Date.now() + ttlMs }); return identity; }, };}4. Pass the resolver to the server
Section titled “4. Pass the resolver to the server”import { Module } from '@nestjs/common';import { PilotooModule } from '@pilotoo/server';
import { cachedIdentity } from './cached-identity';import { createAppIdentityResolver } from './identity';
@Module({ imports: [ PilotooModule.forRoot({ env: process.env, identityResolver: cachedIdentity(createAppIdentityResolver(process.env.API_URL!)), identityHeaders: ['x-tenant-id'], }), ],})export class AppModule {}identityHeaders lists request headers that are copied to the resolver (credentials.headers, names lower-case) and forwarded to your endpoint, for an identity service that picks the tenant by header. It is optional.
How it works
Section titled “How it works”- Every REST call and socket handshake goes through the resolver. The result decides which conversations and attachments the caller can see.
- With
AUTH_IDENTITY_URLset and noidentityResolver, the kit builds a plain HTTP resolver for you. Use the code above when you needmap, a timeout or a cache. - With neither, every caller is a local owner. Never run that on a public host.
Next steps
Section titled “Next steps”- Authentication: the identity shape and the resolver contract.
- Let a tool call your own API: reuse the caller’s token in a tool.
- Multi-tenant SaaS: per-tenant skills and roles.