Framework SDKs and edge middleware
React, Vue, Next.js and an offline session verifier for Workers and the edge, all on top of the CapchaID browser SDK and its public JWKS.
@capchacloud/id-edge
Offline CapchaID session verification for Cloudflare Workers, edge runtimes, Node 18+ and Deno/Bun. Verifies the ES256 session JWT against the JWKS, with caching, key rotation, stale-if-error and an optional revocation check. No dependencies.
import { verifySession, sessionErrorResponse } from "@capchacloud/id-edge";
const opts = { audience: "YOUR_CAPCHAID_APPLICATION_ID" }; // the JWT's aud claim
export default {
async fetch(request: Request): Promise<Response> {
const session = await verifySession(request, opts); // Authorization: Bearer <access token>
if (!session.ok) return sessionErrorResponse(session); // 401, or 503 if the auth backend is down
return Response.json({ user: session.claims.sub, email: session.claims.email });
},
};
- Verification is offline: the JWKS (https://id.capchacloud.com/.well-known/jwks.json) is fetched once and cached (5 minutes fresh, up to 24 hours stale if the auth origin is down), then every request is a local signature check.
- Only ES256 is accepted. `none`, HS256 and RS256 tokens are refused before any key is looked up. `iss`, `aud`, `exp`, `nbf` and `iat` are checked with a clock-skew allowance (default 30 s, max 300 s).
- An unknown key id triggers at most one JWKS refetch per 30 seconds, so key rotation works and random-kid spraying cannot hammer the auth origin.
- Offline verification cannot know a session was revoked. For that, pass `revocation: { url: 'https://id.capchacloud.com/v1/sessions/verify', secretKey }` (server-side secret key only): the verifier then confirms with capcha-id on each check and fails closed if it cannot.
@capchacloud/id-core
Framework-neutral CapchaID browser session store that the React and Vue bindings share: SDK loading, auth-state snapshots, optional first-party session-cookie sync, passkey autofill helpers. No dependencies.
import { createAuthStore, loadCapchaIDClient } from "@capchacloud/id-core";
const store = createAuthStore({
loadClient: loadCapchaIDClient({ publishableKey: "cid_pk_live_..." }),
syncCookie: { name: "cid_at" }, // optional: lets server code verify the session
});
store.subscribe(() => console.log(store.getSnapshot())); // { isLoaded, isSignedIn, user, error }
await store.start(); // loads the SDK and restores the session
- You normally use this through @capchacloud/id-react or @capchacloud/id-vue rather than directly.
- The optional session cookie is written by JavaScript, so it is not httpOnly. It carries only the short-lived (about an hour) access token, never the refresh token. Know that trade-off before enabling it.
@capchacloud/id-react
React hooks and components for CapchaID: CapchaIDProvider, useSession, useUser, SignedIn / SignedOut, SignInButton and PasskeyButton with passkey autofill (conditional UI). React 18+.
import { CapchaIDProvider, SignedIn, SignedOut, SignInButton, PasskeyButton, useUser } from "@capchacloud/id-react";
const Hello = () => <p>Signed in as {useUser().user?.email}</p>;
export const App = () => (
<CapchaIDProvider publishableKey="cid_pk_live_...">
<SignedOut><SignInButton provider="google" /> <PasskeyButton conditionalInputId="email" /></SignedOut>
<SignedIn><Hello /></SignedIn>
</CapchaIDProvider>
);
- Nothing auth-dependent renders until the session restore finished (`isLoaded`), and the server snapshot is always signed out, so server rendering and hydration agree.
- A failed SDK load or session restore is reported in `useSession().error`; it is not silently treated as signed out.
- Pass `syncCookie={{ name: 'cid_at' }}` to the provider to mirror the access token into a first-party cookie for @capchacloud/id-next.
- `PasskeyButton conditionalInputId` marks your email input for the browser's passkey autofill dropdown. The click path always works as the fallback. Needs a CapchaID SDK build that ships passkey-conditional.js.
@capchacloud/id-vue
Vue 3 plugin and composables for CapchaID: createCapchaID, useSession, useUser, SignedIn / SignedOut, PasskeyButton and usePasskeyAutofill.
import { createApp } from "vue";
import { createCapchaID, useSession } from "@capchacloud/id-vue";
import App from "./App.vue";
createApp(App).use(createCapchaID({ publishableKey: "cid_pk_live_..." })).mount("#app");
// inside any component's <script setup>:
const { isLoaded, isSignedIn, user, getToken, signOut } = useSession(); // refs
// <SignedIn>...</SignedIn> / <SignedOut>...</SignedOut> components are exported too
- Same store, same guarantees as the React bindings (loaded gate, explicit error state, StrictMode-style remount safe).
- `usePasskeyAutofill({ input: 'email', onSignedIn })` wires passkey autofill to an input and aborts on unmount.
@capchacloud/id-next
Next.js integration for CapchaID: an edge-safe authMiddleware, auth() for Server Components and Route Handlers, and withAuth() for API routes. Offline JWT verification via @capchacloud/id-edge. Next 13.4+.
// middleware.ts
import { authMiddleware } from "@capchacloud/id-next";
export default authMiddleware({ audience: process.env.CAPCHAID_APP_ID!, cookieName: "cid_at", publicRoutes: ["/", "/pricing", "/sign-in"], signInUrl: "/sign-in" });
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };
// app/dashboard/page.tsx (Server Component)
import { auth } from "@capchacloud/id-next";
const { userId } = await auth({ audience: process.env.CAPCHAID_APP_ID!, cookieName: "cid_at" });
- Fail closed: with no route lists every matched path needs a session. Paths are normalised (duplicate and trailing slashes) before matching, and RegExp patterns must match the whole path.
- Inbound `x-capcha-*` headers are always stripped; identity headers (`x-capcha-user-id`, `x-capcha-app-id`, `x-capcha-org-id`) are set only from a verified session. `auth()` re-verifies the token itself and does not trust those headers.
- If the auth backend is unreachable the middleware answers 503, never a redirect to sign-in (which would loop) and never allow.
- Browsers hold the access token in memory. For server-rendered pages, enable the first-party cookie with `syncCookie={{ name: 'cid_at' }}` on the React provider (JavaScript-written, short-lived, not httpOnly), or send `Authorization: Bearer` from your own fetch calls.
- Verified against real Next 14 types; the middleware adapter itself (src/index.ts) is a thin wrapper over the tested core and has not been run inside a live Next server.
Browser SDK (script tag)
Framework-agnostic. https://capchacloud.com/integrations/capchacloud-browser.js exposes CapchaCloudBrowser.init({ clientId }) with load(), mount(), on() and typed events widget:loading, widget:loaded, widget:ready, capture:success, widget:error. It also ships in @capchacloud/sdk/browser.