Updated 29 July 2026
What to look for in a Postgres auth library
A Postgres auth library owns more than a user table. Its schema and transactions define how credentials are created, verified, expired, consumed, and revoked. Evaluate the stored values and state changes first. Framework adapters and prebuilt forms sit above those decisions.
Credential storage
Inventory each security record: users, password verifiers, sessions, magic links, email-verification tokens, password-reset tokens, phone codes, organisation invitations, API keys, rate-limit counters, and audit events. The schema should make purpose, owner, expiry, use state, and revocation state explicit where they apply.
Passwords need a password-specific function. OWASP recommends Argon2id for new systems because its memory and compute costs are designed to resist offline guessing. Session tokens, one-time links, and API keys are different inputs. They should be generated with a cryptographically secure random source, returned only to the path that needs the raw value, and represented in Postgres by a protected verifier.
Own Auth uses Argon2id for passwords. It stores protected hashes for sessions, magic links, verification and reset tokens, phone codes, invitations, and application API keys. OWN_AUTH_TOKEN_PEPPER supplies a stable server-only secret for those verifiers. Rotating the pepper invalidates the protected credentials, so it is an access-reset mechanism rather than a routine transparent rotation.
Server-authoritative sessions
A database session should expose an opaque credential while keeping state in Postgres. The record needs absolute expiry, optional idle expiry, revocation, and enough activity metadata to support session lists and incident review. Verification must read current server state so logout, administrator action, account disablement, or expiry takes effect without waiting for a client token to age out.
import { createOwnAuth } from "own-auth";
export const auth = createOwnAuth({
tokenPepper: process.env.OWN_AUTH_TOKEN_PEPPER,
session: {
ttlMs: 30 * 24 * 60 * 60 * 1000,
idleTtlMs: 7 * 24 * 60 * 60 * 1000,
},
});Own Auth returns a raw opaque session token after authentication and stores its protected hash. auth.getCurrentSession validates the token against Postgres and returns the current user and session, or null for a missing, expired, revoked, or disabled-user session. auth.revokeSession marks one device session as revoked; auth.revokeAllSessions invalidates the account's current sessions.
The client transport remains an application decision. A same-origin web application can use a Secure HttpOnly cookie with an explicit SameSite policy and a CSRF control for state-changing requests. Native and API clients can use protected credential storage and an authorization header. In every model, raw tokens stay out of URLs and logs.
Atomic one-time credentials
Magic links, verification links, password resets, SMS codes, and invitations each authorize a state change. Checking a used_at field and updating it in separate operations leaves a concurrency gap. The storage interface needs an atomic consume operation so two requests cannot both claim the same credential.
Own Auth 0.3.6 includes atomic AuthStorage.consumeToken and AuthStorage.consumeSmsOtp operations in its storage contract. Its built-in Postgres implementation admits one successful consumer under concurrent verification. Token type, expiry, use state, and SMS attempt policy are checked as part of the workflow before a session or account change is returned.
Authentication and tenant authorization
The library should derive the actor from a verified session or API key. Route handlers must not trust a user ID, organisation ID, or role supplied by the client. For B2B products, organisation membership is checked for the selected tenant, and every product query still applies its own tenant boundary.
Own Auth enforces roles and permissions for its organisation membership, invitation, organisation API-key, and organisation audit operations. It does not infer authorization for application tables such as projects, invoices, or deployments. Those checks remain in product code and use the verified actor returned by Own Auth.
Shared abuse state
Rate-limit state must be shared by every application instance. A process-local counter resets on restart and splits a limit across replicas. A Postgres-backed store coordinates limits using operation-specific keys such as normalized email, phone number, user, organisation, or API-key owner.
Password verification, magic-link requests, SMS sending, invitation creation, and API-key issuance protect different resources. Inspect the key, window, threshold, and public error for each operation. Network-level limits can remain at the application edge instead of being folded into one database bucket.
Schema ownership and migrations
The package should publish ordered migrations for every auth table, index, constraint, and storage contract change. Apply those migrations before traffic reaches code that expects the new schema. If the application uses another migration tool, exclude package-owned tables from generated changes so two systems do not compete for the same definitions.
Own Auth exposes the authentication behavior through its documented server API rather than generated framework routes. auth.signUpEmailPassword creates a user and session, auth.signInEmailPassword verifies credentials, and auth.getCurrentSession resolves the actor on later requests. The application does not call password-hashing helpers or write directly to auth tables.
const { user, sessionToken } =
await auth.signUpEmailPassword({
email: "alice@example.com",
password: "her-password",
name: "Alice",
});
const current = await auth.getCurrentSession(sessionToken);