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
- API Keys for long-lived machine-to-machine credentials
- Refresh tokens for long-lived sessions
- Role-Based Access Control (RBAC) with privilege inheritance
- Multi-tenancy support with tenant isolation
- Token impersonation / on-behalf-of for administrative and integration 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 contexttenantId— Tenant identifier for multi-tenancyiss(issuer) —"tSM"iat(issued at) — Token creation timestampexp(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 contexttenantId— Tenant identifieriss(issuer) —"tSM"iat(issued at) — Token creation timestampexp(expiration) — Token expiry timestamp
Translation Process:
- Frontend access token contains roles
- API Gateway translates roles to privileges using
/api/tokenendpoint - Backend access token is cached by API Gateway (5 minutes)
- 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
lastUpdatedAttimestamp 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:
- Extract user ID and roles from input access token
- Translate roles to privileges (cached)
- Generate new access token with privileges
- 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
ExternalTokenAuthenticatorbean - 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 (On Behalf Of / Impersonation)
Grant Type: token_exchange
Use Cases:
- On Behalf Of — An integration/service user performs actions on behalf of a target end-user, passing their identity through the system
- Admin Impersonation — An administrator acts as another user for troubleshooting or support purposes
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) of the target user whose identity will be assumedactor_token— Access token of the calling user (integration user or admin)
Process:
- Validate actor token (integration user or admin)
- Verify the actor has impersonation/on-behalf-of privileges
- Load target user's roles and privileges
- Generate access token for the target user
- Include
actclaim with the actor's user ID for traceability
Token Payload:
{
"sub": "target-user-id",
"authorities": ["Support_Agent"],
"act": {
"sub": "integration-user-id"
},
"iss": "tSM",
"iat": 1703260800,
"exp": 1703261700
}
Integration User (On Behalf Of) Pattern:
A common scenario is an external system (e.g., a portal or an orchestration layer) that authenticates as a service/integration user and then performs tSM operations on behalf of the actual end-user.
Steps for the integration user:
- Authenticate as the integration user using
client_credentialsgrant - Exchange the token via
token_exchange, passing the target user's UUID assubject_token - Use the resulting token for all subsequent API calls — requests are processed with the target user's identity and privileges
- The
actclaim preserves the integration user's identity for audit purposes
Audit:
- All actions performed via token exchange are logged
actclaim identifies the original actor (integration user or admin)- Audit trail clearly distinguishes direct actions from on-behalf-of actions
2.7 API Key
Grant Type: api_key
Use Case: Long-lived credential for machine-to-machine integrations without token refresh management
Request:
POST /api/token
Content-Type: application/json
{
"grant_type": "api_key",
"client_id": "tsm-microservice",
"api_key": "tsm_ak_7f3a92c1e8b04d5f..."
}
Response:
{
"access_token": "eyJhbGc...",
"expires_in": 3600,
"claims": ["Um.User.View", "Um.User.Edit"]
}
Alternative — Direct Header Authentication:
API keys can also be passed directly in request headers, bypassing the token exchange step:
GET /api/tsm-ticket/api/v1/tickets
X-API-Key: tsm_ak_7f3a92c1e8b04d5f...
Key Characteristics:
- Bound to a specific user — inherits that user's roles and privileges
- Optional expiration date (can be non-expiring for trusted internal services)
- Revocable at any time without affecting the user account
- Multiple API keys can be created per user
- No refresh token issued — the key itself is the long-lived credential
API Key vs. OAuth 2.0 ID Token:
| Aspect | API Key | ID Token (OAuth 2.0) |
|---|---|---|
| Purpose | Authenticate a client/service for API access | Prove user identity to a client application |
| Format | Opaque string (e.g., tsm_ak_...) | JWT with identity claims (sub, email, name) |
| Issued to | Machine / service account | End-user (via authorization code or implicit flow) |
| Lifetime | Long-lived (weeks to indefinite) | Short-lived (minutes to hours) |
| Contains privileges | No — resolved server-side from bound user | No — contains identity only, not authorization |
| Revocation | Instant (delete from DB) | Cannot be revoked (must wait for expiry) |
| Refresh flow | Not needed — key is reusable | Re-issued via refresh token or new login |
| Use in tSM | Machine-to-machine API authentication | Not currently issued (see Section 1.2) |
Key takeaway: API keys replace the need for ID tokens in M2M scenarios. While an OAuth ID token answers "who is this user?", an API key answers "which service is calling and with whose permissions?". API keys are simpler to manage for integrations because they don't expire on short cycles and don't require a token refresh flow.
Database Storage:
API keys are stored in the umt_api_key table:
CREATE TABLE umt_api_key (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
tenant_id VARCHAR(255) DEFAULT 'default.default',
key_hash VARCHAR(512) NOT NULL,
key_prefix VARCHAR(12) NOT NULL,
description VARCHAR(255),
issued_at TIMESTAMP NOT NULL,
expire_at TIMESTAMP,
last_used_at TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_by UUID,
FOREIGN KEY (user_id) REFERENCES umt_user(id)
);
| Column | Description |
|---|---|
key_hash | SHA-256 hash of the API key — the plain-text key is never stored |
key_prefix | First characters of the key (e.g., tsm_ak_7f3a) for identification in the UI |
status | ACTIVE or REVOKED |
expire_at | Optional expiration timestamp; NULL means non-expiring |
last_used_at | Updated on each successful authentication for audit |
Management:
- API keys are created and managed in User Management → Users → API Keys tab
- See Users — API Key Authentication for step-by-step instructions
Security Considerations:
- The plain-text API key is shown only once at creation — only the hash is stored
- Store API keys in a secrets manager (HashiCorp Vault, AWS Secrets Manager)
- Never commit API keys to source control
- Use expiration dates for keys used by external partners
- Monitor API key usage via audit logs
- Revoke keys immediately when compromised
3. Client Configuration
Clients define how tokens are generated and what claims they contain.
3.1 Available Clients
| Client ID | Privileges in Access Token | Privileges in Response | Refresh Token | Expiration |
|---|---|---|---|---|
tsm-ui | No (roles only) | Yes | Yes | 15 min |
tsm-microservice | Yes | Yes | No | 60 min |
basic-auth | Yes | No | No | 15 min |
api-key | Yes | Yes | No | 60 min |
3.2 Client Configuration
Location: Clients enum in cz.datalite.tsm.commons.conf.jwt.Clients
Properties:
clientId— Unique client identifiercreateRefreshToken— Whether to issue refresh tokensprivilegesClaimsInAccessToken— Include privileges (vs roles) in access tokenprivilegesClaimsInResponse— Include privileges in responseclaimsfieldexpirationInSec— 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 Exchange (Impersonation / On Behalf Of)
Note: The same flow applies when an integration user acts on behalf of an end-user via API. See Section 2.6 for the full integration pattern with sequence diagram.
5. Privilege Resolution
5.1 Role to Privilege Translation
Process:
- User has assigned roles (e.g.,
Admin,Support_Agent) - Each role grants/denies specific privileges via prefix notation
- System resolves privileges using global priority and composition rules
- 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 environmentcustomer1.staging— Customer 1, staging environmentisp.default— ISP tenant
Purpose:
master_tenant— Separate database/schemadata_tenant— Tenant discriminator within same database
6.2 Tenant Propagation
HTTP Header: X-Tenant-Id
Token Claim: tenantId
Priority:
- HTTP header (if present)
- Token claim
- Default:
default.default
Example Request:
GET /api/users
Authorization: Bearer eyJhbGc...
X-Tenant-Id: customer1.production
6.3 Tenant Validation
Process:
- Extract tenant ID from header or token
- Validate tenant exists in
umt_tenanttable - Check user belongs to tenant
- Set Hibernate/JPA tenant context
- 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:
| Expiration | Pros | Cons |
|---|---|---|
| Short (5-15 min) | Better security, forced re-validation | More refresh requests, worse UX |
| Medium (30-60 min) | Balanced | Default for most scenarios |
| Long (>2 hours) | Better UX | Security risk, stale privileges |
Recommendations by Use Case:
| Use Case | Access Token | Refresh Token |
|---|---|---|
| Public web app | 15 minutes | 30 days |
| Internal portal | 30 minutes | 90 days |
| Mobile app | 60 minutes | 180 days |
| Backend API | 60 minutes | N/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-Agentheader containing "Java" - Privilege checks bypassed
- TODO: Replace with custom header for better security
Empty Authority:
- If
anyAuthorityis null or empty → access granted - Use with caution
10. Audit and Logging
10.1 Authentication Events
SIEM Events:
SIEM_LOGIN_SUCCESS— Successful authenticationSIEM_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:
- Implement automatic token refresh
- Check system clock synchronization
- 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:
- Check
umt_refresh_tokentable - Verify refresh token expiration settings
- Review token cleanup jobs
Issue: "Access denied" with correct privileges
Symptoms:
- User has role with privilege
- API returns 403 Forbidden
Debugging:
- Enable debug logging:
logging:
level:
cz.datalite.tsm.commons.service: DEBUG
cz.datalite.tsm.usermanagement: DEBUG
- Check role-privilege translation cache
- Verify role global priority
- Check access rules (data-level restrictions)
Issue: Cache stampede on system user token
Symptoms:
- Multiple simultaneous requests to auth service
- High load on
/api/tokenendpoint
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
HttpOnlycookies 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
- Users — User creation and management
- Roles and Privileges — Permission model
- Access Rules — Data-level access control
- Configuration — Authentication provider setup