Every serious project needs authentication. As someone early in my career, I kept running into the same problem: register, login, email verification, password reset, and OAuth. Every. Single. Project. I’d used Clerk before, and it’s excellent, but I started wondering: how does it actually work under the hood? That question turned into AuthKit — a self-hosted, multi-tenant identity API I now use across all my projects.
The Problem
I’d used Clerk on a previous project, and it felt like magic — you drop in a few components, and suddenly you have login, OAuth, user management, all of it. But magic made me uncomfortable. I didn’t understand what was happening underneath. How does token rotation work? How do you securely store sessions? How do services like Clerk isolate thousands of customers on the same infrastructure?
The only way I knew how to answer those questions was to build it myself.
The Solution: AuthKit
I built a self-hosted, multi-tenant identity API that I deploy once and can connect to any project. Each application registers as a tenant, receives its own API key, and gets its own isolated identity stack — OAuth, 2FA, email verification, password resets, account deletion and restoration, everything you’d expect from a modern auth platform.
Technical Highlights
-
Multi-Tenancy
- Each request includes a tenant API key
- Every database query is automatically scoped to that tenant
- Two apps can have users with the same email address while remaining completely isolated — no separate infrastructure required
-
Security layers
- httpOnly cookies for XSS protection
- Short-lived access tokens (15 minutes)
- 7-day refresh tokens that rotate on every use and are stored server-side
- Bcrypt-hashed passwords
- CSRF-safe OAuth flows
- Rate-limited endpoints
-
Token Refresh (the hardest part)
- The real challenge wasn't the backend — it was coordinating refresh logic on the frontend
- When several requests fail at once, each attempts to trigger a refresh, leading to race conditions and invalid tokens
- AuthKit solves this by queueing failed requests, performing a single refresh, then replaying everything with the new token
-
JWT rotation
- Access tokens are short-lived; refresh tokens rotate on every use
- Each rotation invalidates the previous token and issues a new one
- Refresh tokens are stored in the database and revoked on use
The Frontend
I built a reference client in Next.js to prove the system works end‑to‑end. It handles automatic token refresh, shows clear feedback when rate limits are hit, and uses a mobile‑first responsive layout. The 2FA setup supports both QR codes and manual entry.
What I Learned
Building AuthKit taught me why services like Clerk and Auth0 make the choices they do. Error codes matter a lot — distinguishing TOKEN_EXPIRED from UNAUTHORIZED, for example, lets the frontend decide whether to refresh silently or redirect to the login page. Toast timing matters — if a toast persists across navigations, the user sees duplicate error messages, and the UI feels chaotic.
Try It Yourself
The full source is on GitHub. If you're learning auth or want a starting point for your own projects, give it a spin.
Live Demo
Backend Repo
Frontend Repo
If it helps, a star would mean a lot!

Top comments (0)