IAM Service Architecture
The IAM service (iam-service) is a Spring Boot 3.2 application running on Java 21 that serves as the centralized identity provider for the MATIH platform. It manages user identities, authentication flows, authorization policies, and session state for every tenant in the system.
6.1High-Level Architecture
The IAM service follows a layered architecture pattern with clear separation of concerns:
+---------------------+
| API Gateway |
| (Port 8080) |
+----------+----------+
|
+----------v----------+
| IAM Service |
| (Port 8081) |
| |
| +------------------+ |
| | REST Controllers | |
| | (16 controllers) | |
| +--------+---------+ |
| | |
| +--------v---------+ |
| | Business Services| |
| | (23 services) | |
| +--------+---------+ |
| | |
| +--------v---------+ |
| | Security Layer | |
| | JWT / Filters | |
| +--------+---------+ |
| | |
| +--------v---------+ |
| | JPA Repositories | |
| | (25 repos) | |
| +--------+---------+ |
+----------+-----------+
|
+----------------+----------------+
| |
+---------v---------+ +----------v----------+
| PostgreSQL | | Redis |
| (users, roles, | | (sessions, |
| tokens, keys) | | permission cache) |
+-------------------+ +---------------------+Service Overview
| Property | Value |
|---|---|
| Artifact | com.matih:iam-service |
| Framework | Spring Boot 3.2 |
| Language | Java 21 |
| Port | 8081 |
| Database | PostgreSQL via Spring Data JPA |
| Cache | Redis via Spring Cache |
| Security | Spring Security 6 with custom filter chain |
| Token Library | JJWT 0.12.3 |
| Policy Engine | Open Policy Agent (OPA) |
| API Docs | SpringDoc OpenAPI 3.0 |
Package Structure
The service is organized into clearly separated packages:
com.matih.iam/
config/ # Spring configuration classes
controller/ # REST API controllers (16 controllers)
dto/
request/ # Inbound request DTOs (26 DTOs)
response/ # Outbound response DTOs (20 DTOs)
entity/ # JPA entity models (25 entities)
exception/ # Custom exception types and global handler
keycloak/ # Keycloak SSO integration
mapper/ # Entity-to-DTO mappers
opa/ # Open Policy Agent client
pbac/ # Policy-based access control
ratelimit/ # Rate limiting configuration
repository/ # Spring Data JPA repositories (25 repos)
scim/ # SCIM 2.0 protocol implementation
secrets/ # Secrets management integration
security/ # JWT provider, filters, and auth logic
service/ # Business logic services (23 services)
sms/ # SMS provider abstraction (Twilio)
sso/ # SSO provider configuration (OAuth, SAML)
zerotrust/ # Zero-trust continuous verification6.2Domain Model
Core Entities
| Entity | Table | Description |
|---|---|---|
User | users | Core user identity with email, password hash, tenant association, and status |
Role | roles | Named role with a set of permissions, supports parent-child inheritance |
UserRole | user_roles | Many-to-many mapping between users and roles |
Permission | permissions | Fine-grained permission in resource:action format |
PasswordPolicy | password_policies | Per-tenant password complexity, rotation, and history rules |
PasswordHistory | password_history | Previous password hashes to prevent reuse |
Authentication Entities
| Entity | Table | Description |
|---|---|---|
EmailVerificationToken | email_verification_tokens | Time-limited token for email verification |
RefreshToken | refresh_tokens | Opaque refresh token with token family for rotation detection |
LoginHistory | login_history | Audit trail of login attempts with IP, user agent, and outcome |
UserDevice | user_devices | Registered devices with fingerprint, OS, browser, and trust status |
UserSession | user_sessions | Active session records with creation time and token binding |
MFA Entities
| Entity | Table | Description |
|---|---|---|
UserMfaCredential | user_mfa_credentials | TOTP secret, SMS phone number, or email MFA per user |
MfaChallenge | mfa_challenges | Pending MFA challenge with expiration and attempt counter |
MfaRecoveryToken | mfa_recovery_tokens | Recovery tokens for MFA bypass |
UserBackupCode | user_backup_codes | One-time backup codes generated during enrollment |
MfaPolicy | mfa_policies | Tenant-level MFA enforcement rules |
MfaBypassHistory | mfa_bypass_history | Audit trail of MFA bypass events |
OAuth2 Entities
| Entity | Table | Description |
|---|---|---|
OAuth2Client | oauth2_clients | Registered OAuth2 client applications |
OAuth2AuthorizationCode | oauth2_authorization_codes | Short-lived authorization codes |
OAuth2AccessToken | oauth2_access_tokens | Issued access tokens with scope and revocation status |
Administrative Entities
| Entity | Table | Description |
|---|---|---|
ImpersonationSession | impersonation_sessions | Admin impersonation sessions with audit trail |
ApiKey | api_keys | Long-lived API keys with scoped permissions |
AccessRequest | access_requests | Self-service access request workflow |
AccessRequestAuditLog | access_request_audit_logs | Audit trail for access request decisions |
User Entity Detail
The User entity is the central domain object supporting multi-tenancy and social login:
@Entity
@Table(name = "users")
@SQLRestriction("deleted = false")
public class User {
private Long id;
private UUID tenantId;
private String email;
private String passwordHash;
private String firstName;
private String lastName;
private String displayName;
private String phoneNumber;
private boolean enabled;
private boolean locked;
private boolean emailVerified;
private boolean mfaEnabled;
private int failedLoginAttempts;
private Instant lockedUntil;
private Instant lastLoginAt;
private String lastLoginIp;
private String authProvider; // password, google, microsoft, github
private String googleSubjectId;
private String microsoftSubjectId;
private String githubSubjectId;
private Set<Role> roles;
private Set<UUID> groupIds; // JSONB for MFA policy targeting
private boolean deleted;
private Instant deletedAt;
}Key design decisions:
- Soft delete via
@SQLRestriction("deleted = false")-- deleted users are filtered at the JPA level - Optimistic locking via
@Versionto prevent concurrent update conflicts - JPA Auditing via
@CreatedDate,@LastModifiedDate,@CreatedBy,@LastModifiedBy - Unique constraint on
(tenant_id, email)for multi-tenant email uniqueness - Social login support via dedicated subject ID columns per OAuth provider
Entity Relationship Diagram
User ----< UserRole >---- Role ----< RolePermission >---- Permission
| |
|----< UserSession |----< ChildRole (self-referencing)
|----< UserDevice
|----< UserMfaCredential
|----< UserBackupCode
|----< RefreshToken
|----< ApiKey
|----< LoginHistory
|----< PasswordHistory
|----< AccessRequest
|----< ImpersonationSession (as admin or target)
|----< EmailVerificationToken6.3Security Architecture
Filter Chain
Request
|
v
[CORS Filter]
|
v
[Rate Limiting Filter]
|
v
[API Key Authentication Filter] -- For service-to-service calls
|
v
[JWT Authentication Filter] -- For user-facing calls
|
v
[Scope Authorization Aspect] -- @RequiresScope annotation check
|
v
[Controller Method]JWT Token Provider
The JwtTokenProvider uses JJWT 0.12.3 with HS256 signing and generates four distinct token types:
@Component
public class JwtTokenProvider {
public String generateAccessToken(User user) {
Map<String, Object> claims = new HashMap<>();
claims.put("user_id", user.getId());
claims.put("tenant_id", user.getTenantId());
claims.put("roles", new ArrayList<>(user.getAuthorities()));
return Jwts.builder()
.claims(claims)
.subject(user.getEmail())
.issuer(securityProperties.getJwt().getIssuer())
.id(UUID.randomUUID().toString())
.issuedAt(new Date())
.expiration(expiryDate)
.signWith(secretKey, Jwts.SIG.HS256)
.compact();
}
}Token Types
| Token Type | Use Case | Subject | Key Claims |
|---|---|---|---|
| Access Token | User authentication | User email | user_id, tenant_id, roles |
| Refresh Token | Token renewal | User email | type: "refresh" |
| OAuth2 Access Token | OAuth2 flows | User email or client ID | client_id, scope, tenant_id |
| Impersonation Token | Admin troubleshooting | Target user email | impersonation: true, impersonator_id, impersonation_session_id |
JWT Claims Structure
| Claim | Type | Description |
|---|---|---|
sub | String | User email address |
user_id | Long | Internal user ID |
tenant_id | UUID | Tenant identifier |
roles | List<String> | Authorities (ROLE_ADMIN, users:read, etc.) |
iss | String | Token issuer |
aud | String | Token audience |
jti | String | Unique token ID (UUID) |
iat | Long | Issued at timestamp |
exp | Long | Expiration timestamp |
6.4Multi-Tenancy
Tenant isolation is enforced at every layer:
Controller Layer: Most endpoints require an X-Tenant-ID header:
public ResponseEntity<UserResponse> createUser(
@RequestHeader("X-Tenant-ID") UUID tenantId,
@Valid @RequestBody CreateUserRequest request) {
UserResponse response = userService.createUser(tenantId, request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}Repository Layer: JPA queries filter by tenant_id:
Optional<User> findByTenantIdAndEmail(UUID tenantId, String email);
Page<User> findByTenantId(UUID tenantId, Pageable pageable);Token Layer: The tenant_id claim in JWT tokens ensures downstream services verify tenant context without additional database lookups.
6.5Integration Points
Inbound Dependencies
| Consumer | Protocol | Purpose |
|---|---|---|
| API Gateway | HTTP | Token validation, routing |
| Tenant Service | HTTP | User provisioning |
| Frontend applications | HTTP | Login, registration, MFA |
| Data plane services | JWT | Token validation via shared library |
Outbound Dependencies
| Dependency | Protocol | Purpose |
|---|---|---|
| PostgreSQL | JDBC | Persistent storage |
| Redis | Redis | Session and permission cache |
| Keycloak | OIDC/HTTP | SSO federation |
| OPA | HTTP | Policy evaluation |
| Notification Service | HTTP | Email verification, alerts |
| Twilio | HTTPS | SMS MFA delivery |
6.6Error Handling
The GlobalExceptionHandler maps exceptions to structured error responses:
| Exception | HTTP Status | Error Code |
|---|---|---|
AuthenticationException | 401 | AUTHENTICATION_FAILED |
TokenException | 401 | TOKEN_INVALID |
ResourceNotFoundException | 404 | RESOURCE_NOT_FOUND |
BusinessException | 400 | BUSINESS_RULE_VIOLATION |
DuplicateResourceException | 409 | RESOURCE_DUPLICATE |
{
"timestamp": "2026-02-12T10:30:00Z",
"status": 401,
"code": "AUTHENTICATION_FAILED",
"message": "Invalid credentials",
"path": "/api/v1/auth/login"
}6.7Configuration
matih:
security:
jwt:
secret-key: ${JWT_SECRET_KEY}
access-token-expiration: 900000 # 15 minutes
refresh-token-expiration: 604800000 # 7 days
issuer: "matih-platform"
audience: "matih-api"
lockout:
max-attempts: 5
lockout-duration-minutes: 30
mfa:
totp-issuer: "MATIH Platform"
challenge-expiration: 300
backup-code-count: 106.8Health and Observability
| Endpoint | Purpose |
|---|---|
GET /health | Liveness probe |
GET /health/ready | Readiness probe |
GET /actuator/prometheus | Prometheus metrics |
Key metrics: iam_login_total, iam_mfa_challenges_total, iam_token_issued_total, iam_active_sessions, iam_permission_cache_hit_ratio.
Next Steps
- Authentication -- login, registration, and token flows
- Multi-Factor Authentication -- TOTP, SMS, email MFA
- Roles and Permissions -- RBAC model and OPA