auth
@briven/auth is a drop-in sign-in client for your briven project. one package gives you email + password, magic links, email OTP, passkeys (WebAuthn), and OAuth social login — plus React hooks and a prebuilt <BrivenSignIn /> panel. the SDK talks to the hosted briven auth service; you never run an auth server yourself.
setup in 10 minutes
the shortest path from zero to a working sign-in. for a browser-only sign-off after this, use the plain-language checklist in the briven repo: AUTH-GO-LIVE-CHECKLIST.md.
- open your project at briven.tech → Auth → enable auth if it is not already on.
- Auth → API keys → create a key → copy
pk_briven_auth_…(browser-safe). never put abrk_…key in the browser. - in your app folder, wire the project first:
that writes# NEW project: briven setup my-app # EXISTING: briven connect p_… briven auth scaffold pnpm add @briven/authmiddleware.ts,lib/auth.ts, and a seeded.env.localif missing. never usesetup --projectto attach — that flag is gone; usebriven connect. - paste the public key into
NEXT_PUBLIC_BRIVEN_AUTH_KEY(and the same value intoBRIVEN_AUTH_PUBLIC_KEYfor middleware). - send users to the hosted page or drop in the panel:
// hosted (fastest pilot) window.location.assign(auth.hostedPageURL('sign-in', '/dashboard')); // or embedded React panel import { BrivenSignIn } from '@briven/auth/react'; // <BrivenSignIn redirectTo="/dashboard" /> - prove it in a real browser: sign up, sign out, sign in, refresh still signed in. optional: wrong-password rejection + rate limit after many failures (production needs redis —
/readymust showredis: ok).
starter copy: examples/auth-pilot/ in the briven monorepo.
install
published to public npm — see npm for the current version.
$ npm install @briven/auth
subpaths: @briven/auth (the vanilla client, zero React deps), @briven/auth/react (hooks + the <BrivenSignIn /> component), and @briven/auth/server (Next.js App Router helpers).
get your keys
you need two things from the dashboard: your project id (looks like p_01HZ…) and a public auth key (looks like pk_briven_auth_…).
- open your project at briven.tech.
- go to Auth → API keys.
- click create key, give it a name, set the scope to
read-write. - copy the
pk_briven_auth_…value — it is shown once.
pk_briven_auth_ key is browser-safe. it identifies your tenant and unlocks the end-user sign-in surface only — it can't read or write your data. ship it to the browser freely. your server brk_* keys are not browser-safe; never put one in client code.create the client
createBrivenAuth returns a stateless client. all auth state lives in the browser cookie set by the service on a successful sign-in, so re-creating the client across renders is safe.
// lib/auth.ts
import { createBrivenAuth } from '@briven/auth';
export const auth = createBrivenAuth({
projectId: 'p_01HZ...',
publicKey: process.env.NEXT_PUBLIC_BRIVEN_AUTH_KEY!, // 'pk_briven_auth_...'
});options: projectId and publicKey are required. apiOrigin defaults to https://api.briven.tech (override it only for self-hosted or local dev). authUrl defaults to https://<projectId>.auth.briven.tech and backs the OAuth redirect.
the client exposes signIn, signUp.email, signOut(), getSession(), and getUser().
react bindings
import from @briven/auth/react. wrap your app in the provider, then read state with the hooks or drop in the prebuilt panel. works in any React 19 environment — no hard Next.js dependency.
provider
// app/providers.tsx
'use client';
import { BrivenAuthProvider } from '@briven/auth/react';
import { auth } from '../lib/auth';
export function Providers({ children }: { children: React.ReactNode }) {
return <BrivenAuthProvider value={auth}>{children}</BrivenAuthProvider>;
}hooks
useSession() returns { session, isLoading, refresh }; useUser() returns { user, isLoading, refresh }. both fetch once on mount — call refresh() after a sign-in or sign-out to re-read.
'use client';
import { useSession, useUser } from '@briven/auth/react';
export function AccountBadge() {
const { session, isLoading } = useSession();
const { user } = useUser();
if (isLoading) return <span>…</span>;
if (!session?.authenticated) return <a href="/sign-in">sign in</a>;
return <span>signed in</span>; // don't render user.email into the UI — see security
}prebuilt panel
<BrivenSignIn />renders email + password, magic link, and your chosen OAuth buttons in one panel. it carries no icon library and no css framework — your app's styles apply via className and the element classes.
'use client';
import { BrivenSignIn } from '@briven/auth/react';
export function SignIn() {
return (
<BrivenSignIn
providers={['google', 'github', 'konnos']}
showEmailPassword
showMagicLink
redirectTo="/dashboard"
onSuccess={({ userId }) => console.log('signed in', userId)}
className="my-signin"
/>
);
}props: providers (OAuth buttons to show — empty array hides the section), showEmailPassword (default true), showMagicLink (default true), redirectTo, onSuccess, and className.
sign-in flows
if you want your own UI, call the client methods directly. every async method returns a tagged result — { ok: true, ... } on success or { ok: false, code, message } on failure — so you never throw on a bad password.
email + password
// sign up, then sign in — both return { ok, userId, sessionExpiresAt }
await auth.signUp.email({ email, password, name: 'Jane' });
const result = await auth.signIn.email({ email, password });
if (result.ok) {
// result.userId, result.sessionExpiresAt
} else {
// result.code: 'invalid_credentials' | 'unverified_email' | 'rate_limited' | ...
console.error(result.message);
}magic link
a passwordless flow of its own — not an OAuth provider. it emails the user a one-click sign-in link.
const res = await auth.signIn.magicLink({
email,
redirectTo: '/dashboard', // where the link lands after verify
});
// res.ok === true means the email was sentemail OTP
// 1. send the code
await auth.signIn.otpRequest({ email });
// 2. verify the 6-digit code the user typed
const result = await auth.signIn.otpVerify({ email, otp: '123456' });
if (result.ok) { /* result.userId */ }OAuth social
social() is synchronous — it builds the start URL and hands it back. you redirect the browser to it. provider is one of 'google', 'github', 'discord', 'microsoft', or 'konnos'.
const { redirectUrl } = auth.signIn.social({
provider: 'google',
redirectTo: '/dashboard',
});
window.location.assign(redirectUrl);passkeys (WebAuthn)
passwordless sign-in backed by the device's platform authenticator (Touch ID, Face ID, Windows Hello, a hardware key). two ceremonies, both wrapped for you so you never touch the raw navigator.credentials calls:
// REGISTER — adds a passkey to the *currently signed-in* user.
// call this from an account-settings screen, after a normal sign-in.
const reg = await auth.passkey.register();
if (reg.ok) { /* passkey saved on this device */ }
// SIGN IN — no password, no email field needed.
const result = await auth.passkey.signIn();
if (result.ok) {
// result.userId, result.sessionExpiresAt
} else {
// result.code: 'not_enabled' | 'cancelled' | 'unsupported' | ...
console.error(result.message);
}passkeys are per-tenant: enable under Auth → providers. auth.passkey.register() needs an active session first (magic link / OTP / password), then Face ID / Touch ID runs in the browser. platform routes: GET /v1/auth-tenant/passkey/generate-authenticate-options (sign-in) and GET …/generate-register-options (enrol) — not POST on those paths (POST returns 404 by design). verify is POST …/verify-authentication / POST …/verify-registration. rpID is the parent domain (e.g. briven.tech for apps on *.briven.tech). WebAuthn needs https or localhost; add every app origin under Auth → Allowed Domains.
two-factor + backup codes
when the project has two-factor enabled, password sign-in may return twoFactorRequired. complete the challenge with a TOTP app code or a single-use backup recovery code (for lost phones).
// after password sign-in returns twoFactorRequired:
const totp = await auth.twoFactor.verify('123456');
// or lost phone:
const backup = await auth.twoFactor.verifyBackupCode('XXXX-XXXX');
// enroll (show codes once after verify):
await auth.twoFactor.enable(password);
await auth.twoFactor.verify(appCode);
const { codes } = await auth.twoFactor.generateBackupCodes(password);
// save codes offline — each works oncehosted pages: after password, users land on /auth/<projectId>/two-factor with a toggle for backup codes. react: TwoFactorSetup + TwoFactorChallenge from @briven/auth/react.
testing tokens (e2e)
for automated tests, mint a testing token in the dashboard (or POST /v1/projects/:id/auth/test-tokens as an admin). it bypasses bot checks, rate limits, and MFA for a short window and is returned once as briven_test_….
// CI / e2e (never commit the raw token)
const session = await auth.signIn.testToken(process.env.BRIVEN_AUTH_TEST_TOKEN!);
// → { ok: true, expiresAt } and a real session cookierevoke used tokens in the dashboard. production apps for real humans should not rely on testing tokens.
email sender domain
every email briven auth sends for you — magic links, OTP codes, email verification, password resets — has a From: address. by default that is noreply@briven.tech, which works out of the box with zero setup. when you want those emails branded as your own product (say noreply@yourapp.com), you set a sender domain.
how to set it
- open your project at briven.tech.
- go to Auth → branding.
- fill in sender name (the display name, e.g. your product name) and sender domain — your root domain, e.g.
yourapp.com. do not enter a subdomain likeauth.yourapp.comunless you really send from that subdomain. - save. that's the whole dashboard side.
verify the domain (2–3 DNS records)
email worldwide runs on one rule: a domain must publicly declare who is allowed to send mail in its name. that declaration lives in your domain's DNS as SPF and DKIM records. without them, providers like Gmail and Outlook treat mail from your domain as forged — so no platform (briven included) can skip this step. briven hands you the exact records to add at your domain provider; a guided setup wizard is landing in the branding panel.
what happens before you verify
nothing breaks — this is the important part. until your domain is verified, briven automatically keeps sending from the noreply@briven.techfallback (with your sender name), so your users' sign-in emails always arrive. the moment the domain verifies, sends switch to noreply@yourdomain on their own. you never need to time the switch or fear a broken login flow while DNS propagates.
noreply@briven.techeven though you filled in a sender domain? that means the domain isn't verified yet — finish adding the DNS records and allow up to an hour for DNS to propagate. emails not arriving at all? check spam first, then Auth → usage for delivery status.verify users with tokens (JWT + JWKS)
getSession()asks the briven auth service every time. when your setup has its own backend — or several apps sharing one sign-in — you often just need a fast local answer to "is this user signed in?" without a network round-trip per request. for that, briven auth issues verifiable tokens: short-lived signed JWTs your own code can check against the project's public keys.
the two endpoints
GET /v1/auth-tenant/token— requires a signed-in session (the browser cookie). returns{ token: "<JWT>" }, a short-lived signed JWT for the current user.GET /v1/auth-tenant/jwks— public, no auth. returns the project's JSON Web Key Set, so your code can verify JWTs locally without calling briven.
both live under https://api.briven.tech and carry your x-briven-project-id header like every auth request.
verify a token
any standard JWT library that understands remote JWKS works — shown here with jose:
// in the signed-in browser: mint a token, hand it to your backend
const res = await fetch('https://api.briven.tech/v1/auth-tenant/token', {
headers: {
'x-briven-project-id': 'p_01HZ...',
authorization: 'Bearer pk_briven_auth_...', // the browser-safe key
},
credentials: 'include', // sends the session cookie
});
const { token } = await res.json();
// on YOUR server: verify locally — no call to briven per request
import { createRemoteJWKSet, jwtVerify } from 'jose';
const jwks = createRemoteJWKSet(
new URL('https://api.briven.tech/v1/auth-tenant/jwks'),
{ headers: { 'x-briven-project-id': 'p_01HZ...' } },
);
const { payload } = await jwtVerify(token, jwks);
// payload.sub is the signed-in user's idjose do this for you). tokens are short-lived by design — refetch /token on expiry rather than storing one long-term.security
- the
pk_briven_auth_key is browser-safe: it identifies the tenant and unlocks the end-user sign-in surface only. your serverbrk_*keys are not — keep them server-side.readscope keys cannot POST mutating auth-tenant routes. - never log or display an end-user's email or IP. the
Userobject carriesemailfor the account holder — don't echo it into a list view, a log line, or an analytics event. - sessions live in an http cookie the service sets on sign-in; the SDK sends requests with
credentials: 'include'so the browser stores and returns it. you don't handle tokens yourself. - password policy (length / character rules / max age / force reset) and rate limits are per-project. production should show
redis: okon/readyso limits share across servers. - new device emails use a hashed browser fingerprint (no raw IPs). SSO redirects after SAML/OIDC only allow listed app origins.
x-briven-project-id: <projectId> and authorization: Bearer <publicKey> to https://api.briven.tech/v1/auth-tenant/*. the service resolves your tenant from the header and routes the request. the SDK adds these headers for you.for AI agents
when an agent wires auth, it should follow the briven-auth skill (not invent OAuth). trusted order: link project → briven auth scaffold → install @briven/auth → human pastes pk_briven_auth_… → hosted page or BrivenSignIn → human runs the go-live checklist.
- MCP helpers are read-only today:
auth_config_get,sender_domain_status,auth_docs_ask. they explain; they do not mint keys or write provider secrets. - never put secrets in git or chat. never use
brk_inNEXT_PUBLIC_*. - sessions are cookies — prefer
getSession()/useSession(), not home-rolled localStorage tokens. - for e2e, use testing tokens (
auth.signIn.testToken), not real MFA bypass hacks. for lost-phone recovery, implement backup codes (see two-factor). - framework hooks: react
useUserMetadata/useUserEmails; vue + svelte have matching helpers. pilot:examples/auth-pilot/.