Custom authentication
Connect any TypeScript session system while preserving stable identity, tenant isolation, and password-independent recovery.
Ownfold does not authenticate users. VaultServer asks the host application for one stable user ID
on every operation. This keeps the encryption lifecycle independent of session vendors and lets the
same adapter work with cookies, OAuth sessions, signed tokens, or an internal identity service.
For a process that is truly private and single-user, use the explicit no-authentication recipe instead of inventing a fake session.
Implement the resolver
import type { VaultServerOptions } from "@ownfold/server"
export const getUserId: VaultServerOptions["getUserId"] = async ({ request }) => {
const session = await sessions.read(request)
return session?.user.id ?? null
}
export const vaultServer = createVaultServer({
adapter,
getUserId,
})
Return an immutable, non-empty application user ID or null. Ownfold converts null into an
authentication failure. A thrown resolver error fails closed and must not select a default identity.
Stable identity
Use the application’s internal primary user ID, not an email address, display name, provider access token, or mutable username. If users can link several login providers, all linked identities must resolve to the same internal ID.
Changing the returned ID makes the existing vault appear to belong to another account. Account migrations therefore need an explicit, transactional ownership-migration procedure; changing a resolver mapping during login is unsafe.
Cookie sessions
const getUserId: VaultServerOptions["getUserId"] = async ({ request }) => {
const cookie = request.headers.get("cookie")
if (cookie === null) return null
const session = await sessionStore.verifyCookie(cookie)
return session?.userId ?? null
}
The verifier must check signature or opaque session lookup, expiry, revocation, and intended
application. Configure cookies with Secure, HttpOnly, and an appropriate SameSite policy.
Bearer tokens
const getUserId: VaultServerOptions["getUserId"] = async ({ request }) => {
const authorization = request.headers.get("authorization")
if (authorization?.startsWith("Bearer ") !== true) return null
const claims = await tokenVerifier.verify(authorization.slice("Bearer ".length), {
audience: "ownfold-example-api",
})
return claims?.subject ?? null
}
Verify signature, issuer, audience, expiry, not-before time, revocation policy, and tenant before using the subject. Decoding a JWT is not verification.
Multi-tenant applications
If one human can have separate vaults in separate tenants, return a canonical composite identity that cannot collide:
return `${session.tenantId}:${session.userId}`
Both parts must come from the verified session. Do not accept tenantId from query parameters or a
request body and combine it with an authenticated user.
Trusted proxies
Identity headers such as x-user-id are safe only when an authenticated proxy overwrites them and
the application origin cannot be reached directly. Strip inbound copies at the edge, authenticate
the proxy-to-application connection, and document the trust boundary. Otherwise verify the session
inside the application.
Password reset and recovery
Login passwords and vault recovery secrets are independent. A host password reset may restore account access, but it must not silently recover encrypted data. The user still needs an authorized device or Recovery Kit.
Never pass these values to Ownfold:
- login passwords;
- OAuth access or refresh tokens;
- MFA secrets;
- session database rows; or
- Recovery Kit passwords or codes.
Account deletion
Define whether account deletion immediately removes Ownfold coordination rows and encrypted application records or schedules retention. Explain that deleting every authorized device envelope and Recovery Kit copy makes remaining ciphertext permanently unrecoverable.
Deletion must authorize the current user through the host system; possession of a vault identifier is not authorization.
Tests
- Missing, expired, revoked, malformed, and wrong-audience sessions return unauthorized.
- User A cannot read or mutate user B’s vault, devices, pairings, rotations, or records.
- Tenant A cannot select tenant B through request input.
- Linked login methods resolve to one stable internal ID.
- Password reset does not create a vault recovery path.
- Resolver exceptions fail closed and do not call the adapter under a fallback user.
- Framework bridges preserve the original cookies and authorization headers.
Troubleshooting
A returning user sees “vault not created”
Compare the current stable ID with the ID used at vault creation. Common causes are switching from database IDs to emails, changing provider subject formats, or omitting tenant scope.
Every request is unauthorized
Confirm the original Request reaches VaultServer, the browser sends the intended credentials,
and the session verifier accepts the endpoint’s audience and origin.
Users can access another tenant’s vault
Stop traffic to the affected endpoint. The resolver or adapter ownership predicate is incomplete. Preserve scrubbed evidence, repair tenant derivation, and follow the incident-response guide before resuming service.