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 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 (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 impersonateactor_token— Current user's access token
Process:
- Validate actor token (current user)
- Verify current user has impersonation privileges
- Load target user's roles and privileges
- Generate access token for target user
- Include
actclaim 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
actclaim identifies the original actor
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 |
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 Impersonation
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