Distributed authentication system to compare JWT (RS256) and PASETO (v4.public) in a microservice architecture.
This project demonstrates:
- JWT and PASETO token issuance and verification.
- Access token + refresh token flow with rotation.
- API Gateway as a single public entry point.
- Internal service-to-service trust boundary using
x-internal-secret. - Shared token verification logic for both gateway and resource service.
Services:
api-gateway(http://localhost:3000)auth-service(http://localhost:3001)resource-service(http://localhost:4000)
Responsibilities:
auth-service- Register/login users.
- Issue JWT or PASETO access/refresh tokens.
- Verify tokens.
- Refresh token rotation and reuse detection.
api-gateway- Public entrypoint (
/api/*). - Auth and resource rate limiting.
- Token auth middleware.
- Proxy to internal services.
- Inject trusted
x-internal-secretheader.
- Public entrypoint (
resource-service- Validates internal secret.
- Re-verifies bearer access token.
- Exposes protected resources.
Shared code:
shared/types: common auth types.shared/utils/token.ts: common token normalization/verification helpers.
JWT:
- Algorithm:
RS256. - Access token TTL:
15m. - Refresh token TTL:
7d.
PASETO:
- Version:
v4.public(Ed25519). - Access token TTL:
15m. - Refresh token TTL:
7d. subis encoded as string when signing (library claim requirement), then normalized back to number on verification.
- Node.js + TypeScript
- Express
- Prisma + PostgreSQL
jsonwebtoken+pasetohttp-proxy-middlewareexpress-rate-limit
- Node.js 20+
- npm 10+
- PostgreSQL
Top-level packages:
api-gateway/auth-service/resource-service/shared/scripts/REST/(HTTP request collection files for manual testing)
Create .env for each service as needed.
Required:
- For
auth-service:DATABASE_URL(PostgreSQL connection string)INTERNAL_SECRET(must match gateway/resource)
- For
api-gateway:INTERNAL_SECRET(must match auth/resource)
- For
resource-service:INTERNAL_SECRET(must match gateway/auth)
Optional (gateway rate limit):
AUTH_RATE_LIMIT_WINDOW_MS(default60000)AUTH_RATE_LIMIT_MAX(default20)RESOURCE_RATE_LIMIT_WINDOW_MS(default60000)RESOURCE_RATE_LIMIT_MAX(default60)
From repository root:
npm install
npm --prefix api-gateway install
npm --prefix auth-service install
npm --prefix resource-service installGenerate key pairs (JWT RSA + PASETO Ed25519):
npm run gen-keysRun Prisma migration (auth-service):
npm --prefix auth-service run prisma:generate
npx --prefix auth-service prisma migrate deployFor local development (if you create new schema changes):
npx --prefix auth-service prisma migrate devRun all services concurrently:
npm run dev:allOr run one by one:
npm run dev:auth
npm run dev:gateway
npm run dev:resourcePublic endpoints through gateway:
GET /api/publicGET /api/jwt-protectedGET /api/paseto-protectedPOST /api/auth/jwt/registerPOST /api/auth/jwt/loginPOST /api/auth/jwt/verifyPOST /api/auth/jwt/refreshPOST /api/auth/jwt/logoutPOST /api/auth/paseto/registerPOST /api/auth/paseto/loginPOST /api/auth/paseto/verifyPOST /api/auth/paseto/refreshPOST /api/auth/paseto/logoutGET /api/auth(health via proxy to auth-service)
Resource endpoints through gateway:
- JWT route prefix:
/api/jwt-resource/* - PASETO route prefix:
/api/paseto-resource/*
Examples after rewrite:
/api/jwt-resource/profile/api/paseto-resource/orders
Use files in REST/:
gateway-auth-flow.httpjwt-flow.httppaseto-flow.httpjwt-logout-flow.httppaseto-logout-flow.httpjwt-abuse-edge.httppaseto-abuse-edge.http
These files cover:
- register/login/verify
- refresh token rotation
- logout behavior
- abuse/reuse edge cases
- Gateway strips any client
x-internal-secretand injects trusted internal secret. auth-serviceandresource-servicereject requests without valid internal secret.- Refresh tokens are stored as hashes in database.
- Refresh token reuse triggers revocation of all active refresh tokens for that user.
This project is a thesis prototype and is not production-ready.
- Key distribution is handled locally for demonstration purposes.
- Testing is mainly based on HTTP request files rather than automated integration tests.
- The focus is on authentication design and security controls rather than production deployment.
- Docker/Compose containerization is not included yet (services are run directly in local development).
- Access tokens are stateless and are not revoked immediately on logout (they remain valid until expiry).
- Rate limiting uses in-memory stores and is not distributed across multiple instances.
- Add Docker and Docker Compose for reproducible local and demo environments.
- Introduce automated integration and end-to-end tests for auth and refresh flows.
- Add role/permission claims and authorization policies.
- Move rate limiting and token revocation state to shared infrastructure for horizontal scaling.
- Add structured logging, metrics, and basic observability dashboards.
- Add CI pipeline checks for linting, build, and test execution.
If npm run dev:all exits with code 1:
- Ensure all
.envfiles exist andINTERNAL_SECRETmatches across services. - Ensure
DATABASE_URLis set and PostgreSQL is reachable. - Re-run key generation:
npm run gen-keys. - Reinstall dependencies in each package.
- Re-run Prisma generate/migrations for auth-service.
If TypeScript shows path/rootDir errors:
- Confirm each service uses local TypeScript from its own
node_modules. - Reinstall package dependencies after changing
tsconfigor TS version.
ISC