@sonordev/site-kit/admin-auth — Sign in with Sonor
Put a site's own admin area behind a Sonor login. Whoever can open the project in Sonor (its owners and members, the managing agency, platform admins) can sign in; nobody else can. There are no passwords on the site.
Wiring
// lib/sonor-sso.ts
import { createSonorSso } from '@sonordev/site-kit/admin-auth'
export const sso = createSonorSso({
paths: '/admin', // or ['/admin', '/staff'] for several areas
// loginPath: '/admin/login' (default: `${paths[0]}/login`)
// adminEmails: process.env.ADMIN_EMAILS,
})// proxy.ts
import { createProxy } from '@sonordev/site-kit/proxy'
import { sso } from '@/lib/sonor-sso'
export default createProxy({ before: sso.gate, redirects: true, securityHeaders: true })// app/api/auth/callback/route.ts
import { sso } from '@/lib/sonor-sso'
export const GET = sso.handleCallback// app/api/auth/logout/route.ts
import { sso } from '@/lib/sonor-sso'
export const GET = sso.handleLogout
export const POST = sso.handleLogout// app/admin/login/page.tsx (server component)
import { SSO_ERROR_MESSAGES } from '@sonordev/site-kit/admin-auth'
import { sso } from '@/lib/sonor-sso'
export default async function Login({ searchParams }) {
const { error, return_to } = await searchParams
let href: string | null = null
try { href = await sso.getLoginUrl({ returnTo: return_to }) } catch {}
return (
<>
{error && <p>{SSO_ERROR_MESSAGES[error] ?? error}</p>}
{href ? <a href={href}>Sign in with Sonor</a> : <p>Sign-in is unavailable.</p>}
</>
)
}// Every API route and server action that touches admin data
const session = await sso.getSession()
if (!session) return new Response('Unauthorized', { status: 401 })JSON endpoints can be guarded in the middleware too: pass
apiPaths: ['/api/admin'] and include them in the matcher. They get a
401 JSON response instead of a redirect.
Env
| Var | |
|---|---|
SONOR_API_KEY | Already on every site. Identifies the project and verifies tokens. |
SONOR_SESSION_SECRET | 32+ random chars (openssl rand -hex 32). Rotate to sign everyone out. |
NEXT_PUBLIC_SITE_URL | The site's public origin, for the callback URL. |
The callback route has to be reachable at callbackPath (default
/api/auth/callback) on NEXT_PUBLIC_SITE_URL.
Rules
- The gate doesn't cover
/api/*. The standard middleware matcher excludes it. CheckgetSession()in every API route. - Don't trust identity from headers, query strings or bodies.
getSession()reads the httpOnly cookie and verifies its signature on every call; that's the only source. - Moving a site onto this module: pass its existing
cookieNameandsecretEnvso current sessions stay valid.