TypeScript & Next.js
@qeetid/sdk for server-side token verification and management, and @qeetid/nextjs for the hosted-login flow with silent refresh.
There are two TypeScript packages, for two jobs:
@qeetid/sdk— a server-side client: verify access tokens, run authorization checks, and manage users/tenants. Zero runtime dependencies (built-infetch+node:crypto, Node ≥18; also Bun, Deno, edge).@qeetid/nextjs— drives the hosted-login OAuth flow for App Router apps: route handlers, Edge middleware, and silent refresh.
@qeetid/sdk (server)
pnpm add @qeetid/sdkimport { Qeetid } from "@qeetid/sdk";
export const qeetid = new Qeetid({
apiKey: process.env.QEETID_API_KEY!, // qk_… — server only
// baseUrl: "https://api.qeetid.com", // default
// timeoutMs: 10_000, maxRetries: 2,
});Verify a session
Local — checks the ES256 signature against the cached JWKS, then expiry/issuer/audience.
const claims = await qeetid.sessions.verify(accessToken);
// claims.userId, claims.tenantId, claims.sessionIdAuthorize
if (await qeetid.can({ user: claims.userId, tenant: claims.tenantId!, permission: "billing:write" })) {
// …
}Manage users & tenants
const user = await qeetid.users.create({ email: "new@acme.com", display_name: "New User" });
for await (const u of qeetid.users.listAll({ tenant: "acme" })) console.log(u.email);Errors
import { RateLimitError, InvalidCredentialsError } from "@qeetid/sdk";
try {
await qeetid.users.get("usr_missing");
} catch (err) {
if (err instanceof RateLimitError) await wait(err.retryAfterSeconds);
else if (err instanceof InvalidCredentialsError) rotateApiKey();
else throw err;
}429 and idempotent 5xx are retried automatically with backoff, honoring
Retry-After.
@qeetid/nextjs (hosted login)
pnpm add @qeetid/nextjsEnvironment
QEETID_CLIENT_ID=qci_…
QEETID_CLIENT_SECRET=…
QEETID_API_URL=https://api.qeetid.com
QEETID_APP_URL=https://app.acme.com
QEETID_COOKIE_SECRET=… # ≥32 random chars
# QEETID_SCOPES="openid profile email"Register ${QEETID_APP_URL}/api/auth/callback as a redirect URI and
${QEETID_APP_URL} as a post-logout URI on your OIDC client.
Mount the auth routes
import { handleAuth } from "@qeetid/nextjs";
export const GET = handleAuth(); // /api/auth/login | /callback | /logoutProtect routes (Edge middleware)
import { qeetidMiddleware } from "@qeetid/nextjs/middleware";
export default qeetidMiddleware({ publicRoutes: ["/", "/pricing"] });
export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"] };Unauthenticated requests are redirected to hosted login; near-expiry sessions are silently refreshed (the rotated refresh token is persisted) so users aren't bounced.
Read the user
import { auth, currentUser } from "@qeetid/nextjs";
export default async function Page() {
const { isAuthenticated, userId, tenantId } = await auth();
if (!isAuthenticated) return null;
const user = await currentUser(); // OIDC userinfo, or null
return <p>Hello {user?.sub}</p>;
}| Export | Use |
|---|---|
handleAuth() | Route handler for /api/auth/[...qeetid]. |
qeetidMiddleware(opts) | Route protection + silent refresh (Edge). |
auth() | { isAuthenticated, userId, tenantId, sessionId, accessToken }. |
currentUser() | OIDC userinfo, or null. |
getToken() | Current access token to call your own APIs. |
How it splits: the Edge middleware uses Web Crypto only (no Node code in the Edge
bundle); auth() runs in the Node runtime and verifies the ES256 token via
@qeetid/sdk. Pair with @qeetid/react for UI.