WFM Archive supports multiple authentication methods for API access and user management.
The primary authentication method uses JWT tokens obtained through the login endpoint.
POST /auth/login
curl -X POST "{base_url}/auth/login" \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe",
"password": "SecurePassword123!"
}'{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "user-123",
"username": "john.doe",
"email": "john.doe@company.com",
"roles": ["DOCUMENT_VIEWER", "DOCUMENT_EDITOR"]
}
}Include the token in the Authorization header:
curl -X GET "{base_url}/api/documents" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Or as a query parameter (for file downloads):
curl -X GET "{base_url}/api/documents/download?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."For enterprise Single Sign-On, WFM Archive integrates with Keycloak.
{
"realm": "wfmarchive",
"enabled": true,
"sslRequired": "external",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true
}{
"clientId": "wfmarchive-app",
"enabled": true,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"http://localhost:8080/*",
"https://wfmarchive.yourdomain.com/*"
],
"webOrigins": ["+"],
"protocol": "openid-connect",
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": true
}In app.properties:
keycloak.enabled = true
keycloak.serverUrl = https://keycloak.yourdomain.com/auth
keycloak.realm = wfmarchive
keycloak.clientId = wfmarchive-app
keycloak.clientSecret = ${KEYCLOAK_CLIENT_SECRET}
keycloak.ssl.required = externalsequenceDiagram
participant User
participant App as WFM Archive
participant KC as Keycloak
User->>App: Access Protected Resource
App->>User: Redirect to Keycloak
User->>KC: Login with Credentials
KC->>KC: Validate Credentials
KC->>User: Return with Auth Code
User->>App: Present Auth Code
App->>KC: Exchange Code for Token
KC->>App: Return Access Token
App->>User: Grant Access
# Get token from Keycloak
curl -X POST "https://keycloak.yourdomain.com/auth/realms/wfmarchive/protocol/openid-connect/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=wfmarchive-app" \
-d "client_secret=your-secret" \
-d "grant_type=password" \
-d "username=john.doe" \
-d "password=password"
# Use token with API
curl -X GET "{base_url}/api/documents" \
-H "Authorization: Bearer {keycloak_token}"For system-to-system integration, API keys provide a simple authentication method.
POST /auth/api-key
curl -X POST "{base_url}/auth/api-key" \
-H "Authorization: Bearer {admin_token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Integration Service",
"expiresAt": "2026-12-31T23:59:59Z",
"scopes": ["documents:read", "documents:write"]
}'Response:
{
"apiKey": "wfm_ak_1234567890abcdef",
"name": "Integration Service",
"created": "2025-09-21T10:00:00Z",
"expiresAt": "2026-12-31T23:59:59Z",
"scopes": ["documents:read", "documents:write"]
}Include the API key in the X-API-Key header:
curl -X GET "{base_url}/api/documents" \
-H "X-API-Key: wfm_ak_1234567890abcdef"POST /auth/refresh
curl -X POST "{base_url}/auth/refresh" \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}POST /auth/logout
curl -X POST "{base_url}/auth/logout" \
-H "Authorization: Bearer {token}"GET /auth/validate
curl -X GET "{base_url}/auth/validate" \
-H "Authorization: Bearer {token}"Response:
{
"valid": true,
"expires_in": 1800,
"user": {
"id": "user-123",
"username": "john.doe"
}
}Authorization: Bearer {token}
Content-Type: application/json
X-Request-ID: {unique-request-id}# CORS settings in web-app.properties
cuba.rest.cors.allowedOrigins = https://app.yourdomain.com
cuba.rest.cors.allowedMethods = GET,POST,PUT,DELETE,OPTIONS
cuba.rest.cors.allowedHeaders = Authorization,Content-Type,X-Request-ID,X-API-Key
cuba.rest.cors.exposedHeaders = X-Total-Count,X-Page-Count
cuba.rest.cors.maxAge = 3600| Role | Description | Permissions |
|---|---|---|
| ADMIN | Full system access | All operations |
| DOCUMENT_MANAGER | Manage documents | Create, read, update, delete documents |
| DOCUMENT_EDITOR | Edit documents | Create, read, update documents |
| DOCUMENT_VIEWER | View documents | Read documents only |
| API_USER | API access | Based on API key scopes |
{
"documents:read": "View documents",
"documents:write": "Create and update documents",
"documents:delete": "Delete documents",
"admin:users": "Manage users",
"admin:config": "System configuration",
"admin:audit": "View audit logs"
}GET /auth/permissions
curl -X GET "{base_url}/auth/permissions" \
-H "Authorization: Bearer {token}"Response:
{
"roles": ["DOCUMENT_EDITOR"],
"permissions": [
"documents:read",
"documents:write"
],
"organizations": ["org-001", "org-002"]
}POST /auth/mfa/enable
curl -X POST "{base_url}/auth/mfa/enable" \
-H "Authorization: Bearer {token}"Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_code": "data:image/png;base64,iVBORw0KGgo...",
"backup_codes": [
"12345678",
"87654321",
"11111111"
]
}POST /auth/mfa/verify
curl -X POST "{base_url}/auth/mfa/verify" \
-H "Content-Type: application/json" \
-d '{
"code": "123456"
}'- Store tokens securely (never in localStorage for web apps)
- Use short expiration times (1 hour for access tokens)
- Implement token refresh mechanism
- Revoke tokens on logout
- Rotate API keys regularly
- Use different keys for different environments
- Restrict API key scopes to minimum required
- Monitor API key usage
- Always use HTTPS in production
- Implement certificate pinning for mobile apps
- Use TLS 1.2 or higher
- Enable HSTS headers
# Rate limiting configuration
security.rateLimit.enabled = true
security.rateLimit.requests = 1000
security.rateLimit.window = 3600
security.rateLimit.blockDuration = 900All authentication events are logged:
- Successful logins
- Failed login attempts
- Token refreshes
- Permission changes
- API key usage
{
"error": "INVALID_TOKEN",
"message": "Token is invalid or expired",
"code": 401
}Solution: Refresh token or re-authenticate
{
"error": "FORBIDDEN",
"message": "Insufficient permissions for this operation",
"code": 403
}Solution: Check user roles and permissions
{
"error": "SSO_ERROR",
"message": "Unable to connect to Keycloak server",
"code": 503
}Solution: Verify Keycloak URL and network connectivity
const axios = require('axios');
class WFMArchiveClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.token = null;
}
async login(username, password) {
const response = await axios.post(`${this.baseUrl}/auth/login`, {
username,
password
});
this.token = response.data.access_token;
return this.token;
}
async getDocuments() {
return await axios.get(`${this.baseUrl}/api/documents`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
}
}
// Usage
const client = new WFMArchiveClient('https://api.example.com');
await client.login('john.doe', 'password');
const documents = await client.getDocuments();import requests
class WFMArchiveClient:
def __init__(self, base_url):
self.base_url = base_url
self.token = None
def login(self, username, password):
response = requests.post(f"{self.base_url}/auth/login", json={
"username": username,
"password": password
})
self.token = response.json()["access_token"]
return self.token
def get_documents(self):
headers = {"Authorization": f"Bearer {self.token}"}
return requests.get(f"{self.base_url}/api/documents", headers=headers)
# Usage
client = WFMArchiveClient("https://api.example.com")
client.login("john.doe", "password")
documents = client.get_documents()- Try API Examples
- Review REST Endpoints
- Check Error Codes