Skip to main content

Command Palette

Search for a command to run...

Building a Production-Grade Authentication Service (From Scratch)

Updated
•4 min read•View as Markdown
Building a Production-Grade Authentication Service (From Scratch)
P
Developer. Systems Engineer.

Most authentication tutorials stop at “generate a JWT and you’re done.”

That’s not how real systems work.

I wanted to understand authentication deeply, not just use libraries, so I built a production-grade authentication service from scratch with security, scalability, and real-world failure scenarios in mind.

Goals

  • Understand how authentication works internally

  • Build secure token-based authentication

  • Handle real-world issues like token theft, replay attacks, and brute force

  • Design a system that scales horizontally

Tech Stack

  • Node.js with Express for the API layer

  • PostgreSQL for persistent storage

  • Redis for caching and coordination

  • JWT for access tokens

  • bcrypt for hashing passwords and refresh tokens

Core Design Philosophy

I designed the system around a simple principle: separate performance from control.

This led to a two-token architecture.

Access Tokens (Stateless)

  • Short-lived, around 15 minutes

  • Stored on the client

  • Not stored in the database

  • Verified using a signature

Refresh Tokens (Stateful)

  • Long-lived

  • Stored in hashed form in the database

  • Can be revoked

  • Used to generate new access tokens

This separation provides performance through stateless verification and security through centralized control.

Password Storage

Passwords are never stored directly.

bcrypt.hash(password, 10)

bcrypt provides built-in salting, resistance to brute force attacks, and an adjustable cost factor.

Even if the database is compromised, raw passwords are not exposed.

Token System Design

Access Token

  • Contains userId and role

  • Signed using a secret key

  • Not encrypted, only signed

  • Verified on every request

JWT payloads are readable. Security comes from the signature, not secrecy.

Refresh Token Design

Instead of storing raw tokens, I generate a combination of tokenId and tokenSecret.

Only the hashed tokenSecret is stored in the database, and the combined token is returned to the client.

If the database is compromised, attackers cannot use the stored tokens because they are hashed.

Refresh Token Rotation

On every refresh request, the old token is invalidated and a new one is issued.

This prevents replay attacks and reuse of stolen tokens.

Token Revocation

Each refresh token includes a revoked flag and an expiration timestamp.

This allows invalidating tokens instantly and logging out specific sessions.

Security Hardening

Rate limiting is used to prevent brute force attacks and returns HTTP 429 when limits are exceeded.

Account lockout is implemented after multiple failed login attempts to prevent password guessing.

Refresh tokens are stored in hashed form to protect against database leaks.

JWT signatures ensure that any tampering makes the token invalid.

Architecture Refactor

Initially, all logic was inside route handlers.

I refactored the system into:

  • Routes for handling HTTP requests

  • Services for business logic

  • Middleware for reusable functionality

This improved code structure, testability, and scalability.

Error Handling

Instead of using try-catch blocks everywhere, I implemented centralized error handling middleware.

This ensures consistent responses, cleaner code, and easier debugging.

Background Jobs

Expired tokens are periodically cleaned up from the database.

This prevents unnecessary data growth and maintains performance.

JWT Key Rotation

Instead of relying on a single secret key, the system supports multiple keys.

Old keys can be phased out gradually, allowing safe rotation without downtime.

Key Learnings

JWT is not secure by default.

Stateless and stateful approaches involve tradeoffs.

Security is achieved through multiple layers.

Refresh tokens provide the main control mechanism.

Separation of concerns improves system design.

What I’d Improve Next

  • Add OAuth support such as Google or GitHub login

  • Implement two-factor authentication

  • Add device and session tracking

  • Move secrets to a dedicated secret manager

Conclusion

This project changed how I think about backend systems.

Authentication is not just about logging users in.

It is about trust, control, and handling failure scenarios correctly.

Understanding how the system behaves under attack is just as important as making it work.