Auth Service is structured around Clean Architecture with a strict, unidirectional dependency rule:Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ricardomb-tech/auth-service/llms.txt
Use this file to discover all available pages before exploring further.
infrastructure → application → domain. This means that the core domain logic — account lifecycle, token semantics, and business invariants — has zero knowledge of Spring, JPA, HTTP, or any external SDK. Downstream concerns (persistence, email, OAuth2, REST controllers) are adapters that implement interfaces defined by the domain. The dependency rule is not a convention enforced by code review; it is mechanically verified by an ArchUnit test that runs in every CI build and breaks the pipeline if violated.
Layer Overview
The codebase is organized undercom.auth_service.auth into four top-level packages:
domain/ — the core
The domain layer contains the Account aggregate root, value objects (Email, HashedPassword, AccountId), the AccountStatus and Role enumerations, domain events (AccountRegistered, AccountVerified, TokenFamilyRevoked, …), domain exceptions, and port interfaces (e.g. AccountRepository, RefreshTokenRepository, EmailSender). This layer imports nothing from Spring, JPA, or any external SDK — it is plain Java 21.
application/ — use cases
Each PRD capability maps to a single use case class in application/usecase/ (e.g. RegisterAccountUseCase, LoginUseCase, RefreshTokenUseCase, FederatedLoginUseCase). Use cases are the only layer annotated with @Transactional, and the only layer that orchestrates domain objects through port interfaces. Controllers hold no business logic; domain objects hold no transaction management.
infrastructure/ — adapters
Infrastructure adapters implement the domain port interfaces:
adapters/postgresql/— JPA entities and Spring Data repositories; maps between JPA entities and domain models.adapters/email/—SmtpEmailSender(prod, behind a Resilience4j circuit breaker) andLoggingEmailSender(dev/MailHog).adapters/oauth/—OAuth2AuthenticationSuccessHandler,OAuth2AuthenticationFailureHandler,CookieOAuth2AuthorizationRequestRepository, andGitHubOAuth2UserService. All delegate intoFederatedLoginUseCase.adapters/security/—JwtAuthenticationFilterintercepts every request, validates the JWT, and populates theSecurityContext.adapters/messaging/— transactional outbox poller and Kafka event publisher (staged for Epic 6).controller/—AuthController(public auth endpoints) andUserController(protected profile endpoint), each delegating entirely to use cases.
Layer Dependency Diagram
Theconfig/ package sits outside this graph — it wires adapters to use cases through dependency injection and is the only place where Spring beans are composed across layers.
Architectural Decision Records
AD-2 — Single token issuance point (TokenIssuer)
All AccessToken + RefreshToken pairs — whether produced by credential login, refresh rotation, or OAuth2 federation — are emitted exclusively by TokenIssuer in application/usecase. No adapter or controller constructs JWTs directly. This guarantees that the credential login and the OAuth2 success handler can never diverge in claims format, TTL, or signing algorithm.
AD-3 — JWT HS256, fixed claim set, 15-minute TTL
This is a security contract that downstream services depend on. Any change to the claim set or signing algorithm is a breaking change for all JWT consumers.
| Claim | Value |
|---|---|
sub | Account UUID (stable identifier for downstream authorization) |
email | Verified email address |
roles | JSON array of role strings, e.g. ["USER"] |
iat | Issued-at timestamp (Unix seconds) |
exp | Expiry timestamp (Unix seconds); TTL = 15 minutes |
iss | "auth-service" |
AD-4 — Opaque refresh tokens, SHA-256 hash storage, family-based rotation with reuse detection
Presenting a refresh token that has already been used triggers revocation of the entire token family — all sessions for the affected account are invalidated. This is the intended defense against token theft.
family_id— groups all tokens in a rotation chain togetherexpires_at— 7-day TTL from issuanceused_at— set when the token is exchanged for a new pairrevoked_at— set when the family is invalidated
POST /auth/refresh:
- The presented token hash is looked up in the database.
- If
used_atorrevoked_atis non-null → entire family is revoked; client must log in again. - If valid →
used_atis stamped, a new token is issued in the same family, and the new pair is returned.
AD-5 — Credential and one-time token hardening
- Passwords are stored as BCrypt hashes via Spring Security’s
DelegatingPasswordEncoder. Plaintext passwords are never logged. - Verification and password-reset tokens are random values sent by email; only their SHA-256 hash is stored in the database. Each token is single-use (stamped with
consumed_aton redemption) with a TTL of 24 hours (email verification) or 1 hour (password reset).
AD-8 — RFC 9457 Problem Details for all errors
There are two distinct code paths that produce error responses. Both produce identical
application/problem+json shapes so that API consumers can handle them uniformly.application/problem+json (RFC 9457 / formerly RFC 7807). There are two response paths:
- MVC layer — a
@RestControllerAdvice(GlobalExceptionHandler) handles domain and application exceptions and buildsProblemDetailobjects. - Security filter layer —
JwtAuthenticationFilterfailures (missing or invalid JWT) never reach the MVC dispatcher.SecurityConfigregisters a customAuthenticationEntryPointandAccessDeniedHandlerthat writeapplication/problem+jsondirectly to the servlet response using the sameProblemDetailstructure.
AD-11 — Deny-all security; explicit public allowlist
Every new endpoint is protected by default. Adding a route to the public allowlist in
SecurityConfig is a deliberate, reviewable decision — not the default.SecurityConfig terminates the SecurityFilterChain with anyRequest().authenticated(). The explicit public allowlist is:
SessionCreationPolicy.STATELESS — no server-side session is ever created. CSRF protection is disabled because the API uses no session cookies. The OAuth2 authorization request state is stored in a short-lived cookie (CookieOAuth2AuthorizationRequestRepository) rather than the HTTP session to remain compatible with stateless operation.
Actuator endpoints (/actuator/**) are never in the public allowlist and must be exposed on a separate management port or behind infrastructure-level authentication.
AD-12 — ArchUnit enforces layer dependencies in CI
The dependency rule (infrastructure → application → domain) is verified by an ArchitectureRulesTest using ArchUnit 1.4.2 on every build. The test fails — and therefore blocks the CI pipeline — if:
- Any type in
domain/imports fromapplication/orinfrastructure/. - Any type in
application/imports frominfrastructure/. - Any cyclic dependency between packages is detected.
Database Schema Overview
The following entity-relationship diagram shows the core tables managed by Flyway: Hibernate is configured withspring.jpa.hibernate.ddl-auto=validate across all profiles — it validates the schema against the entity mappings but never generates DDL. All schema changes are made through versioned Flyway migrations (db/migration/V<n>__<description>.sql).
System Context
The following diagram shows how Auth Service fits within a broader system: Downstream services validate JWTs locally using the sharedJWT_SECRET_CURRENT. They never call Auth Service for token introspection — the stateless JWT design means verification is a local operation at the cost of a 15-minute revocation lag.
Capability to Architecture Mapping
| Capability | Use Case | Governing ADs |
|---|---|---|
| Registration + email verification | RegisterAccountUseCase, VerifyAccountUseCase | AD-5, AD-6, AD-8, AD-9, AD-13, AD-14 |
| Credential login | LoginUseCase → TokenIssuer | AD-2, AD-3, AD-8, AD-13 |
| Refresh token rotation | RefreshTokenUseCase | AD-4, AD-13 |
| Logout | LogoutUseCase | AD-4, AD-13 |
| OAuth2 social login | adapters/oauth → FederatedLoginUseCase → TokenIssuer | AD-2, AD-6, AD-13, AD-17 |
| Password reset | RequestPasswordResetUseCase, ResetPasswordUseCase | AD-4, AD-5, AD-8, AD-9, AD-13 |
| Own profile | UserController → GetOwnProfileUseCase | AD-3, AD-11, AD-18 |
| Admin account management | AdminController → ManageAccountUseCase | AD-4, AD-6, AD-11, AD-18, AD-20 |
| Layer rule enforcement | ArchitectureRulesTest | AD-12, AD-21 |
Deferred Decisions
The following decisions are intentionally deferred and documented rather than left implicit:- RS256 + JWKS endpoint — HS256 with dual-secret rotation is sufficient for the current single-consumer model. RS256 with a public JWKS endpoint will be revisited when multiple independent consumer teams exist.
- Token introspection endpoint — Not implemented; no consumer has requested it.
- Concrete secret store (Vault / AWS Secrets Manager / Kubernetes Secrets) — AD-19 defines the contract (active + previous key pair injected from outside the process), not the tool.
- Rate limiting and brute-force lockout — Story 3.2; currently no rate limiting on
/auth/loginor/auth/forgot-password. - Transactional outbox publishing — Redpanda is already started by
docker-compose.yml; active consumption begins in Epic 6.