Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

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.

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.

Last updated on August 4, 2026

Was this page helpful?