Centralized authentication service for the API Gateway microservices platform. Provides a provider-based, mechanism-agnostic authentication service that validates credentials and returns verified identity claims.
The Authentication Platform is the centralized identity service for the microservices ecosystem. It authenticates users and API clients, validates credentials on behalf of the API Gateway, manages sessions and refresh tokens, issues JWTs, and provides a unified authentication contract for downstream services. The platform owns authentication concerns while remaining independent of authorization logic in downstream services.
- Login - Authenticate users with username/email and password, issue JWT access tokens, refresh tokens, and session cookies
- Validation - Validate any authentication mechanism (JWT, API Key, Session) forwarded by the API Gateway
- Token Refresh - Rotate refresh tokens with one-time-use semantics
- Session Management - Maintain and revoke Redis-backed user sessions
- API Key Management - Create, list, and revoke API keys for users and API clients
- Observability - Correlation IDs, structured logging, request timing metrics, health probes
- Audit Logging - Authentication events logged for security analysis
The following are intentionally handled by other services or are not yet implemented:
- Service-to-service authentication (mTLS between Gateway and Auth Platform)
- OAuth2 / OpenID Connect flows
- SAML assertion processing
- Passkeys / WebAuthn
- User registration, password reset, or account management
- Kubernetes manifests or deployment runbooks
| Feature | Description |
|---|---|
| Provider Architecture | Pluggable AuthenticationProvider interface with configuration-driven registry |
| JWT Authentication | HMAC-SHA256 signed tokens with configurable key rotation |
| API Key Authentication | Hash-based key lookup, XOR ownership (user or client), lifecycle management |
| Session Authentication | Redis-backed sessions with TTL-based expiration and renewal |
| Login | BCrypt password verification, account status checks, audit logging |
| Refresh Token Rotation | One-time-use tokens with revoke-on-rotate pattern |
| Logout | Single session and bulk session revocation |
| Database | PostgreSQL with Liquibase migrations, JPA entities, seed data |
| Observability | Correlation IDs, JSON logging, Micrometer metrics, health probes |
| Gateway Contract | POST /internal/v1/auth/validate for request-time validation |
graph LR
Client --> API-Gateway
API-Gateway --> AuthPlatform
AuthPlatform --> PostgreSQL
AuthPlatform --> Redis
API-Gateway --> Upstream["Microservices"]
subgraph Authentication Platform
Validation
Login
Refresh
Logout
ApiKeys
end
sequenceDiagram
participant C as Client
participant G as API Gateway
participant AP as Auth Platform
participant DB as PostgreSQL
participant R as Redis
C->>G: POST /api/login {username, password}
G->>AP: POST /internal/v1/auth/login
AP->>DB: Lookup user
AP->>DB: Verify BCrypt hash
AP->>DB: Create refresh token
AP->>AP: Create JWT
AP->>R: Create session (optional)
AP-->>G: {accessToken, refreshToken, sessionToken, claims}
G-->>C: {accessToken, refreshToken, sessionToken, claims}
C->>G: API request + Authorization: Bearer <JWT>
G->>AP: POST /internal/v1/auth/validate {headers}
AP->>AP: Select JWT provider
AP->>AP: Verify HMAC signature
AP-->>G: {authenticated: true, claims}
G->>Upstream: Forward request with identity headers
C->>G: POST /api/refresh {refreshToken}
G->>AP: POST /internal/v1/auth/refresh
AP->>DB: Validate token hash
AP->>DB: Revoke old token
AP->>DB: Create new token
AP->>AP: Create new JWT
AP-->>G: {accessToken, newRefreshToken}
G-->>C: {accessToken, newRefreshToken}
sequenceDiagram
participant G as API Gateway
participant C as Controller
participant S as AuthValidationService
participant R as ProviderRegistry
participant P as AuthenticationProvider
G->>C: POST /internal/v1/auth/validate
C->>C: @Valid + extract correlationId/clientIp/userAgent
C->>S: validate(request, correlationId, clientIp, userAgent)
S->>S: Build AuthenticationContext
S->>R: selectProvider(context)
R->>R: Filter enabled providers
R->>R: Find first matching supports()
R-->>S: AuthenticationProvider
S->>P: authenticate(context)
P-->>S: AuthenticationResult
S->>S: Map to AuthValidationResponse
S-->>C: Mono<AuthValidationResponse>
C-->>G: ResponseEntity.ok(response)
src/main/java/auth/
├── AuthApplication.java # Spring Boot entry point
├── config/
│ ├── AuthProperties.java # @ConfigurationProperties
│ ├── JpaConfig.java # JPA auditing configuration
│ ├── RedisConfig.java # Conditional ReactiveRedisTemplate
│ └── SecurityConfig.java # WebFlux security rules
├── controller/
│ ├── AuthValidationController.java # POST /validate
│ ├── LoginController.java # POST /login
│ ├── RefreshController.java # POST /refresh
│ ├── LogoutController.java # POST /logout, /logout-all
│ └── ApiKeyManagementController.java # POST/GET/DELETE /api-keys
├── dto/
│ ├── request/
│ │ ├── AuthLoginRequest.java
│ │ ├── AuthValidationRequest.java
│ │ ├── CreateApiKeyRequest.java
│ │ └── RefreshRequest.java
│ └── response/
│ ├── ApiKeyEntry.java
│ ├── AuthLoginResponse.java
│ ├── AuthValidationResponse.java
│ ├── CreateApiKeyResponse.java
│ └── RefreshResponse.java
├── exception/
│ ├── AuthException.java
│ ├── ErrorCode.java # AUTH-001 through AUTH-015
│ ├── ErrorResponse.java
│ └── GlobalExceptionHandler.java
├── jwt/
│ └── JwtTokenService.java # JWT creation & validation
├── model/
│ ├── AuthenticationClaims.java # Identity claims record
│ ├── AuthenticationContext.java # Internal auth context
│ └── AuthenticationHeaders.java # 13 forwarded header fields
├── observability/
│ ├── CorrelationIdFilter.java # Correlation ID propagation
│ └── RequestTimingFilter.java # Micrometer request timing
├── persistence/
│ ├── entity/
│ │ ├── ApiClient.java
│ │ ├── ApiKey.java
│ │ ├── AuditLog.java
│ │ ├── Permission.java
│ │ ├── RefreshToken.java
│ │ ├── Role.java
│ │ └── User.java
│ └── repository/
│ ├── ApiClientRepository.java
│ ├── ApiKeyRepository.java
│ ├── AuditLogRepository.java
│ ├── PermissionRepository.java
│ ├── RefreshTokenRepository.java
│ ├── RoleRepository.java
│ └── UserRepository.java
├── provider/
│ ├── AuthenticationProvider.java # Provider interface
│ ├── AuthenticationProviderRegistry.java
│ ├── AuthenticationResult.java
│ └── impl/
│ ├── ApiKeyAuthenticationProvider.java
│ ├── JwtAuthenticationProvider.java
│ ├── MockAuthenticationProvider.java
│ └── SessionAuthenticationProvider.java
├── service/
│ ├── ApiKeyManagementService.java
│ ├── AuthValidationService.java
│ ├── AuthenticationService.java # Login orchestration
│ ├── RefreshTokenService.java
│ └── SessionService.java
├── session/
│ └── SessionData.java # Session data record
└── util/
└── CryptoUtils.java # SHA-256 + random token generation
| Layer | Technology |
|---|---|
| Language | Java 26 |
| Framework | Spring Boot 4.1 |
| Reactive | Spring WebFlux (Netty) |
| Security | Spring Security WebFlux |
| JWT | Nimbus JOSE + JWT (HMAC-SHA256) |
| Persistence | Spring Data JPA + Hibernate |
| Database | PostgreSQL 16+ |
| ORM | Spring Data JPA + Hibernate |
| Migrations | Liquibase (XML changelogs) |
| Cache | Redis 7+ |
| Logging | Log4j2 with JSON template layout |
| Metrics | Micrometer |
| Build | Maven 3.9+ |
| Validation | Jakarta Bean Validation |
| Testing | JUnit 5, Mockito, AssertJ, Reactor Test |
| Property | Default | Description |
|---|---|---|
auth.providers.mock.enabled |
true |
Enable mock provider (development only) |
auth.providers.mock.authenticated-by-default |
true |
Mock always authenticates |
auth.providers.mock.default-subject |
mock-user |
Default mock subject |
auth.providers.jwt.enabled |
true |
Enable JWT Bearer token provider |
auth.providers.api-key.enabled |
true |
Enable API key provider |
auth.providers.session.enabled |
true |
Enable session cookie provider |
| Property | Default | Description |
|---|---|---|
auth.jwt.issuer |
auth-platform |
JWT issuer claim |
auth.jwt.audience |
api-gateway |
JWT audience claim |
auth.jwt.access-token-expiration-seconds |
3600 |
Access token TTL (1 hour) |
auth.jwt.keys[].id |
- | Signing key identifier |
auth.jwt.keys[].secret |
- | Base64-encoded HMAC secret |
auth.jwt.keys[].active |
true |
Whether this key is used for signing |
| Property | Default | Description |
|---|---|---|
auth.refresh-token.expiration-seconds |
604800 |
Refresh token TTL (7 days) |
auth.refresh-token.length-bytes |
32 |
Random token length (256-bit) |
| Property | Default | Description |
|---|---|---|
auth.session.expiration-seconds |
86400 |
Session TTL (24 hours) |
auth.session.token-length-bytes |
32 |
Session token length |
| Property | Default | Description |
|---|---|---|
auth.correlation-id.header-name |
X-Correlation-ID |
Header name for correlation ID |
auth.correlation-id.generate-if-missing |
true |
Auto-generate if not provided |
| Property | Default | Description |
|---|---|---|
spring.datasource.url |
jdbc:postgresql://localhost:5432/auth?currentSchema=auth_platform |
JDBC URL |
spring.datasource.username |
abhilash |
Database user |
spring.datasource.password |
(empty) | Database password |
| Property | Default | Description |
|---|---|---|
spring.redis.host |
localhost |
Redis host |
spring.redis.port |
6379 |
Redis port |
spring.redis.password |
(empty) | Redis password |
| Property | Default | Description |
|---|---|---|
management.endpoints.web.exposure.include |
health,info,metrics |
Exposed endpoints |
management.endpoint.health.probes.enabled |
true |
Kubernetes probe support |
- Java 26 (Temurin recommended)
- PostgreSQL 16+ - running on localhost:5432 with database
authand schemaauth_platform - Redis 7+ - running on localhost:6379 (optional for development without sessions)
CREATE DATABASE auth;
\c auth;
CREATE SCHEMA auth_platform;mvn clean verifyAll tests are unit tests and do not require PostgreSQL or Redis.
mvn spring-boot:runStarts on port 8004 with all providers enabled. Mock provider is the lowest-priority - real providers (JWT, API Key, Session) will match first if their credentials are present.
# Health check
curl http://localhost:8004/actuator/health
# Validate with mock provider
curl -X POST http://localhost:8004/internal/v1/auth/validate \
-H 'Content-Type: application/json' \
-d '{"requestId": "test-1", "headers": {}}'
# Login (requires PostgreSQL with seed data)
curl -X POST http://localhost:8004/internal/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username": "admin", "password": "dev-password"}'docker build -t auth-platform .docker run -p 8004:8004 \
-e DATABASE_URL=jdbc:postgresql://host.docker.internal:5432/auth?currentSchema=auth_platform \
-e DATABASE_USERNAME=user \
-e DATABASE_PASSWORD=password \
-e REDIS_HOST=host.docker.internal \
auth-platformdocker compose up --buildDocker Compose starts only the Authentication Platform. PostgreSQL and Redis must be available externally (see Running Locally for connection details).
When the Authentication Platform runs inside Docker, PostgreSQL and Redis must be reachable from within the container.
| Dependency | Host Example | Docker Example |
|---|---|---|
| PostgreSQL | localhost | host.docker.internal |
| Redis | localhost | host.docker.internal |
When all services run under Docker Compose, use service names instead.
public interface AuthenticationProvider {
boolean supports(AuthenticationContext context);
Mono<AuthenticationResult> authenticate(AuthenticationContext context);
default String getProviderId() { ... }
}supports()- Determines if this provider can handle the given contextauthenticate()- Performs authentication and returns a result with claims
| Provider | Enabled By Default | Trigger | Backend |
|---|---|---|---|
| Mock | Yes (development) | Always | None (in-memory config) |
| JWT | No | Authorization: Bearer <token> |
HMAC-SHA256 verification |
| API Key | No | apiKey header |
PostgreSQL hash lookup |
| Session | No | Cookie: session=<token> |
Redis lookup + renewal |
The AuthenticationProviderRegistry collects all provider beans, filters by enabled configuration, and selects the first provider where supports() returns true. Provider ordering follows Spring bean registration order.
- Implement
AuthenticationProvider - Annotate with
@Component - Add configuration to
application.ymlunderauth.providers.{id} - Register the
{id}mapping inAuthenticationProviderRegistry.isProviderEnabled()
No orchestration code changes in services or controllers are required.
- Format: Signed JWT (HMAC-SHA256) with standard and custom claims
- Expiration: Configurable (default 1 hour)
- Validation: Signature verification, expiration check, issuer/audience validation
- Key Rotation: Multiple keys supported with active/inactive flag - zero-downtime rotation
- Format: Opaque random token (256-bit), SHA-256 hash stored in database
- Expiration: Configurable (default 7 days)
- Rotation: One-time-use - old token revoked, new token issued on each refresh
- Theft Detection: If a token is used by an attacker, the legitimate client's next refresh will fail
- Format: Opaque token prefixed with
sess_, stored in Redis - Expiration: Configurable TTL (default 24 hours), refreshed on each validation
- Revocation: Single session or all user sessions
- Storage: Redis with key
session:<token>→ serializedSessionData - User Index:
user:sessions:<userId>→ set of active session IDs - TTL: Configurable, reset on each validated request
- Serialization: Pipe-delimited with escape/unescape handling
- Revocation: Single (
POST /logout) or bulk (POST /logout-all)
| Endpoint | Method | Description |
|---|---|---|
POST /internal/v1/api-keys |
Create | Creates a new API key (raw key returned once) |
GET /internal/v1/api-keys?userId=X |
List by user | Lists keys for a user |
GET /internal/v1/api-keys?clientId=X |
List by client | Lists keys for an API client |
DELETE /internal/v1/api-keys/{id} |
Revoke | Disables a key |
- Format:
ak_<64-hex-chars>(prefix + 32 random bytes) - Storage: SHA-256 hash only - raw key is irrecoverable after creation
- Ownership: XOR - belongs to exactly one User or ApiClient
- Lifecycle: Created → Used → Expired or Revoked
| Endpoint | Method | Description |
|---|---|---|
POST /internal/v1/auth/refresh |
Rotate | Validates, revokes old, and issues new refresh token + JWT |
- Storage: SHA-256 hash in PostgreSQL (
refresh_tokenstable) - Rotation: Each use revokes the presented token and creates a new one
- Error Codes: AUTH-011 (not found), AUTH-012 (revoked), AUTH-013 (expired)
The CorrelationIdFilter extracts or generates a correlation ID on every request. It is propagated via:
- Exchange attributes (accessible throughout the request lifecycle)
- Response headers (
X-Correlation-ID) - MDC (for log correlation)
Log4j2 with JSON template layout. Each log event includes:
level,timestamp(ISO-8601),logger,messagecorrelationId,traceId,spanIdthread,exception(if applicable)
Micrometer metrics are exposed via Actuator:
| Metric | Type | Description |
|---|---|---|
auth.request.duration |
Timer | Request duration by path, method, status |
auth.jwt.created |
Counter | JWT tokens created |
auth.jwt.validation.success |
Counter | Successful JWT validations |
auth.jwt.validation.failure |
Counter | Failed JWT validations (tagged by reason) |
auth.api-key.authentication.success |
Counter | Successful API key auth |
auth.api-key.authentication.failure |
Counter | Failed API key auth (tagged by reason) |
auth.session.authentication.success |
Counter | Successful session auth |
auth.session.authentication.failure |
Counter | Failed session auth (tagged by reason) |
| Endpoint | Probes | Description |
|---|---|---|
GET /actuator/health |
- | Overall health |
GET /actuator/health/liveness |
Liveness | Is the app alive? |
GET /actuator/health/readiness |
Readiness | Is the app ready for traffic? |
GET /actuator/info |
- | Application info |
| Entity | Table | Purpose |
|---|---|---|
User |
users |
Authenticated user accounts |
Role |
roles |
Named role definitions |
Permission |
permissions |
Granular permission actions |
| - | user_roles |
User-to-role assignment (join) |
| - | role_permissions |
Role-to-permission grants (join) |
ApiClient |
api_clients |
Registered API client apps |
ApiKey |
api_keys |
Hashed API key credentials |
RefreshToken |
refresh_tokens |
Hashed refresh token credentials |
AuditLog |
audit_logs |
Append-only audit trail |
All schema changes are managed by Liquibase in src/main/resources/db/changelog/. The master changelog references 11 v1 migrations creating tables, indexes, constraints, and seed data.
| Resource | Username | Password |
|---|---|---|
| Admin User | admin |
dev-password |
| Dev User | devuser |
dev-password |
Seed data is for development only and should not be applied in production.
A complete Postman Collection covering all endpoints is available at:
docs/postman/Auth Platform.postman_collection.json
Import it into Postman and set the baseUrl variable to your Auth Platform host. The collection includes:
- Login, Validate, Refresh, Logout, Logout All
- API Key creation, listing, and revocation
- Actuator health, info, and metrics endpoints
Detailed architecture decisions and design rationale are documented in docs/architecture/:
| Document | Topic |
|---|---|
| ADR 0001 | Architecture decision records |
| ADR 0002 | Pluggable provider architecture |
| ADR 0003 | JWT token service |
| ADR 0004 | Refresh token rotation |
| ADR 0005 | API key management |
| ADR 0006 | Session management |
| ADR 0007 | Persistence model |
| ADR 0008 | Authentication flow |
| Path | Access |
|---|---|
GET /actuator/health/** |
Public |
GET /actuator/info |
Public |
/internal/** |
Gateway internal (no service-to-service auth yet) |
/fallback/** |
Public |
/actuator/** |
ADMIN role |
CSRF is disabled globally (stateless service, no browser sessions at this level).
See CONTRIBUTING.md for development setup, coding standards, and pull request process.
Licensed under the Apache License 2.0. See LICENSE.