Password Login with Lockout and Audit Trail on Better Auth
Local accounts with slow password hashing, progressive lockout and an audit record for every sign-in outcome.
- Runs out of the box: yes
- Tests: yes
- CI: yes
- Docs: yes
- Used in production: no
Purpose#
A Next.js application with local, admin-created accounts. Passwords are hashed with a memory-hard algorithm, repeated failures lock the account for progressively longer periods, and every outcome (success, failure, lockout, reset) writes an append-only audit row.
The example shows where the session cookie is issued, how the lockout counter is reset, and how the sign-in form avoids revealing whether an account exists.
What you will learn#
- Configure Better Auth for username-and-password sign-in with admin-created accounts
- Implement progressive lockout without enabling denial of service against known usernames
- Write one audit row per sign-in outcome with enough context to investigate
Media#
Technologies & features#
- Technologies
- TypeScriptNext.jsBetter AuthPostgreSQL
- Features
- Password AuthenticationAudit Logging
- Topics
- Credential ManagementprimaryAuthorization & Security
Lessons learned#
- Worked
Showing the same message for unknown users and wrong passwords, and adding the same delay to both, removed the account-enumeration signal in our manual testing.
- Didn’t work
A hard lock after five failures let anyone lock out a known username. Progressive delays with a ceiling kept the account usable for its owner.
Try it#
git clone https://git.example.internal/business-systems/password-login-example.git
cd password-login-example
pnpm install
cp .env.example .env.local
pnpm db:migrate && pnpm devSign in as admin with the one-time password printed by pnpm db:migrate.
Commands come from the owner’s README. Credentials are never stored here.

