Authentication & Authorization Flow
This document explains how users log in, how their identity is verified across different services, and how we keep the system secure.
1. Executive Summary
Goal: Ensure that only the right people can access the right data.
- Secure Login: We use industry-standard encryption to protect passwords and user sessions.
- Hybrid Token Strategy: We support both Bearer tokens (localStorage) and HttpOnly cookies to handle modern browser restrictions on third-party cookies.
- Data Privacy: A practitioner from "Clinic A" can never accidentally see patient records from "Clinic B". This is enforced automatically by the system.
- Session Safety: If a user logs out or is banned, their access is revoked immediately across the entire platform.
2. The Login Flow (Frontend to Auth Service)
The Basics
When a user logs in on the website:
- They enter their email and password (case-insensitive email matching).
- The frontend sends a
POSTrequest to the Auth Service (https://auth.skinclubpro.com). - The Auth Service checks the password. If it's correct, it creates a "Session Token" (JWT).
- Dual Token Delivery: The Auth Service:
- Sets the token as an HttpOnly Cookie (for same-origin requests)
- Returns the token in the response body (for cross-origin Bearer auth)
- The frontend stores the token in localStorage and includes it as a
Bearertoken in theAuthorizationheader for all subsequent requests.
Why Both Methods?
Modern browsers increasingly block third-party cookies (cookies set by a different origin than the page). Since our frontend (skinclubpro.com) calls APIs on subdomains (auth.skinclubpro.com, clinic.skinclubpro.com), cookies may not be sent reliably. The Bearer token approach solves this.
Architectural Deep Dive
- Credential Exchange: Standard username/password exchange over TLS.
- Token Generation: The Auth Service signs a JWT (JSON Web Token) using an RSA Private Key (RS256). This asymmetric signing is key to our distributed architecture.
- Cookie Security (Backup Method): The cookie is configured with:
HttpOnly: Prevents client-side script access.Secure: Sent only over HTTPS (in production).SameSite=None: Required for cross-origin cookie sending (withSecure).Domain: Set to.skinclubpro.comto allow subdomains to share the session.
- Bearer Token (Primary Method):
- Stored in
localStorageunder keyaccess_token. - Attached to every request via
Authorization: Bearer <token>header. - Axios interceptors automatically add this header to all API clients.
- Stored in
3. Accessing Protected Data (Frontend to Clinic/CMS)
The Basics
Now the user wants to view their appointments (Clinic Service) or edit a blog post (CMS Service).
- The frontend makes a request (e.g.,
GET /appointments). - The Axios client automatically attaches the
Authorization: Bearer <token>header. - The Service (Clinic or CMS) receives the request and extracts the token from:
- First: The
Authorizationheader (Bearer token) - Fallback: The
access_tokencookie
- First: The
- It extracts the token and verifies: "Is this token valid?" and "Who is this user?".
- If valid, the request is allowed.
Token Extraction Order
jwtFromRequest: ExtractJwt.fromExtractors([
ExtractJwt.fromAuthHeaderAsBearerToken(), // Primary
(req) => req.cookies?.access_token, // Fallback
])
4. Automated Tenant Isolation (Anti-IDOR)
The Basics
In a multi-tenant system like Skin Club Pro, it is critical that a practitioner from "Clinic A" cannot see data from "Clinic B." We use an automated system to ensure every database query is restricted to the correct tenant.
Implementation Details
- Tenant Context: We use Node.js
AsyncLocalStorageto store thetenantIdfor the duration of an API request. This context is "thread-safe" and accessible anywhere in the code without passing parameters. - Global Interceptor: A
TenantInterceptorruns on every request. It extracts theuserId(our tenant key) from the JWT and "runs" the request inside a tenant-scoped context. - Base Entity: All tenant-specific database tables extend a
TenantBaseEntity.- Auto-Save: When a new record is created, the
tenantIdis automatically pulled from the context and saved to the database. - Auto-Filter: We use a
withTenant()utility to automatically inject thetenantIdinto database queries, ensuring developers cannot accidentally leak data.
- Auto-Save: When a new record is created, the
5. Verifying the Token (Service-to-Service Trust)
The Basics
How does the Clinic Service know the token is real? It didn't create it! It uses a system called JWKS (JSON Web Key Set).
- The Auth Service publishes a public "lock" (Public Key) at a known URL (
https://auth.skinclubpro.com/.well-known/jwks.json). - The Clinic Service downloads this public key on startup.
- When a token arrives, the Clinic Service uses this public key to verify the signature.
- If the signature matches, it proves the Auth Service created the token, and it hasn't been tampered with.
Architectural Deep Dive
- Zero Trust / Shared Nothing: The services do not share a secret key (like
JWT_SECRET). This prevents a compromised Clinic Service from minting valid tokens for other services. - Asymmetric Cryptography (RS256):
- Auth Service: Holds the Private Key (can sign tokens). Stored in AWS Secrets Manager as
{Env}/AuthService/JwtPrivateKey. - Auth Service: Also holds the Public Key for its own verification. Stored as
{Env}/AuthService/JwtPublicKey. - Other Services: Fetch the Public Key dynamically via JWKS endpoint (can only verify tokens).
- Auth Service: Holds the Private Key (can sign tokens). Stored in AWS Secrets Manager as
- Key Management:
- Keys are stored base64-encoded in AWS Secrets Manager.
- The infrastructure (CDK) automatically injects these as environment variables into ECS containers.
- Caching: The
jwks-rsalibrary caches the public key to prevent making an HTTP request to the Auth Service for every single API call, ensuring high performance.
6. Token Revocation (Logout)
The Basics
JWTs are valid until they expire (e.g., 1 hour). What if a user logs out or their account is banned? We need to stop the token now.
- When a user clicks "Logout", we send the token's ID to the Auth Service.
- The Auth Service saves this ID in a "Blocklist" (using Redis).
- The frontend clears the token from
localStorage. - Now, whenever any service checks a token, it asks the Auth Service (or checks Redis directly): "Is this token blocked?"
- If it is, the request is rejected, even if the token signature is valid.
Architectural Deep Dive
- The Revocation Problem: Stateless JWTs cannot be revoked by design. We re-introduce state via a Redis Blocklist.
- JTI (JWT ID): Every token has a unique
jticlaim. - Redis Strategy:
- Key:
blocklist:<jti> - Value:
true - TTL: The key is set to expire when the JWT itself expires. This keeps Redis memory usage efficient (self-cleaning).
- Key:
- Distributed Check:
- The
JwtStrategyin the Auth Service queries Redis directly to check for revocation. - Other services (Clinic, CMS) rely on the Auth Service for token verification via JWKS.
- The