IAM Service Internal Architecture
The IAM service is the security foundation of the MATIH platform. Every authenticated request that flows through the system was authorized by a JWT token issued by this service. This page documents the internal architecture, domain model, token lifecycle, and security patterns implemented within the IAM service.
2.3.B.1Internal Architecture
The IAM service follows the standard layered architecture enforced by commons-java, with additional security-specific layers:
Security Filter Chain
Every request passes through an ordered filter chain before reaching any controller:
// Filter execution order (Spring Security filter chain)
1. SecurityFilter (HIGHEST_PRECEDENCE + 10)
- Validates request headers for injection attacks
- Checks for path traversal and null bytes
- Rejects oversized payloads
2. JwtAuthenticationFilter (HIGHEST_PRECEDENCE + 20)
- Extracts Bearer token from Authorization header
- Validates JWT signature, expiry, issuer
- Extracts userId, tenantId, roles from claims
- Sets SecurityContextHolder
3. TenantContextFilter (HIGHEST_PRECEDENCE + 30)
- Reads X-Tenant-ID header (set by gateway)
- Calls TenantContextHolder.setTenantId()
- Clears context in finally block
4. RbacFilter (HIGHEST_PRECEDENCE + 40)
- Reads required permissions from @RequirePermission annotation
- Evaluates user roles against required permissions
- Returns 403 if insufficient permissions2.3.B.2Domain Model
User Entity
@Entity
@Table(name = "users")
public class User {
@Id
private UUID id;
private String email; // Unique, indexed
private String passwordHash; // BCrypt, 12 rounds
private String firstName;
private String lastName;
private String tenantId; // Primary tenant
private UserStatus status; // ACTIVE, SUSPENDED, LOCKED, PENDING
private boolean mfaEnabled;
private String mfaSecret; // TOTP secret (encrypted at rest)
private int failedLoginAttempts; // Reset on successful login
private Instant lockedUntil; // Account lockout expiry
private Instant lastLoginAt;
private Instant createdAt;
private Instant updatedAt;
@ManyToMany
private Set<Role> roles; // Tenant-scoped roles
}Role and Permission Model
The RBAC model uses a three-level hierarchy:
Platform Roles (global scope)
- platform_admin: Full platform access
- tenant_creator: Can create new tenants
Tenant Roles (tenant-scoped)
- tenant_admin: Full access within tenant
- data_analyst: Query and dashboard access
- data_engineer: Pipeline and catalog access
- ml_engineer: ML service access
- dashboard_viewer: Read-only dashboard access
Resource Permissions (fine-grained)
- dashboard:read, dashboard:write, dashboard:delete
- query:execute, query:cancel
- model:train, model:deploy, model:delete
- pipeline:create, pipeline:run, pipeline:deleteRoles inherit permissions hierarchically: tenant_admin inherits all permissions from data_analyst, data_engineer, and ml_engineer.
2.3.B.3Token Lifecycle
Access Token Generation
public String generateAccessToken(UUID userId, String tenantId, Set<String> roles) {
Instant now = Instant.now();
return Jwts.builder()
.setId(UUID.randomUUID().toString()) // jti: unique token ID
.setSubject(userId.toString()) // sub: user identifier
.setIssuer("matih-platform") // iss: token issuer
.setIssuedAt(Date.from(now)) // iat: issued timestamp
.setExpiration(Date.from(now.plus(15, MINUTES))) // exp: 15-min expiry
.claim("type", "access") // Token type
.claim("tenant_id", tenantId) // Tenant scope
.claim("roles", String.join(",", roles)) // Permission set
.signWith(signingKey, SignatureAlgorithm.HS256) // HMAC-SHA256
.compact();
}Token Validation Flow
When a downstream service receives a request with a JWT:
1. Extract token from Authorization: Bearer <token>
2. Parse JWT without validation (extract header)
3. Validate signature against platform signing key
4. Check expiry (exp claim vs current time)
5. Check issuer (iss == "matih-platform")
6. Check token type (type == "access")
7. Check token not blacklisted (Redis lookup: blacklist:{jti})
8. Extract claims: sub, tenant_id, roles
9. Set SecurityContextHolder with authenticated principal
10. Set TenantContextHolder with tenant_idToken Blacklisting
When a user logs out or a token is revoked, the token's jti is added to the Redis blacklist with a TTL equal to the token's remaining validity:
public void blacklistToken(String jti, Instant expiry) {
long ttlSeconds = Duration.between(Instant.now(), expiry).getSeconds();
if (ttlSeconds > 0) {
redisTemplate.opsForValue().set(
"blacklist:" + jti,
"revoked",
Duration.ofSeconds(ttlSeconds)
);
}
}This ensures blacklisted tokens are automatically cleaned up from Redis when they would have expired naturally.
2.3.B.4Password Security
Password Hashing
Passwords are hashed using BCrypt with 12 rounds (2^12 = 4096 iterations):
private final BCryptPasswordEncoder encoder =
new BCryptPasswordEncoder(12);
public String hashPassword(String rawPassword) {
return encoder.encode(rawPassword);
}
public boolean verifyPassword(String rawPassword, String hash) {
return encoder.matches(rawPassword, hash);
}Password Policy
The PasswordPolicyService enforces configurable password requirements:
| Policy | Default Value | Configurable |
|---|---|---|
| Minimum length | 8 characters | Yes |
| Maximum length | 128 characters | Yes |
| Require uppercase | Yes | Yes |
| Require lowercase | Yes | Yes |
| Require digit | Yes | Yes |
| Require special character | Yes | Yes |
| Password history | Last 5 passwords | Yes |
| Maximum age | 90 days | Yes |
Account Lockout
After 5 consecutive failed login attempts, the account is locked for 30 minutes:
public void handleFailedLogin(User user) {
user.setFailedLoginAttempts(user.getFailedLoginAttempts() + 1);
if (user.getFailedLoginAttempts() >= MAX_FAILED_ATTEMPTS) {
user.setStatus(UserStatus.LOCKED);
user.setLockedUntil(Instant.now().plus(LOCKOUT_DURATION));
auditLogger.log("ACCOUNT_LOCKED", user.getId(), user.getTenantId());
}
}2.3.B.5Database Schema
The IAM service uses its own PostgreSQL database (iam) with the following core tables:
| Table | Purpose | Indexes |
|---|---|---|
users | User accounts | email (unique), tenant_id, status |
roles | Role definitions | name + tenant_id (unique) |
permissions | Permission catalog | code (unique) |
user_roles | User-to-role mapping | user_id + role_id (composite) |
role_permissions | Role-to-permission mapping | role_id + permission_id (composite) |
api_keys | API key records | key_hash (unique), tenant_id, user_id |
sessions | Active sessions | user_id, tenant_id, expires_at |
mfa_settings | MFA configuration per user | user_id (unique) |
password_history | Previous password hashes | user_id, created_at |
login_attempts | Login attempt tracking | email, ip_address, attempted_at |
Related Sections
- Control Plane Overview -- All Control Plane services
- Authentication in APIs -- JWT usage in API calls
- Security and Multi-Tenancy -- Platform security architecture
- IAM Deep Dive -- Full IAM documentation