Stop 22 · Build Backend Portfolio ProjectsProjects · 7/14

Authentication Microservice

Part of the Backend Developer Roadmap

In short

Login looks simple until you add tokens, refresh, password reset and five other services that need to trust it. A standalone auth service shows you understand security, not just bcrypt.

A dedicated, reusable auth service — signup, login, tokens, password reset — that other services in a larger system could call, practicing service boundaries and security end-to-end.

The purple blob at a campus security desk hands a blank ID badge to a student.

Think of it like this

The campus ID card office

One office checks your documents and prints your ID card. After that, the library, the hostel and the exam hall just look at the card. They don't call the office every time. That's an auth service issuing signed tokens.

Watch it happen

Log in once, use everywhere

App
Auth service
Orders service

POST /login

1/7The app sends email and password to the auth service only.

The big ideas

What you need to know

Hash passwords properly

Use argon2id or bcrypt. Never store plain or simply SHA-256 hashed passwords.

Short access, rotating refresh

Access tokens live minutes. Refresh tokens live days, are stored hashed, and are replaced on every use.

Asymmetric signing

Sign with a private key (ES256 or RS256) and publish public keys at a JWKS URL so services verify alone.

Password reset done right

Random, single-use, short-expiry tokens, stored hashed, sent by email. Old sessions end after a reset.

See it in code

Walk through the code

ts
1import { SignJWT, importPKCS8 } from "jose";2import argon2 from "argon2";3 4const privateKey = await importPKCS8(process.env.JWT_PRIVATE_KEY!, "ES256");5export async function login(email: string, password: string) {6  const user = await db.user.findUnique({ where: { email } });7  // Same error for "no user" and "wrong password", so attackers can't probe emails8  if (!user || !(await argon2.verify(user.passwordHash, password))) {9    throw new Error("Invalid email or password");10  }11  const accessToken = await new SignJWT({ role: user.role })12    .setProtectedHeader({ alg: "ES256", kid: "key-1" })13    .setSubject(user.id).setIssuer("auth.myapp.dev").setExpirationTime("15m")14    .sign(privateKey);15  return { accessToken, refreshToken: await createRefreshToken(user.id) };16}

1/3Load the private signing key once at startup. Only the auth service ever has it.

Other services fetch the public key from your JWKS endpoint and verify tokens with jose's jwtVerify, checking issuer and expiry.

Save yourself the pain

Common mistakes

Oops

Giving access tokens a 30-day expiry because refresh feels complicated.

Do this instead

A stolen 30-day token works for 30 days. Keep access tokens short and use refresh tokens.

Oops

Leaving the login endpoint without rate limits.

Do this instead

Limit attempts per account and per IP, or attackers can guess passwords all day.

Your mission

Milestone 1: login and verify

~45 min

Quick check

Why sign tokens with a private key and let other services verify with a public key?

Go deeper

  1. 1Expose signup, login, token refresh and password reset as clean, documented endpoints.
  2. 2Design it to be consumed by other services — issue JWTs other APIs can verify independently.
  3. 3Add rate limiting and account lockout on the login endpoint specifically.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge