MATIH Platform is in active MVP development. Documentation reflects current implementation status.
6. Identity & Access Management
Token Management

Token Lifecycle

JWT tokens are the primary authentication mechanism throughout the MATIH platform. The IAM service generates, validates, and manages the lifecycle of multiple token types. Every authenticated request -- whether from a browser, CLI, or service-to-service call -- carries a JWT that the receiving service validates to extract identity and authorization context.


Token Types

The JwtTokenProvider generates four distinct token types, each with a specific purpose and claims structure:

Token TypePurposeDefault TTLSubject
Access TokenUser authentication15 minutesUser email
Refresh TokenAccess token renewal7 daysUser email
OAuth2 Access TokenOAuth2 client flowsClient-configuredUser email or client ID
Impersonation TokenAdmin troubleshooting1-4 hoursTarget user email

Access Token Claims

The standard access token is issued during login and registration. Its claims carry the user's identity and authorization context:

{
  "sub": "jane.doe@acme.com",
  "iss": "matih-platform",
  "aud": "matih-api",
  "iat": 1707735000,
  "exp": 1707735900,
  "jti": "550e8400-e29b-41d4-a716-446655440000",
  "user_id": 42,
  "tenant_id": 7,
  "roles": ["ADMIN", "ANALYST"]
}

Claims Reference

ClaimTypeDescription
subStringUser email address (the subject)
issStringToken issuer, always "matih-platform"
audStringToken audience, always "matih-api"
iatNumberIssued-at timestamp (Unix epoch seconds)
expNumberExpiration timestamp (Unix epoch seconds)
jtiStringUnique token identifier (UUID v4)
user_idNumberMATIH user ID
tenant_idNumberTenant ID for multi-tenancy scoping
rolesArrayList of role authority strings

Tenant Isolation via Claims

The tenant_id claim is the cornerstone of multi-tenant data isolation. Every service that receives a request extracts this claim and uses it to scope database queries, API calls, and resource access:

// Example: extracting tenant context in a data plane service
Long tenantId = jwtTokenProvider.getTenantIdFromToken(token);
TenantContext.setCurrentTenant(tenantId);

This pattern is enforced by the shared commons-java library. Services that fail to extract and apply the tenant context will reject the request.


Refresh Token Claims

Refresh tokens have a minimal claims set to reduce their attack surface:

{
  "sub": "jane.doe@acme.com",
  "iss": "matih-platform",
  "aud": "matih-api",
  "iat": 1707735000,
  "exp": 1708339800,
  "jti": "660e8400-e29b-41d4-a716-446655440001",
  "type": "refresh"
}

The type: "refresh" claim distinguishes refresh tokens from access tokens. The isRefreshToken() method on JwtTokenProvider checks for this claim.

Refresh Token Rotation

When a refresh token is used to obtain a new access token, the IAM service implements token rotation:

  1. The old refresh token is looked up in the refresh_tokens table
  2. Its used flag is set to true
  3. A new refresh token is generated and persisted
  4. Both new access token and new refresh token are returned

If a previously-used refresh token is presented (replay attack), all refresh tokens for that user are revoked and the user must re-authenticate.

Client                   IAM Service                    Database
  |                          |                              |
  |  POST /auth/refresh      |                              |
  |  {refreshToken: "old"}   |                              |
  |------------------------->|                              |
  |                          |  Lookup refresh token         |
  |                          |------------------------------>|
  |                          |  Mark old token as used       |
  |                          |  Generate new access token    |
  |                          |  Generate new refresh token   |
  |                          |  Persist new refresh token    |
  |                          |------------------------------>|
  |  {accessToken, refreshToken}                            |
  |<-------------------------|                              |

OAuth2 Token Claims

OAuth2 access tokens include client and scope information:

Authorization Code Grant Token

{
  "sub": "jane.doe@acme.com",
  "iss": "matih-platform",
  "aud": "my-app-client-id",
  "iat": 1707735000,
  "exp": 1707738600,
  "jti": "770e8400-e29b-41d4-a716-446655440002",
  "client_id": "my-app-client-id",
  "scope": "read write",
  "user_id": 42,
  "tenant_id": 7,
  "token_type": "access_token"
}

Client Credentials Grant Token

{
  "sub": "my-service-client-id",
  "iss": "matih-platform",
  "aud": "my-service-client-id",
  "iat": 1707735000,
  "exp": 1707738600,
  "jti": "880e8400-e29b-41d4-a716-446655440003",
  "client_id": "my-service-client-id",
  "scope": "api:read api:write",
  "tenant_id": 7,
  "token_type": "access_token",
  "grant_type": "client_credentials"
}

Note that client credentials tokens do not contain a user_id claim since they operate in a service context.


Impersonation Token Claims

Impersonation tokens allow administrators to act as another user for troubleshooting. They include full audit trail information:

{
  "sub": "target.user@acme.com",
  "iss": "matih-platform",
  "aud": "matih-api",
  "iat": 1707735000,
  "exp": 1707742200,
  "jti": "990e8400-e29b-41d4-a716-446655440004",
  "user_id": 99,
  "tenant_id": 7,
  "roles": ["ANALYST"],
  "impersonation": true,
  "impersonation_session_id": "imp_a1b2c3d4e5f6",
  "impersonator_id": 1,
  "impersonator_email": "admin@matih.ai"
}
ClaimDescription
impersonationBoolean flag, always true for impersonation tokens
impersonation_session_idLinks to the ImpersonationSession record
impersonator_idThe admin user performing the impersonation
impersonator_emailThe admin's email for audit trail

Services can detect impersonation by checking the impersonation claim and log both the acting user and the impersonator for audit compliance.


Token Validation

The JwtTokenProvider.validateToken() method performs the following checks:

CheckException on Failure
Signature verification (HS256)SignatureException
Token structure (well-formed JWT)MalformedJwtException
Expiration checkExpiredJwtException
Supported algorithmUnsupportedJwtException
Non-empty claimsIllegalArgumentException

Validation in Other Services

Data plane services validate tokens using the shared commons-java JwtTokenProvider class. The validation key is distributed via Kubernetes secrets:

# Helm values for a data plane service
env:
  - name: JWT_SECRET_KEY
    valueFrom:
      secretKeyRef:
        name: matih-jwt-secret
        key: secret-key

This ensures all services use the same signing key and can validate tokens independently without calling back to the IAM service.


Token Extraction Methods

The JwtTokenProvider provides typed extraction methods for each claim:

// Extract username (subject)
String username = jwtTokenProvider.getUsernameFromToken(token);
 
// Extract user ID
Long userId = jwtTokenProvider.getUserIdFromToken(token);
 
// Extract tenant ID
Long tenantId = jwtTokenProvider.getTenantIdFromToken(token);
 
// Extract scopes (OAuth2 tokens)
Set<String> scopes = jwtTokenProvider.getScopesFromToken(token);
 
// Extract JTI (token ID)
String jti = jwtTokenProvider.getJtiFromToken(token);
 
// Extract client ID (OAuth2 tokens)
String clientId = jwtTokenProvider.getClientIdFromToken(token);
 
// Check token type
boolean isRefresh = jwtTokenProvider.isRefreshToken(token);
boolean isOAuth2 = jwtTokenProvider.isOAuth2Token(token);
boolean isImpersonation = jwtTokenProvider.isImpersonationToken(token);
 
// Extract impersonation details
Long impersonatorId = jwtTokenProvider.getImpersonatorId(token);
String impersonatorEmail = jwtTokenProvider.getImpersonatorEmail(token);
String sessionId = jwtTokenProvider.getImpersonationSessionId(token);
 
// Extract from HTTP request
Long userId = jwtTokenProvider.getUserIdFromRequest(httpServletRequest);

Token Revocation

Tokens can be revoked through several mechanisms:

MechanismScopeLatency
Refresh token revocationSingle refresh tokenImmediate
Session revocationAll tokens in a sessionImmediate
OAuth2 token revocationSingle access or refresh tokenImmediate
User lockoutAll active sessionsImmediate
Permission cache flushRefreshes on next requestEventual (cache TTL)

Since access tokens are stateless JWTs, revocation of access tokens is not immediate. The token remains valid until expiration. For immediate revocation, services can check a token blocklist stored in Redis:

Redis Key: "token:revoked:{jti}"
TTL: Same as the token's remaining lifetime

The API Gateway checks this blocklist on every request, providing platform-wide immediate revocation at a small performance cost.


Token Configuration

Token lifetimes are configurable through Spring Boot properties:

PropertyDefaultDescription
matih.security.jwt.access-token-expiration900000 (15 min)Access token lifetime in milliseconds
matih.security.jwt.refresh-token-expiration604800000 (7 days)Refresh token lifetime in milliseconds
matih.security.jwt.secret-key(from secret)HS256 signing key (minimum 256 bits)
matih.security.jwt.issuermatih-platformToken issuer claim
matih.security.jwt.audiencematih-apiToken audience claim

OAuth2 clients have their own configurable token lifetimes set per client:

Client PropertyDescription
accessTokenValidityAccess token lifetime in seconds
refreshTokenValidityRefresh token lifetime in seconds

Security Considerations

Key management. The JWT signing key is stored as a Kubernetes secret and injected via environment variable. It is never committed to source code or configuration files.

Algorithm choice. HS256 (HMAC-SHA256) is used because all services share the same signing key via Kubernetes secrets. If the platform evolves to support third-party token issuers, migration to RS256 (RSA-SHA256) with public/private key pairs would be required.

Minimum key length. The JwtTokenProvider constructor enforces a minimum 32-byte (256-bit) key length. If the configured key is shorter, it is padded to meet the HS256 minimum requirement.

Token size. Access tokens with standard claims are approximately 400-600 bytes. OAuth2 tokens with scope lists may be larger. The API Gateway is configured to handle tokens up to 8 KB.


Next Steps