page.tsx637 lines · main
| 1 | import { PmTabs } from '../../components/pm-tabs'; |
| 2 | import { DocsShell } from '../../components/shell'; |
| 3 | import { pmInstall } from '../../lib/pm'; |
| 4 | |
| 5 | export const metadata = { title: 'auth' }; |
| 6 | |
| 7 | const INSTALL = pmInstall('@briven/auth'); |
| 8 | |
| 9 | export default function AuthPage() { |
| 10 | return ( |
| 11 | <DocsShell> |
| 12 | <h1 className="font-mono text-2xl tracking-tight">auth</h1> |
| 13 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 14 | <code>@briven/auth</code> is a drop-in sign-in client for your briven project. one |
| 15 | package gives you email + password, magic links, email OTP, passkeys (WebAuthn), and |
| 16 | OAuth social login — plus React hooks and a prebuilt <code><BrivenSignIn /></code>{' '} |
| 17 | panel. the SDK |
| 18 | talks to the hosted briven auth service; you never run an auth server yourself. |
| 19 | </p> |
| 20 | |
| 21 | <nav className="mt-6 flex flex-wrap gap-2 font-mono text-xs"> |
| 22 | {[ |
| 23 | ['setup (10 min)', '#setup'], |
| 24 | ['install', '#install'], |
| 25 | ['get your keys', '#keys'], |
| 26 | ['create the client', '#client'], |
| 27 | ['react bindings', '#react'], |
| 28 | ['sign-in flows', '#flows'], |
| 29 | ['two-factor + backup codes', '#two-factor'], |
| 30 | ['testing tokens', '#testing-tokens'], |
| 31 | ['email sender domain', '#sender-domain'], |
| 32 | ['verify users with tokens', '#verify-tokens'], |
| 33 | ['security', '#security'], |
| 34 | ['agents', '#agents'], |
| 35 | ].map(([label, href]) => ( |
| 36 | <a |
| 37 | key={href} |
| 38 | href={href} |
| 39 | className="rounded-md border border-[var(--color-border)] px-3 py-1 text-[var(--color-text-muted)] hover:text-[var(--color-text)]" |
| 40 | > |
| 41 | {label} |
| 42 | </a> |
| 43 | ))} |
| 44 | </nav> |
| 45 | |
| 46 | {/* setup --------------------------------------------------------- */} |
| 47 | <section id="setup" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 48 | <h2 className="font-mono text-lg tracking-tight">setup in 10 minutes</h2> |
| 49 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 50 | the shortest path from zero to a working sign-in. for a browser-only |
| 51 | sign-off after this, use the plain-language checklist in the briven |
| 52 | repo: <code>AUTH-GO-LIVE-CHECKLIST.md</code>. |
| 53 | </p> |
| 54 | <ol className="mt-3 list-inside list-decimal font-mono text-sm text-[var(--color-text-muted)] space-y-2"> |
| 55 | <li> |
| 56 | open your project at{' '} |
| 57 | <a className="underline" href="https://briven.tech"> |
| 58 | briven.tech |
| 59 | </a>{' '} |
| 60 | → <em>Auth</em> → enable auth if it is not already on. |
| 61 | </li> |
| 62 | <li> |
| 63 | <em>Auth → API keys</em> → create a key → copy{' '} |
| 64 | <code>pk_briven_auth_…</code> (browser-safe). never put a{' '} |
| 65 | <code>brk_…</code> key in the browser. |
| 66 | </li> |
| 67 | <li> |
| 68 | in your app folder, wire the project first: |
| 69 | <Snippet>{`# NEW project: briven setup my-app |
| 70 | # EXISTING: briven connect p_… |
| 71 | briven auth scaffold |
| 72 | pnpm add @briven/auth`}</Snippet> |
| 73 | that writes <code>middleware.ts</code>, <code>lib/auth.ts</code>, and a |
| 74 | seeded <code>.env.local</code> if missing. never use{' '} |
| 75 | <code>setup --project</code> to attach — that flag is gone; use{' '} |
| 76 | <code>briven connect</code>. |
| 77 | </li> |
| 78 | <li> |
| 79 | paste the public key into <code>NEXT_PUBLIC_BRIVEN_AUTH_KEY</code> (and |
| 80 | the same value into <code>BRIVEN_AUTH_PUBLIC_KEY</code> for middleware). |
| 81 | </li> |
| 82 | <li> |
| 83 | send users to the hosted page or drop in the panel: |
| 84 | <Snippet>{`// hosted (fastest pilot) |
| 85 | window.location.assign(auth.hostedPageURL('sign-in', '/dashboard')); |
| 86 | |
| 87 | // or embedded React panel |
| 88 | import { BrivenSignIn } from '@briven/auth/react'; |
| 89 | // <BrivenSignIn redirectTo="/dashboard" />`}</Snippet> |
| 90 | </li> |
| 91 | <li> |
| 92 | prove it in a real browser: sign up, sign out, sign in, refresh still |
| 93 | signed in. optional: wrong-password rejection + rate limit after many |
| 94 | failures (production needs redis — <code>/ready</code> must show{' '} |
| 95 | <code>redis: ok</code>). |
| 96 | </li> |
| 97 | </ol> |
| 98 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 99 | starter copy: <code>examples/auth-pilot/</code> in the briven monorepo. |
| 100 | </p> |
| 101 | </section> |
| 102 | |
| 103 | {/* install ------------------------------------------------------- */} |
| 104 | <section id="install" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 105 | <h2 className="font-mono text-lg tracking-tight">install</h2> |
| 106 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 107 | published to public npm — see{' '} |
| 108 | <a |
| 109 | className="underline" |
| 110 | href="https://www.npmjs.com/package/@briven/auth" |
| 111 | > |
| 112 | npm |
| 113 | </a>{' '} |
| 114 | for the current version. |
| 115 | </p> |
| 116 | <div className="mt-3"> |
| 117 | <PmTabs commands={INSTALL} /> |
| 118 | </div> |
| 119 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 120 | subpaths: <code>@briven/auth</code> (the vanilla client, zero React deps),{' '} |
| 121 | <code>@briven/auth/react</code> (hooks + the <code><BrivenSignIn /></code>{' '} |
| 122 | component), and <code>@briven/auth/server</code> (Next.js App Router helpers). |
| 123 | </p> |
| 124 | </section> |
| 125 | |
| 126 | {/* keys ---------------------------------------------------------- */} |
| 127 | <section id="keys" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 128 | <h2 className="font-mono text-lg tracking-tight">get your keys</h2> |
| 129 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 130 | you need two things from the dashboard: your <strong>project id</strong> (looks like{' '} |
| 131 | <code>p_01HZ…</code>) and a <strong>public auth key</strong> (looks like{' '} |
| 132 | <code>pk_briven_auth_…</code>). |
| 133 | </p> |
| 134 | <ol className="mt-3 list-inside list-decimal font-mono text-sm text-[var(--color-text-muted)] space-y-1"> |
| 135 | <li>open your project at <a className="underline" href="https://briven.tech">briven.tech</a>.</li> |
| 136 | <li>go to <em>Auth → API keys</em>.</li> |
| 137 | <li>click <em>create key</em>, give it a name, set the scope to <code>read-write</code>.</li> |
| 138 | <li>copy the <code>pk_briven_auth_…</code> value — it is shown once.</li> |
| 139 | </ol> |
| 140 | |
| 141 | <section className="mt-5 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 142 | <strong className="text-[var(--color-text)]">which key is which:</strong> the{' '} |
| 143 | <code>pk_briven_auth_</code> key is <em>browser-safe</em>. it identifies your tenant |
| 144 | and unlocks the end-user sign-in surface only — it can't read or write your data. |
| 145 | ship it to the browser freely. your server <code>brk_*</code> keys are{' '} |
| 146 | <strong>not</strong> browser-safe; never put one in client code. |
| 147 | </section> |
| 148 | </section> |
| 149 | |
| 150 | {/* client -------------------------------------------------------- */} |
| 151 | <section id="client" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 152 | <h2 className="font-mono text-lg tracking-tight">create the client</h2> |
| 153 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 154 | <code>createBrivenAuth</code> returns a stateless client. all auth state lives in the |
| 155 | browser cookie set by the service on a successful sign-in, so re-creating the client |
| 156 | across renders is safe. |
| 157 | </p> |
| 158 | <Snippet>{`// lib/auth.ts |
| 159 | import { createBrivenAuth } from '@briven/auth'; |
| 160 | |
| 161 | export const auth = createBrivenAuth({ |
| 162 | projectId: 'p_01HZ...', |
| 163 | publicKey: process.env.NEXT_PUBLIC_BRIVEN_AUTH_KEY!, // 'pk_briven_auth_...' |
| 164 | });`}</Snippet> |
| 165 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 166 | <span className="text-[var(--color-text)]">options: </span> |
| 167 | <code>projectId</code> and <code>publicKey</code> are required.{' '} |
| 168 | <code>apiOrigin</code> defaults to <code>https://api.briven.tech</code> (override it |
| 169 | only for self-hosted or local dev). <code>authUrl</code> defaults to{' '} |
| 170 | <code>https://<projectId>.auth.briven.tech</code> and backs the OAuth redirect. |
| 171 | </p> |
| 172 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 173 | the client exposes <code>signIn</code>, <code>signUp.email</code>,{' '} |
| 174 | <code>signOut()</code>, <code>getSession()</code>, and <code>getUser()</code>. |
| 175 | </p> |
| 176 | </section> |
| 177 | |
| 178 | {/* react --------------------------------------------------------- */} |
| 179 | <section id="react" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 180 | <h2 className="font-mono text-lg tracking-tight">react bindings</h2> |
| 181 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 182 | import from <code>@briven/auth/react</code>. wrap your app in the provider, then read |
| 183 | state with the hooks or drop in the prebuilt panel. works in any React 19 environment |
| 184 | — no hard Next.js dependency. |
| 185 | </p> |
| 186 | |
| 187 | <h3 className="mt-5 font-mono text-sm">provider</h3> |
| 188 | <Snippet>{`// app/providers.tsx |
| 189 | 'use client'; |
| 190 | import { BrivenAuthProvider } from '@briven/auth/react'; |
| 191 | import { auth } from '../lib/auth'; |
| 192 | |
| 193 | export function Providers({ children }: { children: React.ReactNode }) { |
| 194 | return <BrivenAuthProvider value={auth}>{children}</BrivenAuthProvider>; |
| 195 | }`}</Snippet> |
| 196 | |
| 197 | <h3 className="mt-5 font-mono text-sm">hooks</h3> |
| 198 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 199 | <code>useSession()</code> returns <code>{`{ session, isLoading, refresh }`}</code>;{' '} |
| 200 | <code>useUser()</code> returns <code>{`{ user, isLoading, refresh }`}</code>. both |
| 201 | fetch once on mount — call <code>refresh()</code> after a sign-in or sign-out to |
| 202 | re-read. |
| 203 | </p> |
| 204 | <Snippet>{`'use client'; |
| 205 | import { useSession, useUser } from '@briven/auth/react'; |
| 206 | |
| 207 | export function AccountBadge() { |
| 208 | const { session, isLoading } = useSession(); |
| 209 | const { user } = useUser(); |
| 210 | |
| 211 | if (isLoading) return <span>…</span>; |
| 212 | if (!session?.authenticated) return <a href="/sign-in">sign in</a>; |
| 213 | return <span>signed in</span>; // don't render user.email into the UI — see security |
| 214 | }`}</Snippet> |
| 215 | |
| 216 | <h3 className="mt-5 font-mono text-sm">prebuilt panel</h3> |
| 217 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 218 | <code><BrivenSignIn /></code> renders email + password, magic link, and your |
| 219 | chosen OAuth buttons in one panel. it carries no icon library and no css framework — |
| 220 | your app's styles apply via <code>className</code> and the element classes. |
| 221 | </p> |
| 222 | <Snippet>{`'use client'; |
| 223 | import { BrivenSignIn } from '@briven/auth/react'; |
| 224 | |
| 225 | export function SignIn() { |
| 226 | return ( |
| 227 | <BrivenSignIn |
| 228 | providers={['google', 'github', 'konnos']} |
| 229 | showEmailPassword |
| 230 | showMagicLink |
| 231 | redirectTo="/dashboard" |
| 232 | onSuccess={({ userId }) => console.log('signed in', userId)} |
| 233 | className="my-signin" |
| 234 | /> |
| 235 | ); |
| 236 | }`}</Snippet> |
| 237 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 238 | <span className="text-[var(--color-text)]">props: </span> |
| 239 | <code>providers</code> (OAuth buttons to show — empty array hides the section),{' '} |
| 240 | <code>showEmailPassword</code> (default true), <code>showMagicLink</code> (default |
| 241 | true), <code>redirectTo</code>, <code>onSuccess</code>, and <code>className</code>. |
| 242 | </p> |
| 243 | </section> |
| 244 | |
| 245 | {/* flows --------------------------------------------------------- */} |
| 246 | <section id="flows" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 247 | <h2 className="font-mono text-lg tracking-tight">sign-in flows</h2> |
| 248 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 249 | if you want your own UI, call the client methods directly. every async method returns |
| 250 | a tagged result — <code>{`{ ok: true, ... }`}</code> on success or{' '} |
| 251 | <code>{`{ ok: false, code, message }`}</code> on failure — so you never throw on a bad |
| 252 | password. |
| 253 | </p> |
| 254 | |
| 255 | <h3 className="mt-5 font-mono text-sm">email + password</h3> |
| 256 | <Snippet>{`// sign up, then sign in — both return { ok, userId, sessionExpiresAt } |
| 257 | await auth.signUp.email({ email, password, name: 'Jane' }); |
| 258 | |
| 259 | const result = await auth.signIn.email({ email, password }); |
| 260 | if (result.ok) { |
| 261 | // result.userId, result.sessionExpiresAt |
| 262 | } else { |
| 263 | // result.code: 'invalid_credentials' | 'unverified_email' | 'rate_limited' | ... |
| 264 | console.error(result.message); |
| 265 | }`}</Snippet> |
| 266 | |
| 267 | <h3 className="mt-5 font-mono text-sm">magic link</h3> |
| 268 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 269 | a passwordless flow of its own — <strong>not</strong> an OAuth provider. it emails the |
| 270 | user a one-click sign-in link. |
| 271 | </p> |
| 272 | <Snippet>{`const res = await auth.signIn.magicLink({ |
| 273 | email, |
| 274 | redirectTo: '/dashboard', // where the link lands after verify |
| 275 | }); |
| 276 | // res.ok === true means the email was sent`}</Snippet> |
| 277 | |
| 278 | <h3 className="mt-5 font-mono text-sm">email OTP</h3> |
| 279 | <Snippet>{`// 1. send the code |
| 280 | await auth.signIn.otpRequest({ email }); |
| 281 | |
| 282 | // 2. verify the 6-digit code the user typed |
| 283 | const result = await auth.signIn.otpVerify({ email, otp: '123456' }); |
| 284 | if (result.ok) { /* result.userId */ }`}</Snippet> |
| 285 | |
| 286 | <h3 className="mt-5 font-mono text-sm">OAuth social</h3> |
| 287 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 288 | <code>social()</code> is synchronous — it builds the start URL and hands it back. you |
| 289 | redirect the browser to it. provider is one of{' '} |
| 290 | <code>'google'</code>, <code>'github'</code>,{' '} |
| 291 | <code>'discord'</code>, <code>'microsoft'</code>, or{' '} |
| 292 | <code>'konnos'</code>. |
| 293 | </p> |
| 294 | <Snippet>{`const { redirectUrl } = auth.signIn.social({ |
| 295 | provider: 'google', |
| 296 | redirectTo: '/dashboard', |
| 297 | }); |
| 298 | window.location.assign(redirectUrl);`}</Snippet> |
| 299 | |
| 300 | <h3 className="mt-5 font-mono text-sm">passkeys (WebAuthn)</h3> |
| 301 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 302 | passwordless sign-in backed by the device's platform authenticator (Touch ID, Face |
| 303 | ID, Windows Hello, a hardware key). two ceremonies, both wrapped for you so you never |
| 304 | touch the raw <code>navigator.credentials</code> calls: |
| 305 | </p> |
| 306 | <Snippet>{`// REGISTER — adds a passkey to the *currently signed-in* user. |
| 307 | // call this from an account-settings screen, after a normal sign-in. |
| 308 | const reg = await auth.passkey.register(); |
| 309 | if (reg.ok) { /* passkey saved on this device */ } |
| 310 | |
| 311 | // SIGN IN — no password, no email field needed. |
| 312 | const result = await auth.passkey.signIn(); |
| 313 | if (result.ok) { |
| 314 | // result.userId, result.sessionExpiresAt |
| 315 | } else { |
| 316 | // result.code: 'not_enabled' | 'cancelled' | 'unsupported' | ... |
| 317 | console.error(result.message); |
| 318 | }`}</Snippet> |
| 319 | <p className="mt-2 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 320 | passkeys are <strong>per-tenant</strong>: enable under <em>Auth → providers</em>.{' '} |
| 321 | <code>auth.passkey.register()</code> needs an active session first (magic link / OTP / |
| 322 | password), then Face ID / Touch ID runs in the browser. platform routes:{' '} |
| 323 | <code>GET /v1/auth-tenant/passkey/generate-authenticate-options</code> (sign-in) and{' '} |
| 324 | <code>GET …/generate-register-options</code> (enrol) — <strong>not</strong> POST on those |
| 325 | paths (POST returns 404 by design). verify is{' '} |
| 326 | <code>POST …/verify-authentication</code> / <code>POST …/verify-registration</code>. rpID |
| 327 | is the parent domain (e.g. <code>briven.tech</code> for apps on{' '} |
| 328 | <code>*.briven.tech</code>). WebAuthn needs https or localhost; add every app origin under{' '} |
| 329 | <em>Auth → Allowed Domains</em>. |
| 330 | </p> |
| 331 | |
| 332 | <section className="mt-5 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 333 | <strong className="text-[var(--color-text)]">before these work in production:</strong>{' '} |
| 334 | social login (google / github / discord / microsoft / konnos) needs the project owner |
| 335 | to set that provider's client id <em>and</em> secret under <em>Auth → providers</em> |
| 336 | in the dashboard — until both are set, the provider stays disabled. magic-link and |
| 337 | email-OTP emails send out of the box from the briven fallback sender — to brand them |
| 338 | as your own domain, see{' '} |
| 339 | <a className="underline" href="#sender-domain">email sender domain</a>. |
| 340 | </section> |
| 341 | </section> |
| 342 | |
| 343 | {/* two-factor ------------------------------------------------------ */} |
| 344 | <section |
| 345 | id="two-factor" |
| 346 | className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8" |
| 347 | > |
| 348 | <h2 className="font-mono text-lg tracking-tight">two-factor + backup codes</h2> |
| 349 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 350 | when the project has two-factor enabled, password sign-in may return{' '} |
| 351 | <code>twoFactorRequired</code>. complete the challenge with a TOTP app code or a |
| 352 | single-use <strong>backup recovery code</strong> (for lost phones). |
| 353 | </p> |
| 354 | <Snippet>{`// after password sign-in returns twoFactorRequired: |
| 355 | const totp = await auth.twoFactor.verify('123456'); |
| 356 | // or lost phone: |
| 357 | const backup = await auth.twoFactor.verifyBackupCode('XXXX-XXXX'); |
| 358 | |
| 359 | // enroll (show codes once after verify): |
| 360 | await auth.twoFactor.enable(password); |
| 361 | await auth.twoFactor.verify(appCode); |
| 362 | const { codes } = await auth.twoFactor.generateBackupCodes(password); |
| 363 | // save codes offline — each works once`}</Snippet> |
| 364 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 365 | hosted pages: after password, users land on{' '} |
| 366 | <code>/auth/<projectId>/two-factor</code> with a toggle for backup codes. |
| 367 | react: <code>TwoFactorSetup</code> + <code>TwoFactorChallenge</code> from{' '} |
| 368 | <code>@briven/auth/react</code>. |
| 369 | </p> |
| 370 | </section> |
| 371 | |
| 372 | {/* testing tokens ------------------------------------------------- */} |
| 373 | <section |
| 374 | id="testing-tokens" |
| 375 | className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8" |
| 376 | > |
| 377 | <h2 className="font-mono text-lg tracking-tight">testing tokens (e2e)</h2> |
| 378 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 379 | for automated tests, mint a <strong>testing token</strong> in the dashboard (or{' '} |
| 380 | <code>POST /v1/projects/:id/auth/test-tokens</code> as an admin). it bypasses bot |
| 381 | checks, rate limits, and MFA for a short window and is returned <em>once</em> as{' '} |
| 382 | <code>briven_test_…</code>. |
| 383 | </p> |
| 384 | <Snippet>{`// CI / e2e (never commit the raw token) |
| 385 | const session = await auth.signIn.testToken(process.env.BRIVEN_AUTH_TEST_TOKEN!); |
| 386 | // → { ok: true, expiresAt } and a real session cookie`}</Snippet> |
| 387 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 388 | revoke used tokens in the dashboard. production apps for real humans should not rely |
| 389 | on testing tokens. |
| 390 | </p> |
| 391 | </section> |
| 392 | |
| 393 | {/* sender domain -------------------------------------------------- */} |
| 394 | <section |
| 395 | id="sender-domain" |
| 396 | className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8" |
| 397 | > |
| 398 | <h2 className="font-mono text-lg tracking-tight">email sender domain</h2> |
| 399 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 400 | every email briven auth sends for you — magic links, OTP codes, email verification, |
| 401 | password resets — has a From: address. by default that is{' '} |
| 402 | <code>noreply@briven.tech</code>, which works out of the box with zero setup. when you |
| 403 | want those emails branded as your own product (say{' '} |
| 404 | <code>noreply@yourapp.com</code>), you set a <strong>sender domain</strong>. |
| 405 | </p> |
| 406 | |
| 407 | <h3 className="mt-5 font-mono text-sm">how to set it</h3> |
| 408 | <ol className="mt-2 list-inside list-decimal font-mono text-sm text-[var(--color-text-muted)] space-y-1"> |
| 409 | <li>open your project at <a className="underline" href="https://briven.tech">briven.tech</a>.</li> |
| 410 | <li>go to <em>Auth → branding</em>.</li> |
| 411 | <li> |
| 412 | fill in <em>sender name</em> (the display name, e.g. your product name) and{' '} |
| 413 | <em>sender domain</em> — your <strong>root domain</strong>, e.g.{' '} |
| 414 | <code>yourapp.com</code>. do <strong>not</strong> enter a subdomain like{' '} |
| 415 | <code>auth.yourapp.com</code> unless you really send from that subdomain. |
| 416 | </li> |
| 417 | <li>save. that's the whole dashboard side.</li> |
| 418 | </ol> |
| 419 | |
| 420 | <h3 className="mt-5 font-mono text-sm">verify the domain (2–3 DNS records)</h3> |
| 421 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 422 | email worldwide runs on one rule: a domain must publicly declare who is allowed to |
| 423 | send mail in its name. that declaration lives in your domain's DNS as SPF and |
| 424 | DKIM records. without them, providers like Gmail and Outlook treat mail from your |
| 425 | domain as forged — so no platform (briven included) can skip this step. briven hands |
| 426 | you the exact records to add at your domain provider; a guided setup wizard is landing |
| 427 | in the branding panel. |
| 428 | </p> |
| 429 | |
| 430 | <h3 className="mt-5 font-mono text-sm">what happens before you verify</h3> |
| 431 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 432 | nothing breaks — this is the important part. until your domain is verified, briven |
| 433 | automatically keeps sending from the <code>noreply@briven.tech</code> fallback (with |
| 434 | your sender name), so your users' sign-in emails always arrive. the moment the |
| 435 | domain verifies, sends switch to <code>noreply@yourdomain</code> on their own. you |
| 436 | never need to time the switch or fear a broken login flow while DNS propagates. |
| 437 | </p> |
| 438 | |
| 439 | <section className="mt-5 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 440 | <strong className="text-[var(--color-text)]">troubleshooting:</strong> sign-in emails |
| 441 | arriving from <code>noreply@briven.tech</code> even though you filled in a sender |
| 442 | domain? that means the domain isn't verified yet — finish adding the DNS records |
| 443 | and allow up to an hour for DNS to propagate. emails not arriving at all? check spam |
| 444 | first, then <em>Auth → usage</em> for delivery status. |
| 445 | </section> |
| 446 | </section> |
| 447 | |
| 448 | {/* verify tokens -------------------------------------------------- */} |
| 449 | <section |
| 450 | id="verify-tokens" |
| 451 | className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8" |
| 452 | > |
| 453 | <h2 className="font-mono text-lg tracking-tight">verify users with tokens (JWT + JWKS)</h2> |
| 454 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 455 | <code>getSession()</code> asks the briven auth service every time. when your setup has |
| 456 | its own backend — or several apps sharing one sign-in — you often just need a fast |
| 457 | local answer to "is this user signed in?" without a network round-trip per |
| 458 | request. for that, briven auth issues <strong>verifiable tokens</strong>: short-lived |
| 459 | signed JWTs your own code can check against the project's public keys. |
| 460 | </p> |
| 461 | |
| 462 | <h3 className="mt-5 font-mono text-sm">the two endpoints</h3> |
| 463 | <ul className="mt-2 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)] space-y-2"> |
| 464 | <li> |
| 465 | <code>GET /v1/auth-tenant/token</code> — requires a signed-in session (the browser |
| 466 | cookie). returns <code>{`{ token: "<JWT>" }`}</code>, a short-lived signed JWT for |
| 467 | the current user. |
| 468 | </li> |
| 469 | <li> |
| 470 | <code>GET /v1/auth-tenant/jwks</code> — public, no auth. returns the project's |
| 471 | JSON Web Key Set, so your code can verify JWTs locally without calling briven. |
| 472 | </li> |
| 473 | </ul> |
| 474 | <p className="mt-3 font-mono text-xs text-[var(--color-text-subtle)]"> |
| 475 | both live under <code>https://api.briven.tech</code> and carry your{' '} |
| 476 | <code>x-briven-project-id</code> header like every auth request. |
| 477 | </p> |
| 478 | |
| 479 | <h3 className="mt-5 font-mono text-sm">verify a token</h3> |
| 480 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]"> |
| 481 | any standard JWT library that understands remote JWKS works — shown here with{' '} |
| 482 | <code>jose</code>: |
| 483 | </p> |
| 484 | <Snippet>{`// in the signed-in browser: mint a token, hand it to your backend |
| 485 | const res = await fetch('https://api.briven.tech/v1/auth-tenant/token', { |
| 486 | headers: { |
| 487 | 'x-briven-project-id': 'p_01HZ...', |
| 488 | authorization: 'Bearer pk_briven_auth_...', // the browser-safe key |
| 489 | }, |
| 490 | credentials: 'include', // sends the session cookie |
| 491 | }); |
| 492 | const { token } = await res.json(); |
| 493 | |
| 494 | // on YOUR server: verify locally — no call to briven per request |
| 495 | import { createRemoteJWKSet, jwtVerify } from 'jose'; |
| 496 | |
| 497 | const jwks = createRemoteJWKSet( |
| 498 | new URL('https://api.briven.tech/v1/auth-tenant/jwks'), |
| 499 | { headers: { 'x-briven-project-id': 'p_01HZ...' } }, |
| 500 | ); |
| 501 | |
| 502 | const { payload } = await jwtVerify(token, jwks); |
| 503 | // payload.sub is the signed-in user's id`}</Snippet> |
| 504 | |
| 505 | <section className="mt-5 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 506 | <strong className="text-[var(--color-text)]">keys and rotation:</strong> signing keys |
| 507 | are <em>per-project</em> — a token from one project never verifies in another. key |
| 508 | rotation is handled through the JWKS endpoint: when briven rotates a key, the new one |
| 509 | appears in the key set, so refetch the JWKS whenever verification hits an unknown key |
| 510 | id (libraries like <code>jose</code> do this for you). tokens are short-lived by |
| 511 | design — refetch <code>/token</code> on expiry rather than storing one long-term. |
| 512 | </section> |
| 513 | </section> |
| 514 | |
| 515 | {/* security ------------------------------------------------------ */} |
| 516 | <section id="security" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 517 | <h2 className="font-mono text-lg tracking-tight">security</h2> |
| 518 | <ul className="mt-3 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)] space-y-2"> |
| 519 | <li> |
| 520 | the <code>pk_briven_auth_</code> key is browser-safe: it identifies the tenant and |
| 521 | unlocks the end-user sign-in surface only. your server <code>brk_*</code> keys are |
| 522 | not — keep them server-side. <code>read</code> scope keys cannot POST mutating |
| 523 | auth-tenant routes. |
| 524 | </li> |
| 525 | <li> |
| 526 | never log or display an end-user's email or IP. the <code>User</code> object |
| 527 | carries <code>email</code> for the account holder — don't echo it into a list |
| 528 | view, a log line, or an analytics event. |
| 529 | </li> |
| 530 | <li> |
| 531 | sessions live in an http cookie the service sets on sign-in; the SDK sends requests |
| 532 | with <code>credentials: 'include'</code> so the browser stores and returns |
| 533 | it. you don't handle tokens yourself. |
| 534 | </li> |
| 535 | <li> |
| 536 | password policy (length / character rules / max age / force reset) and rate limits |
| 537 | are per-project. production should show <code>redis: ok</code> on{' '} |
| 538 | <code>/ready</code> so limits share across servers. |
| 539 | </li> |
| 540 | <li> |
| 541 | new device emails use a hashed browser fingerprint (no raw IPs). SSO redirects after |
| 542 | SAML/OIDC only allow listed app origins. |
| 543 | </li> |
| 544 | </ul> |
| 545 | |
| 546 | <section className="mt-5 rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 font-mono text-xs text-[var(--color-text-muted)]"> |
| 547 | <strong className="text-[var(--color-text)]">wire protocol (you don't write this):</strong>{' '} |
| 548 | every request carries <code>x-briven-project-id: <projectId></code> and{' '} |
| 549 | <code>authorization: Bearer <publicKey></code> to{' '} |
| 550 | <code>https://api.briven.tech/v1/auth-tenant/*</code>. the service resolves your |
| 551 | tenant from the header and routes the request. the SDK adds these headers for you. |
| 552 | </section> |
| 553 | </section> |
| 554 | |
| 555 | {/* agents -------------------------------------------------------- */} |
| 556 | <section id="agents" className="mt-10 scroll-mt-20 border-t border-[var(--color-border-subtle)] pt-8"> |
| 557 | <h2 className="font-mono text-lg tracking-tight">for AI agents</h2> |
| 558 | <p className="mt-2 font-mono text-sm text-[var(--color-text-muted)]"> |
| 559 | when an agent wires auth, it should follow the <code>briven-auth</code>{' '} |
| 560 | skill (not invent OAuth). trusted order: link project →{' '} |
| 561 | <code>briven auth scaffold</code> → install <code>@briven/auth</code> → |
| 562 | human pastes <code>pk_briven_auth_…</code> → hosted page or{' '} |
| 563 | <code>BrivenSignIn</code> → human runs the go-live checklist. |
| 564 | </p> |
| 565 | <ul className="mt-3 list-inside list-disc font-mono text-sm text-[var(--color-text-muted)] space-y-2"> |
| 566 | <li> |
| 567 | MCP helpers are <strong>read-only</strong> today:{' '} |
| 568 | <code>auth_config_get</code>, <code>sender_domain_status</code>,{' '} |
| 569 | <code>auth_docs_ask</code>. they explain; they do not mint keys or |
| 570 | write provider secrets. |
| 571 | </li> |
| 572 | <li> |
| 573 | never put secrets in git or chat. never use <code>brk_</code> in{' '} |
| 574 | <code>NEXT_PUBLIC_*</code>. |
| 575 | </li> |
| 576 | <li> |
| 577 | sessions are cookies — prefer <code>getSession()</code> /{' '} |
| 578 | <code>useSession()</code>, not home-rolled localStorage tokens. |
| 579 | </li> |
| 580 | <li> |
| 581 | for e2e, use testing tokens (<code>auth.signIn.testToken</code>), not real MFA |
| 582 | bypass hacks. for lost-phone recovery, implement backup codes (see{' '} |
| 583 | <a className="underline" href="#two-factor"> |
| 584 | two-factor |
| 585 | </a> |
| 586 | ). |
| 587 | </li> |
| 588 | <li> |
| 589 | framework hooks: react <code>useUserMetadata</code> / <code>useUserEmails</code>; |
| 590 | vue + svelte have matching helpers. pilot: <code>examples/auth-pilot/</code>. |
| 591 | </li> |
| 592 | </ul> |
| 593 | </section> |
| 594 | |
| 595 | <h2 className="mt-12 font-mono text-lg">what to read next</h2> |
| 596 | <ul className="mt-3 flex flex-col gap-2 font-mono text-sm"> |
| 597 | <NextLink |
| 598 | href="/sdks" |
| 599 | title="client sdks" |
| 600 | body="the data clients — @briven/react, svelte, vue — for reactive queries and mutations" |
| 601 | /> |
| 602 | <NextLink |
| 603 | href="/api-keys" |
| 604 | title="api keys" |
| 605 | body="key types, scopes, and rotation across the dashboard" |
| 606 | /> |
| 607 | <NextLink |
| 608 | href="/quickstart" |
| 609 | title="quickstart" |
| 610 | body="from nothing to a live project in five minutes" |
| 611 | /> |
| 612 | </ul> |
| 613 | </DocsShell> |
| 614 | ); |
| 615 | } |
| 616 | |
| 617 | function Snippet({ children }: { children: string }) { |
| 618 | return ( |
| 619 | <pre className="mt-2 overflow-x-auto rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-code-bg)] p-3 font-mono text-xs text-[var(--color-code-text)]"> |
| 620 | <code>{children}</code> |
| 621 | </pre> |
| 622 | ); |
| 623 | } |
| 624 | |
| 625 | function NextLink({ href, title, body }: { href: string; title: string; body: string }) { |
| 626 | return ( |
| 627 | <li> |
| 628 | <a |
| 629 | href={href} |
| 630 | className="block rounded-md border border-[var(--color-border-subtle)] bg-[var(--color-surface)] p-4 transition hover:border-[var(--color-border)]" |
| 631 | > |
| 632 | <p className="font-mono text-[var(--color-text)]">{title}</p> |
| 633 | <p className="mt-1 font-mono text-xs text-[var(--color-text-muted)]">{body}</p> |
| 634 | </a> |
| 635 | </li> |
| 636 | ); |
| 637 | } |