Credentials and server security
Understand cookie exchange, explicit principals, redaction, and server-boundary controls.
The server boundary treats Better Auth cookies and Convex JWTs as credentials. They must remain request-scoped and absent from public error fields.
Credential paths
| Input | Behavior |
|---|---|
| Request Better Auth cookies | Filter relevant cookies and exchange once per request, shared with SSR |
| Explicit cookie credential | Exchange as a required principal |
Explicit Convex authToken | Set directly on the request-scoped HTTP client |
auth: 'none' | Skip credential resolution |
getConvexUser(event) | Reuse the same request exchange as SSR and serverConvex(event) |
Raw Better Auth session tokens are intentionally not accepted as public bearer credentials. Bearer session handling is reserved for the package's private, marked Convex bridge.
Controls
| Threat | Control |
|---|---|
| Credential forwarded to another origin | Token exchange rejects redirects |
| Oversized/malformed exchange response | Bounded response reader and transport classification |
| Header injection | Empty/control-character credentials rejected before fetch |
| Cookie confusion | Cookie and direct-token paths are mutually constrained |
| Secret leakage in payload/log | cause is non-serialized; public fields are sanitized |
| Cross-request identity reuse | One caller and token promise per request |
Public errors
Authentication and transport failures expose a stable category and safe message. Raw upstream response bodies are not copied into message, code, status, data, or functionName.
Structured Convex application errors remain server errors and preserve their application data. Their message is the text from ConvexError data, never Convex's wire message.
toConvexH3Error and requireConvexUser put only the serialized public error into the H3 response. When the auth backend fails, getConvexUser and requireConvexUser answer 502 without credential details instead of treating the request as anonymous.
Trusted client IP headers
Outside exact loopback development, configure
auth.trustedClientIpHeader with a header that a trusted ingress
overwrites with exactly one client IP. Without it, the build fails and auth requests stop. Do not trust a client-supplied forwarding chain. Public
traffic must not be able to reach the Nuxt origin around the ingress unless the
origin independently authenticates ingress requests; header overwrite alone
cannot defend a bypassable origin.
convex: {
auth: {
origin: process.env.SITE_URL ?? 'http://localhost:3000',
trustedClientIpHeader: process.env.BCN_AUTH_TRUSTED_CLIENT_IP_HEADER
}
}Request-bound SSR hydration and serverConvex(event) use this same signed
identity. Cookie-to-token exchange is deliberately not exposed as a raw public
helper because it would collapse unrelated clients onto the Nitro egress IP.
Application responsibilities
- Validate route bodies and webhook signatures.
- Keep secrets in server or Convex environments.
- Enforce product authorization in Convex.
- Avoid public caching of auth routes and authenticated HTML.
- Redact application logs and monitoring breadcrumbs.
- Rotate compromised secrets and invalidate sessions through Better Auth.
See the broader security model.