Skip to main content
Version: 2.4

Authentication and Authorization Architecture

This document provides a comprehensive overview of the authentication and authorization mechanisms in tSM, including token-based authentication (OAuth 2.0), refresh token management, privilege resolution, and security configuration.

Target Audience: DevOps engineers, system configurators, architects, and security experts responsible for deploying and securing tSM installations.


Overview

tSM implements a multi-layered security architecture based on:

  • OAuth 2.0 / JWT tokens for stateless authentication
  • Refresh tokens for long-lived sessions
  • Role-Based Access Control (RBAC) with privilege inheritance
  • Multi-tenancy support with tenant isolation
  • Token impersonation for administrative operations

1. Token Types and Their Purposes

1.1 Token Hierarchy

1.2 ID Token

Status: Not currently issued by tSM

Purpose: Contains only user identity information, typically with long validity (e.g., 1 month).

Usage: In standard OAuth 2.0 flows, ID tokens are used to verify user identity without exposing credentials repeatedly.


1.3 Refresh Token

Type: GUID (Universally Unique Identifier)

Storage: Database table umt_refresh_token

Validity: 90 days (default, configurable via security.jwt.refresh-expiration)

Purpose: Allows users to obtain new access tokens without re-entering credentials.

Behavior:

  • Generated on initial login
  • Stored with metadata (user agent, IP address, timestamps)
  • Automatically renewed on each use (validity extended by 90 days)
  • Invalidated when user logs out or after expiration

Security Considerations:

  • Each refresh token is unique per login session
  • Can be revoked by deleting from database
  • Tracks usage for audit purposes

Database Schema:

CREATE TABLE umt_refresh_token (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
tenant_id VARCHAR(255) DEFAULT 'default.default',
client_id VARCHAR(255) NOT NULL,
issued_at TIMESTAMP NOT NULL,
expire_at TIMESTAMP NOT NULL,
user_agent TEXT,
ip_address VARCHAR(45),
last_updated_at TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES umt_user(id)
);

1.4 Access Token (Frontend)

Type: JWT (JSON Web Token)

Validity: 15 minutes (default, configurable via security.jwt.expiration)

Purpose: Sent with every API request to authenticate the user.

Claims:

  • sub (subject) — User ID (UUID)
  • authorities — List of roles (e.g., ["Admin", "Support_Agent"])
  • organizationId — Optional organization context
  • tenantId — Tenant identifier for multi-tenancy
  • iss (issuer) — "tSM"
  • iat (issued at) — Token creation timestamp
  • exp (expiration) — Token expiry timestamp

Example Token Payload:

{
"sub": "550e8400-e29b-41d4-a716-446655440000",
"authorities": ["Admin", "Support_Agent"],
"organizationId": "org-123",
"tenantId": "customer1.production",
"iss": "tSM",
"iat": 1703260800,
"exp": 1703261700
}

Usage:

  • Included in HTTP header: Authorization: Bearer <access_token>
  • Validated on each API request
  • Frontend uses roles from claims for UI permissions

1.5 Access Token (Backend)

Type: JWT with enriched claims

Validity: 60 minutes (configurable per client)

Purpose: Used for backend-to-backend (microservice) communication with pre-computed privileges.

Claims:

  • sub (subject) — User ID (UUID)
  • authorities — List of privileges (e.g., ["Um.User.View", "Um.User.Edit"])
  • organizationId — Optional organization context
  • tenantId — Tenant identifier
  • iss (issuer) — "tSM"
  • iat (issued at) — Token creation timestamp
  • exp (expiration) — Token expiry timestamp

Translation Process:

  1. Frontend access token contains roles
  2. API Gateway translates roles to privileges using /api/token endpoint
  3. Backend access token is cached by API Gateway (5 minutes)
  4. Backend services validate token and check privileges directly

Caching Strategy:

  • API Gateway caches backend tokens for 5 minutes
  • Reduces load on user-management service
  • Balance between performance and privilege updates

1.6 Claims List for Frontend

Endpoint: /api/token response includes claims field

Purpose: Frontend receives complete list of user privileges alongside the access token.

Usage:

  • Frontend displays/hides UI elements based on privileges
  • Access token only contains roles (compact token size)
  • Claims list contains full privilege tree

Example Response:

{
"access_token": "eyJhbGc...",
"refresh_token": "550e8400-...",
"expires_in": 900,
"claims": [
"Um.User.View",
"Um.User.Edit",
"Crm.Account.View",
"Crm.Account.Edit"
],
"organizationId": "org-123",
"tenantId": "customer1.production"
}

2. OAuth 2.0 Grant Types

tSM supports multiple OAuth 2.0 grant types for different authentication scenarios.

2.1 Resource Owner Password Credentials (ROPC)

Grant Type: ropc

Use Case: Username + password authentication

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "ropc",
"client_id": "tsm-ui",
"username": "john.doe",
"password": "SecurePassword123!",
"audience": "https://tsm.example.com"
}

Response:

{
"access_token": "eyJhbGc...",
"refresh_token": "550e8400-...",
"expires_in": 900,
"claims": ["Um.User.View", "Um.User.Edit"],
"tenantId": "default.default"
}

Security Notes:

  • Password is transmitted over HTTPS
  • Multiple authentication providers supported (DB, LDAP, AD)
  • Failed attempts are logged with SIEM events

2.2 Refresh Token

Grant Type: refresh_token

Use Case: Obtain new access token using refresh token

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "refresh_token",
"client_id": "tsm-ui",
"refresh_token": "550e8400-e29b-41d4-a716-446655440000"
}

Response:

{
"access_token": "eyJhbGc...",
"refresh_token": "550e8400-e29b-41d4-a716-446655440000",
"expires_in": 900,
"claims": ["Um.User.View", "Um.User.Edit"],
"tenantId": "default.default"
}

Behavior:

  • Refresh token validity is extended by 90 days
  • lastUpdatedAt timestamp is updated in database
  • Original refresh token is reused (not rotated)

2.3 Access Token Exchange

Grant Type: access_token

Use Case: Backend obtains enriched access token with privileges

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "access_token",
"client_id": "tsm-microservice",
"access_token": "eyJhbGc..."
}

Process:

  1. Extract user ID and roles from input access token
  2. Translate roles to privileges (cached)
  3. Generate new access token with privileges
  4. Return token with 60-minute validity

2.4 External Token

Grant Type: external_token

Use Case: Authentication via external identity provider (Google, Azure AD, etc.)

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "external_token",
"client_id": "tsm-ui",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Implementation:

  • Requires ExternalTokenAuthenticator bean
  • Validates external token (Google OAuth, Azure AD, etc.)
  • Maps external user to tSM user (JIT provisioning)
  • Returns tSM tokens

2.5 Client Credentials

Grant Type: client_credentials

Use Case: Service account authentication

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "client_credentials",
"client_id": "monitoring-service",
"username": "service-account",
"password": "ServicePassword123!"
}

Usage:

  • Background jobs and scheduled tasks
  • Monitoring and health checks
  • Microservice-to-microservice authentication

2.6 Token Exchange (Impersonation)

Grant Type: token_exchange

Use Case: Admin impersonates another user

Request:

POST /api/token
Content-Type: application/json

{
"grant_type": "token_exchange",
"client_id": "tsm-ui",
"subject_token": "550e8400-e29b-41d4-a716-446655440000",
"actor_token": "eyJhbGc..."
}

Parameters:

  • subject_token — User ID (UUID) to impersonate
  • actor_token — Current user's access token

Process:

  1. Validate actor token (current user)
  2. Verify current user has impersonation privileges
  3. Load target user's roles and privileges
  4. Generate access token for target user
  5. Include act claim with actor's user ID

Token Payload with Impersonation:

{
"sub": "target-user-id",
"authorities": ["Support_Agent"],
"act": {
"sub": "admin-user-id"
},
"iss": "tSM",
"iat": 1703260800,
"exp": 1703261700
}

Audit:

  • All actions performed under impersonation are logged
  • act claim identifies the original actor

3. Client Configuration

Clients define how tokens are generated and what claims they contain.

3.1 Available Clients

Client IDPrivileges in Access TokenPrivileges in ResponseRefresh TokenExpiration
tsm-uiNo (roles only)YesYes15 min
tsm-microserviceYesYesNo60 min
basic-authYesNoNo15 min

3.2 Client Configuration

Location: Clients enum in cz.datalite.tsm.commons.conf.jwt.Clients

Properties:

  • clientId — Unique client identifier
  • createRefreshToken — Whether to issue refresh tokens
  • privilegesClaimsInAccessToken — Include privileges (vs roles) in access token
  • privilegesClaimsInResponse — Include privileges in response claims field
  • expirationInSec — Token validity in seconds

Example:

enum class Clients(
val clientId: String,
val createRefreshToken: Boolean,
val privilegesClaimsInAccessToken: Boolean,
val privilegesClaimsInResponse: Boolean,
val expirationInSec: Int
) {
TSM_UI("tsm-ui", true, false, true, 15 * 60),
TSM_MICROSERVICE("tsm-microservice", false, true, true, 60 * 60),
BASIC_AUTH("basic_auth", false, true, false, 15 * 60)
}

4. Authentication Flow

4.1 Initial Login (ROPC)

4.2 API Request with Access Token

4.3 Token Refresh

4.4 Token Impersonation


5. Privilege Resolution

5.1 Role to Privilege Translation

Process:

  1. User has assigned roles (e.g., Admin, Support_Agent)
  2. Each role grants/denies specific privileges via prefix notation
  3. System resolves privileges using global priority and composition rules
  4. Result is cached for performance

Example Role Definition:

Admin (globalPriority = 100):
+Um.User
+Crm.Account
-Um.User.Delete

Support_Agent (globalPriority = 50):
+Um.Ticket.View
+Um.Ticket.Edit

Resulting Privileges:

  • Um.User.View ✅ (from Admin)
  • Um.User.Edit ✅ (from Admin)
  • Um.User.Delete ❌ (denied by Admin)
  • Um.Ticket.View ✅ (from Support_Agent)
  • Um.Ticket.Edit ✅ (from Support_Agent)
  • Crm.Account.View ✅ (from Admin)

5.2 Caching Strategy

Cache: translateRolesToPrivileges

Key: List of role codes + organizationId

TTL: Configurable (default: based on cache manager)

Invalidation: On role privilege changes

Implementation:

@Cacheable(CACHE_TRANSLATE_ROLES_TO_PRIVILEGES, sync = true)
fun translateRolesToPrivileges(roles: List<String>, organizationId: String?): List<String> {
return userDetailsService.getPrivilegesByNewRules(
rolePrivRepository.findByRoleCodeIn(roles)
)
}

6. Multi-Tenancy Support

6.1 Tenant Structure

Format: {master_tenant}.{data_tenant}

Examples:

  • default.default — Default tenant (single-tenant deployment)
  • customer1.production — Customer 1, production environment
  • customer1.staging — Customer 1, staging environment
  • isp.default — ISP tenant

Purpose:

  • master_tenant — Separate database/schema
  • data_tenant — Tenant discriminator within same database

6.2 Tenant Propagation

HTTP Header: X-Tenant-Id

Token Claim: tenantId

Priority:

  1. HTTP header (if present)
  2. Token claim
  3. Default: default.default

Example Request:

GET /api/users
Authorization: Bearer eyJhbGc...
X-Tenant-Id: customer1.production

6.3 Tenant Validation

Process:

  1. Extract tenant ID from header or token
  2. Validate tenant exists in umt_tenant table
  3. Check user belongs to tenant
  4. Set Hibernate/JPA tenant context
  5. Process request

Security:

  • System user can access any tenant
  • Regular users are restricted to their assigned tenant
  • Cross-tenant access denied by default

7. System User and Microservices

7.1 System User Token

Purpose: Microservice-to-microservice authentication without user context

Caching: 60 seconds (prevents token stampede)

Usage:

// Get system user token for default tenant
val token = tsmSecurity.getSystemUserToken()

// Get system user token for specific tenant
val token = tsmSecurity.getSystemUserToken("customer1.production")

Token Cache:

  • Implemented with Caffeine cache
  • Thread-safe with request coalescing
  • Prevents multiple parallel requests from overloading auth service

7.2 Cache Stampede Protection

Problem: Multiple threads request system token simultaneously when cache expires.

Solution:

val userTokenCache = Caffeine.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.maximumSize(100)
.build<String, String>()

fun getSystemUserToken(tenantId: String): String {
return userTokenCache.get("systemUser$tenantId") {
authTokenProvider.getSystemUserToken(tenantId)
}
}

Behavior:

  • Only first thread executes loader function
  • Other threads wait for result
  • All receive same token value

7.3 System User Authentication Context

Setup:

// Set security context to system user (default tenant)
tsmSecurity.securitySetSystemUser()

// Set security context to system user (specific tenant)
tsmSecurity.securitySetSystemUser("customer1.production")

Usage Scenarios:

  • Scheduled jobs and background tasks
  • System initialization
  • Admin operations requiring elevated privileges

8. Security Configuration

8.1 JWT Configuration

File: application.yml

security:
jwt:
# Token endpoint
uri: /auth

# HTTP header for token
header: Authorization

# Token prefix
prefix: "Bearer "

# Access token expiration (seconds)
expiration: 900 # 15 minutes

# Refresh token expiration (duration format)
refresh-expiration: 90d # 90 days

# JWT secret key
secret: ${JWT_SECRET:JwtSecretKey}

Environment Variables:

  • JWT_SECRET — Secret key for token signing (should be strong, 256+ bits)

Recommendations:

  • Use environment-specific secrets
  • Rotate secrets periodically
  • Store secrets in secure vault (HashiCorp Vault, AWS Secrets Manager)

8.2 Token Expiration Tuning

Considerations:

ExpirationProsCons
Short (5-15 min)Better security, forced re-validationMore refresh requests, worse UX
Medium (30-60 min)BalancedDefault for most scenarios
Long (>2 hours)Better UXSecurity risk, stale privileges

Recommendations by Use Case:

Use CaseAccess TokenRefresh Token
Public web app15 minutes30 days
Internal portal30 minutes90 days
Mobile app60 minutes180 days
Backend API60 minutesN/A

8.3 Actuator Security

Configuration:

security:
actuator:
# Basic auth for actuator endpoints
username: ${ACTUATOR_USERNAME:}
password: ${ACTUATOR_PASSWORD:}

Behavior:

  • If username/password configured → Basic auth
  • Otherwise → JWT auth (except /actuator/health*, /actuator/info, /actuator/prometheus)

Recommended:

  • Use Basic auth for actuator in production
  • Expose only health/info endpoints publicly
  • Protect sensitive endpoints (/actuator/env, /actuator/beans)

9. Privilege Checking

9.1 Backend Privilege Validation

Service: TsmSecurityService

Methods:

hasAccess

fun hasAccess(anyAuthority: String?): Boolean

Usage:

if (tsmSecurityService.hasAccess("Um.User.Edit")) {
// User has privilege
}

Returns: true if user has any of comma-separated privileges, false otherwise


hasAny

fun hasAny(anyAuthority: String?): Boolean

Usage:

@PreAuthorize("@tsmSecurityService.hasAny('Um.User.Edit')")
fun updateUser(user: User) { ... }

Behavior: Throws AccessDeniedException if access denied, returns true otherwise


assertPrivilege

fun assertPrivilege(privilege: String)

Usage:

tsmSecurityService.assertPrivilege("Um.User.Delete")
// Throws ApiAccessDeniedException if denied

9.2 Special Cases

System User:

  • Bypass privilege checks
  • Full access to all resources
  • Used for internal operations

Microservice Communication:

  • Detected by User-Agent header containing "Java"
  • Privilege checks bypassed
  • TODO: Replace with custom header for better security

Empty Authority:

  • If anyAuthority is null or empty → access granted
  • Use with caution

10. Audit and Logging

10.1 Authentication Events

SIEM Events:

  • SIEM_LOGIN_SUCCESS — Successful authentication
  • SIEM_LOGIN_UNSUCCESS — Failed authentication

Logged Information:

  • Username
  • User ID (UUID)
  • Login code
  • User email
  • Tenant ID
  • Timestamp
  • IP address (from refresh token)
  • User agent (from refresh token)

Example:

logWithSiemContext(SiemEventName.SIEM_LOGIN_SUCCESS) {
logger.info("Successfully authenticated user: $username with id: $id,
login: ${userObject.code}, name: ${userObject.name},
email: ${userObject.email} in tenant: $tenantId")
}

10.2 Authentication Result Events

Event: AuthenticationEventAfter

Payload:

data class AuthenticationResult(
val user: String,
val userId: String? = null,
val success: Boolean
)

Subscribers can:

  • Track failed login attempts
  • Implement account lockout
  • Generate security alerts
  • Update last login timestamp

11. Common Integration Patterns

11.1 Frontend Integration

Initial Login:

// 1. Login request
const response = await fetch('/api/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'ropc',
client_id: 'tsm-ui',
username: 'john.doe',
password: 'password123'
})
});

const tokens = await response.json();
// {access_token, refresh_token, claims, expires_in}

// 2. Store tokens
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
localStorage.setItem('claims', JSON.stringify(tokens.claims));

API Request:

// Include access token in every request
const response = await fetch('/api/users', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
}
});

Token Refresh:

// Intercept 401 Unauthorized responses
axios.interceptors.response.use(
response => response,
async error => {
if (error.response?.status === 401) {
// Refresh token
const refreshResponse = await fetch('/api/token', {
method: 'POST',
body: JSON.stringify({
grant_type: 'refresh_token',
client_id: 'tsm-ui',
refresh_token: localStorage.getItem('refresh_token')
})
});

const tokens = await refreshResponse.json();
localStorage.setItem('access_token', tokens.access_token);

// Retry original request
error.config.headers['Authorization'] = `Bearer ${tokens.access_token}`;
return axios.request(error.config);
}
return Promise.reject(error);
}
);

11.2 Backend Service Integration

Feign Client Configuration:

@FeignClient(name = "tsm-user-management")
interface UserManagementClient {
@GetMapping("/api/users")
fun getUsers(): List<User>
}

Request Interceptor:

@Component
class TsmFeignRequestInterceptor(
private val tsmSecurity: TsmSecurity
) : RequestInterceptor {
override fun apply(template: RequestTemplate) {
val token = tsmSecurity.getSystemUserToken()
template.header("Authorization", "Bearer $token")

// Propagate tenant
val tenantId = tsmSecurityService.currentTenantId()
template.header(TENANT_ID_HEADER, tenantId)
}
}

11.3 External Authentication Integration

Implementation:

@Component
class GoogleTokenAuthenticator : ExternalTokenAuthenticator {
override fun authenticate(
request: AuthJwtTokenRequest,
httpRequest: HttpServletRequest,
tenantId: String
): TsmAuthenticationToken {
// 1. Validate Google ID token
val googleUser = verifyGoogleToken(request.id_token!!)

// 2. Find or create tSM user
val user = userRepository.findByExternalId(googleUser.sub)
?: createUserFromGoogle(googleUser, tenantId)

// 3. Load user roles
val roles = userRoleService.findValidByUserId(user.id!!)
.map { it.code }

// 4. Return authentication
return TsmAuthenticationToken(
user.id,
request.id_token,
roles.map { SimpleGrantedAuthority(it) },
tenantId
)
}
}

12. Troubleshooting

12.1 Common Issues

Issue: "Token expired"

Symptoms:

  • 401 Unauthorized responses
  • Error: "Token expired"

Solutions:

  1. Implement automatic token refresh
  2. Check system clock synchronization
  3. Verify token expiration configuration

Issue: "Refresh token not found"

Symptoms:

  • Cannot refresh access token
  • User forced to re-login

Possible Causes:

  • Refresh token deleted from database
  • Token expired (>90 days)
  • Database connection issues

Solutions:

  1. Check umt_refresh_token table
  2. Verify refresh token expiration settings
  3. Review token cleanup jobs

Issue: "Access denied" with correct privileges

Symptoms:

  • User has role with privilege
  • API returns 403 Forbidden

Debugging:

  1. Enable debug logging:
logging:
level:
cz.datalite.tsm.commons.service: DEBUG
cz.datalite.tsm.usermanagement: DEBUG
  1. Check role-privilege translation cache
  2. Verify role global priority
  3. Check access rules (data-level restrictions)

Issue: Cache stampede on system user token

Symptoms:

  • Multiple simultaneous requests to auth service
  • High load on /api/token endpoint

Solution:

  • Verify Caffeine cache configuration
  • Check cache TTL (should be 60 seconds)
  • Monitor cache hit rate

12.2 Debugging Tools

Token Decoder:

# Decode JWT token (requires jq)
echo "eyJhbGc..." | cut -d'.' -f2 | base64 -d | jq

Check Refresh Token:

SELECT * FROM umt_refresh_token 
WHERE id = '550e8400-e29b-41d4-a716-446655440000';

Check User Roles:

SELECT u.code, r.code as role_code, r.global_priority
FROM umt_user u
JOIN umt_user_role ur ON ur.user_id = u.id
JOIN umt_role r ON r.id = ur.role_id
WHERE u.code = 'john.doe';

13. Security Best Practices

13.1 Token Security

DO:

  • Use HTTPS for all token transmission
  • Store tokens in HttpOnly cookies or secure storage
  • Implement token refresh before expiration
  • Validate token signature and expiration on every request
  • Use strong JWT secrets (256+ bits)

DON'T:

  • Store tokens in localStorage (XSS vulnerability)
  • Log tokens in plaintext
  • Share tokens between users
  • Use predictable token secrets

13.2 Refresh Token Management

DO:

  • Store refresh tokens in database only
  • Track token usage (IP, user agent)
  • Implement token revocation
  • Set reasonable expiration (30-90 days)
  • Clean up expired tokens regularly

DON'T:

  • Store refresh tokens in cookies/localStorage
  • Allow infinite refresh token reuse
  • Skip token expiration checks

13.3 Multi-Tenancy Security

DO:

  • Validate tenant exists before processing requests
  • Enforce tenant isolation in all queries
  • Audit cross-tenant access attempts
  • Use tenant-specific encryption keys

DON'T:

  • Trust client-provided tenant ID without validation
  • Allow system users unrestricted cross-tenant access
  • Skip tenant checks for "internal" endpoints

14. Configuration Checklist

Production Deployment

  • Set strong JWT_SECRET (256+ bits)
  • Configure appropriate token expiration
  • Enable HTTPS/TLS
  • Set up actuator basic auth
  • Configure SIEM event logging
  • Enable audit logging
  • Set up token cleanup jobs
  • Configure cache expiration
  • Review client configurations
  • Test token refresh flow
  • Implement rate limiting
  • Set up monitoring alerts

See Also