Skip to content

Deploy your application

Use this guide for the application you are deploying. If Udibo hosts sign-in, you operate the client, BFF, sessions, and API. If your app hosts its own authorization server, you also operate its credential, code, token, and signing-key storage. The package provides protocol handling; your deployment supplies persistent storage, secrets, delivery, and application policy.

Choose the integration boundary

ResponsibilityApp using UdiboApp hosting authorization
Callback and application sessionsYour appYour app
API token validation and record-level accessYour appYour app
Login, password reset, MFA, and email deliveryHosted sign-in flowYour app's configured identity flows
Registered clients, authorization codes, token issuanceIdentity serviceYour authorization server
Token signing keysIdentity serviceYour authorization server, if issuing JWTs

Udibo's hosted-service setup is described in use Udibo. The rest of this guide documents package integration, not hosted-service administration.

The multi-instance rule

State needed by two requests must be available to either request's process. A local example can keep it in memory; a deployment with replicas, serverless isolates, or restarts needs shared persistence or an appropriate encrypted cookie.

StatePublic contractRequirement
BFF sessionSessionStoreEnforce expiry and revocation; stateful update must never recreate a missing session
Pending browser loginAuthRequestStorage / AuthRequestStorageFactoryPreserve state and PKCE verifier across redirects; bind completion to the initiating browser
Authorization codesAuthorizationCodeServiceInterfaceAtomic single-use consumption, expiry, client and redirect binding
TokensTokenServiceInterfaceProtect stored token material; atomic revocation/rotation claims
App credentialsIdentityUserStorePersist the complete credential, including params; unique identifiers
Reset and verification linksTokenFlowStoreExpiring, single-use records; implement invalidation capabilities your app needs
Email codesOtpStoreAtomic attempt increments and boolean consume; protect low-entropy code hashes
MFAMfaStoreProtect TOTP secrets; atomically consume recovery codes
Rate limits and lockoutRateLimitStore / LockoutStoreShared counters and expiry across instances

Run the appropriate contract suite from @udibo/oauth2/testing/contract or @udibo/oauth2/hono/bff/testing against your actual adapter. Test concurrent callers against the database implementation, not just an in-memory substitute.

Configuration inventory

Keep public app origin, issuer, registered callback URLs, client credentials, requested scopes, session lifetimes, and storage configuration in deployment configuration. Validate required values at startup. Do not silently fall back to demo secrets or localhost URLs in production.

For an app-owned issuer, construct resolve with the configured services and issuer. Do not derive the issuer or emailed links from an unvalidated Host header.

Secrets

Store client secrets, cookie encryption keys, signing keys, and mail credentials in your deployment's secret store. Use different values for each environment. Keep token responses, cookies, authorization codes, PKCE verifiers, reset links, and TOTP/recovery secrets out of logs and exception telemetry.

Persist signing and encryption keys across deployments. Generating replacements at startup breaks existing sessions or token verification on every restart.

Signing keys and rotation

This section applies only when your app issues OIDC or JWT tokens. Generate a signing key with deno run jsr:@udibo/oauth2/cli oidc keygen, then store its output as a secret. The package's issuer signs with ES256; verify that your consumers accept that algorithm.

Use RotatingSigningKeyProvider to publish the previous public key while signing new tokens with a new key:

TypeScript
import {
  importSigningKeyJwk,
  RotatingSigningKeyProvider,
} from "@udibo/oauth2/server/authorization";

declare const currentJwk: JsonWebKey;
declare const previousJwk: JsonWebKey;

const signingKeys = new RotatingSigningKeyProvider({
  current: await importSigningKeyJwk(currentJwk),
  previous: [await importSigningKeyJwk(previousJwk)],
});

For routine rotation, keep the previous public key available through the longest outstanding token lifetime and relevant consumer cache windows. Compromise response may require retiring a key sooner and forcing reauthentication. Offline JWT validation cannot provide immediate per-token revocation.

Stores you provision

Choose the stores required by your enabled flows. A client using Udibo does not need local password or authorization-code tables. An app hosting password login needs credential storage and session revocation; email codes additionally need an OtpStore, while reset links and magic links use TokenFlowStore.

Automatic password rehash or import upgrades require IdentityUserStore.replaceCredential. It must atomically compare all old credential fields before writing the replacement. A failed comparison writes nothing and the password is re-verified against the credential now stored; omitting the capability skips automatic upgrades. Explicit password resets still use setCredential.

For rotating refresh tokens, revokeRotated (or the fallback revoke) must return true only to the caller that consumed the old credential. Keep replay tombstones and family revocation state if you enable reuse detection. The library's calls are separate storage operations; your adapter must account for competing writes and failures. Revoking a family must also prevent a racing save from restoring it.

Sequential OTP resends invalidate prior codes. Concurrent requests use separate invalidate/create calls, so that alone does not guarantee one outstanding code. Serialize issuance for each (email, purpose) across instances when your application requires that policy. Atomic consume separately ensures a given code succeeds once.

Cookies and sessions

The BFF defaults to Secure, HttpOnly, host-bound cookies and enables CSRF checks. Keep those settings on HTTPS deployments. The pending authorization cookie needs to survive the authorization server's redirect back to your app; use the provided pending-login cookie factory or a correctly scoped app-owned store.

Prefer a stateful SessionStore when your app needs immediate revocation, session listings, or backchannel logout. EncryptedCookieSessionStore is an alternative with a different contract: a copied cookie remains usable until expiry, even after logout clears the original browser's cookie. It also has browser cookie-size limits. See session limitations.

Keep the session lifetime consistent between the store and HonoBff.sessionMaxAgeMs. A stateful store's update must reject revoked, expired, or missing records; a refresh finishing after logout must not insert a session again.

If you expose a session list, return a separate, non-secret record ID as SessionSummary.id. The BFF's cookieValue is a credential and must never appear in listings, URLs, analytics, or browser-readable session summaries.

TLS and the reverse proxy

Serve deployed callbacks, token endpoints, and APIs over HTTPS. Configure your proxy to preserve the external origin in a trusted way and forward the request headers required by your CSRF policy. Do not trust arbitrary client-supplied forwarded headers.

Generic OIDC connectors rely on direct TLS token exchange for ID-token integrity. They validate claims but do not verify signatures. Configure trusted issuers, retain endpoint HTTPS checks, and never use these connectors to accept ID tokens obtained through unrelated channels. Apple uses separate signature verification.

CSRF, CORS, and security headers

Keep the BFF and browser on the same origin where possible. BffClient sends the required CSRF header. Handwritten requests must follow the same contract. A React route guard changes rendering; it does not authorize an API request.

honoIdentityRoutes includes a same-origin guard on unsafe requests. Custom login, social-linking, MFA, session-revocation, and password-change routes must apply their own authentication and CSRF policy. Bind sensitive actions to the current authenticated user rather than trusting a submitted user ID.

Configure security headers for your app's content and hosting environment. Do not log or place reset tokens in third-party analytics URLs. Use a restrictive referrer policy on pages that receive credentials in URLs.

Redirect-URI policy in production

Register exact HTTPS callbacks for web applications. Keep local development registrations separate. Native loopback IP redirects have protocol-specific port matching; do not treat that exception as a general wildcard.

If your own server accepts wildcard registrations, supply isPublicSuffix from @udibo/oauth2/server/public-suffix and constrain patterns to a hostname namespace your application controls. Shared hosting domains are not an ownership boundary. Exact callbacks avoid this additional policy surface.

Rate limiting, lockout, and password policy

IdentityService always applies password policy, but rate limiting, account lockout, and CAPTCHA are opt-in integrations. Construct the protections you need; protectionMode: "log-only" records decisions without enforcing them. Default deployed configuration should enforce.

Apply IP/request throttling at the route layer as well as identifier-based limits within identity flows. Rate-limit mail requests and code verification. A six-digit code has only a million possibilities: storing its hash does not make a leaked code database resistant to offline guessing.

For password reset, implement TokenFlowStore.deleteBySubject to invalidate outstanding sign-in links. Reset also attempts OTP invalidation when the reset token contains the email. These cleanup operations are best-effort; decide how your app responds to failures and monitor them. See known limitations.

Backups and restore

Back up persistent credentials and MFA records with appropriate access controls. Keep encryption keys separately protected and rehearse restoration. A restore can reintroduce sessions or tokens that were revoked after the backup; include reauthentication or explicit revocation in the recovery procedure.

Apply retention to expired codes, tokens, sessions, and audit events. Retain rotation tombstones long enough for your chosen replay-detection policy.

Observability, audit, and health checks

Record request IDs, flow outcomes, authenticated client IDs, and stable error codes. Avoid logging raw request bodies on credential endpoints. Treat audit callbacks as observation: a callback whose errors are isolated is not an enforcement gate. Use the extension reference to check when a callback runs and how failures propagate.

Monitor failed callbacks, refresh rejection, storage errors, email-delivery failures, and token-validation timeouts. Test recovery from an unavailable issuer or database without relabeling those failures as bad credentials.

Before you go live

Complete the deployment checklist against your chosen integration. Test in the actual proxy, cookie, and storage environment; a local example passing does not validate production configuration.