Comprehensive authentication and authorization implementation for ThemisDB with enterprise SSO and multi-factor authentication support.
Provides enterprise-grade authentication and authorization for ThemisDB, including JWT/OpenID Connect, Kerberos/GSSAPI, TOTP MFA, and RBAC enforcement.
In scope: JWT validation with JWKS, Kerberos/GSSAPI SSO, TOTP MFA with recovery codes, RBAC principal-to-role mapping, brute force rate limiting, session management and revocation.
Out of scope: User management (external IdP), secrets storage (handled by security module), audit logging (handled by utils module).
jwt_validator.cpp— JWT/OpenID Connect validationgssapi_authenticator.cpp— GSSAPI/Kerberos handlermfa_authenticator.cpp— TOTP MFA with recovery codesoauth_device_flow.cpp— OAuth 2.0 device authorization flow (RFC 8628)oauth_pkce_flow.cpp— OAuth 2.0 Authorization Code Grant with PKCE for public clients (RFC 7636)oidc_provider.cpp— OIDC Provider Discovery and federated identity integrationsaml_authenticator.cpp— SAML 2.0 SP-initiated and IdP-initiated SSOldap_authenticator.cpp— LDAP/Active Directory direct bind authenticationapi_key_authenticator.cpp— static API key + secret authenticationmtls_authenticator.cpp— certificate-based mutual TLS authenticationwebauthn_authenticator.cpp— WebAuthn/FIDO2 hardware token authenticationfederated_identity_manager.cpp— federated identity across multiple realmssession_manager.cpp— session lifecycle (create / validate / revoke / list) with idle and absolute timeoutszero_trust_auth_verifier.cpp— zero-trust continuous verification per request
Maturity: 🟢 Production-Ready — JWT, Kerberos, TOTP, RBAC, OAuth 2.0 PKCE, OAuth 2.0 device flow, SAML 2.0, WebAuthn/FIDO2, mTLS, LDAP, API key authentication, federated identity, zero-trust verification, and session management are all production-ready.
- Overview
- Components
- Authentication Flows
- Configuration
- Security Features
- Compliance
- Usage Examples
- Best Practices
The Authentication Module provides enterprise-grade authentication mechanisms for ThemisDB, supporting modern security standards and compliance requirements. It implements multiple authentication strategies with seamless integration into existing enterprise infrastructure.
- JWT Authentication: OpenID Connect integration with Keycloak, Okta, Auth0, and any standards-compliant provider
- OIDC Provider Federation: Auto-configure from any OIDC provider's discovery endpoint
- Kerberos/GSSAPI: Enterprise SSO for Active Directory environments
- Multi-Factor Authentication: TOTP-based MFA with recovery codes
- WebAuthn/FIDO2: Hardware security keys, passkeys, and biometric authenticators
- OAuth 2.0 Device Flow: Headless device / CLI authentication (RFC 8628)
- OAuth 2.0 PKCE Flow: Authorization Code Grant with PKCE for public clients (RFC 7636)
- SAML 2.0: SP-initiated and IdP-initiated SSO with assertion encryption
- LDAP/Active Directory: Direct bind authentication for enterprise directories
- API Key Authentication: Static key + secret for service-to-service access
- mTLS: Certificate-based mutual TLS for service identity
- Federated Identity: Unified principal namespace across multiple identity realms
- Signature Verification: RS256 with JWKS caching
- Principal-to-Role Mapping: Flexible authorization system
- Clock Skew Tolerance: Distributed system friendly
- Rate Limiting: Brute force and replay attack prevention
- Session Management: Cryptographically random session IDs, idle/absolute timeouts, per-user limits, revocation endpoint
- Zero-Trust Verification: Continuous per-request identity and network policy enforcement
┌─────────────────┐
│ Client App │
└────────┬────────┘
│
┌────▼─────┐
│ Method │
│ Selection│
└────┬─────┘
│
┌────▼──────────────────────────────────────────┐
│ JWT │ Kerberos │ MFA │ Device Flow │
└───┬───┴──────┬─────┴────┬───┴──────┬──────────┘
│ │ │ │
┌───▼──────────▼──────────▼──────────▼──────────┐
│ Principal Extraction │
└───────────────────────┬────────────────────────┘
│
┌───────────────────────▼────────────────────────┐
│ Role Mapping & RBAC │
└───────────────────────┬────────────────────────┘
│
┌───────────────────────▼────────────────────────┐
│ Access Control Decision │
└────────────────────────────────────────────────┘
Validates JWT tokens from OpenID Connect providers (Keycloak, Okta, Auth0, etc.).
Features:
- RS256 signature verification using OpenSSL
- JWKS endpoint caching (configurable TTL)
- Claim extraction (subject, email, tenant, roles, groups)
- Clock skew tolerance (default: 60 seconds)
- Audience and issuer validation
- User-specific key derivation (HKDF-based)
Token Format:
header.payload.signature
Supported Claims:
sub: Subject (user ID)email: User email addresstenant_id: Multi-tenancy supportgroups: Group membershipsroles: Role assignmentsiss: Issuer URLexp: Expiration timestampnbf: Not-before timestampiat: Issued-at timestampaud: Audience
Implements Kerberos/GSSAPI authentication for enterprise single sign-on.
Features:
- MIT Kerberos 5 support
- Active Directory integration
- Heimdal Kerberos support
- Cross-platform (Linux, Windows, macOS)
- Service principal validation
- Keytab-based authentication
- Principal-to-role mapping with wildcards
- Fallback to basic authentication
Supported Platforms:
- Linux: MIT Kerberos, Heimdal
- Windows: SSPI (Windows native)
- macOS: Heimdal (built-in)
Time-based One-Time Password (TOTP) implementation per RFC 6238.
Features:
- TOTP code generation and validation
- QR code provisioning URI generation
- Recovery codes (single-use)
- Configurable time window (default: ±30 seconds)
- 6 or 8 digit codes
- Rate limiting (prevents brute force)
- Replay attack prevention
TOTP Algorithm:
TOTP = HOTP(K, T)
where:
K = shared secret
T = floor((current_time - T0) / time_step)
HOTP = HMAC-SHA1(K, T) truncated to 6-8 digits
OAuth 2.0 Device Authorization Grant (RFC 8628) for headless devices, CLI tools, and IoT clients that cannot open a browser.
Features:
- RFC 8628-compliant device authorization flow
- Automatic polling with
slow_downbackoff id_tokenvalidation via existingJWTValidator(OIDC)- Public and confidential client support (optional
client_secret) - TLS certificate verification always enforced
- High-level
authenticate()with progress callback for CLI UX - Testable via injected HTTP mock (
setHttpPostForTesting)
Flow Steps:
- Client calls
requestDeviceCode()→ receivesuser_codeandverification_uri - User visits
verification_uriand entersuser_codeon their browser - Client polls
pollForToken()atintervalsecond intervals - On success,
validateIdToken()returnsJWTClaims
Auth-layer bridge that enforces the zero-trust "never trust, always verify" principle by re-validating the caller's identity and network location for every inbound request (no session cache).
Features:
- Injectable
TokenVerifiercallback — wire inJWTValidator::parseAndValidatefor JWT re-validation - Per-identity CIDR network policies delegated to
security::ZeroTrustPolicyEnforcer - Configurable minimum trust-score threshold (default: 0.7)
- Audit logging via
AuthAuditLoggerfor every allow/deny decision - Thread-safe; forwards
getMetrics()from the underlying enforcer
Verification flow per request:
- Token re-validated via injected callback (no cached session)
- Source IP checked against registered
NetworkPolicyCIDR allow/deny lists - Composite trust score computed (identity + network + device + freshness)
- Score compared against
min_trust_score; denied if below threshold - Audit event emitted
Usage:
#include "auth/zero_trust_auth_verifier.h"
ZeroTrustAuthVerifier::Config cfg;
cfg.min_trust_score = 0.8;
ZeroTrustAuthVerifier verifier(cfg, [&jwt](const std::string& tok, const std::string& uid) {
try { return jwt.parseAndValidate(tok).sub == uid; }
catch (...) { return false; }
});
verifier.addNetworkPolicy({"corp", "alice", {"10.0.0.0/8"}, {}, true});
ZeroTrustAuthVerifier::Request req;
req.request_id = generate_uuid();
req.user_id = claims.sub;
req.token = bearer_token;
req.client_ip = peer_ip;
req.resource = "data";
req.action = "read";
auto decision = verifier.verify(req);
if (!decision.allowed) { return http_403(decision.reason); }OAuth 2.0 Authorization Code Grant with Proof Key for Code Exchange (RFC 7636) for public clients (native/mobile/SPA apps) that cannot safely store a client secret.
Features:
- RFC 7636-compliant PKCE with S256 challenge method (SHA-256)
- CSPRNG-based
code_verifiergeneration (96 random bytes → 128 Base64URL chars) code_verifieris never sent to the authorization endpointid_tokenvalidation via existingJWTValidator(OIDC)- TLS certificate verification always enforced
- Testable via injected HTTP mock (
setHttpPostForTesting) and random source (setRandBytesForTesting)
Flow Steps:
- Client calls
generateChallenge()→ receivescode_verifier/code_challengepair - Client redirects user to
buildAuthorizationUrl(challenge)at the authorization endpoint - User authenticates; authorization server redirects back with
authorization_code - Client calls
exchangeCode(authorization_code, code_verifier)to obtain tokens - Optionally, call
validateIdToken(token_response)to extract and verify identity claims
Manages the full lifecycle of user authentication sessions. All REST operations are
exposed via server/session_api_handler.cpp at /auth/sessions.
Features:
- Cryptographically random 128-bit session IDs (OpenSSL
RAND_bytes), encoded as 32 lowercase hex characters, prefixedsess_(total length: 37 characters, e.g.sess_a1b2c3d4…) - Configurable idle timeout (default: 8 h) and absolute lifetime (default: 30 days)
- Per-user concurrent session limit with LRU eviction of the oldest session (default: 10)
- Thread-safe: single
std::mutexguards the session map - Automatic pruning of expired entries on
createSession()andvalidateSession()
REST Endpoints (require auth:sessions scope):
| Method | Path | Description |
|---|---|---|
POST |
/auth/sessions |
Create a new session; returns session ID and metadata |
GET |
/auth/sessions |
List all active sessions for the authenticated user |
DELETE |
/auth/sessions/{session_id} |
Revoke a specific session (owner or admin:all scope) |
DELETE |
/auth/sessions |
Revoke all sessions except the one identified by current_session_id in the JSON body ("logout everywhere") |
Configuration (SessionManager::SessionLimits):
| Field | Default | Description |
|---|---|---|
max_sessions_per_user |
10 |
Max concurrent sessions; 0 = unlimited |
idle_timeout |
8 h |
Session invalidated if unused for this duration |
absolute_timeout |
30 days |
Hard session lifetime regardless of activity |
Usage:
#include "auth/session_manager.h"
SessionManager::SessionLimits lim;
lim.max_sessions_per_user = 5;
lim.idle_timeout = std::chrono::hours(1);
lim.absolute_timeout = std::chrono::hours(24);
SessionManager mgr(lim);
// Create
std::string sid = mgr.createSession("alice", "fp-xyz", "10.0.0.1", "MyApp/1.0");
// Validate (updates last_accessed_at)
auto result = mgr.validateSession(sid);
if (result.valid) { /* use result.session->user_id */ }
// Revoke single session
mgr.terminateSession(sid);
// Revoke all other sessions (e.g. "logout everywhere except this device")
int removed = mgr.terminateAllOtherSessions("alice", sid);
// List active sessions for user
auto sessions = mgr.listSessions("alice");Direct bind authentication against an LDAP or Active Directory server.
Features:
- LDAP simple bind and SASL GSSAPI bind
- Active Directory and OpenLDAP support
- Group membership lookup and role mapping
- Cross-platform: OpenLDAP (Linux) and WinLDAP (Windows)
- TLS/LDAPS enforcement
- Configurable with
-DTHEMIS_ENABLE_LDAP=ON(default)
Configuration (LDAPConfig):
| Field | Description |
|---|---|
host |
LDAP server hostname or URI |
port |
LDAP port (default: 389; LDAPS: 636) |
use_tls |
Enforce TLS (STARTTLS or LDAPS) |
bind_dn |
Service account DN for search bind |
bind_password |
Service account password |
search_base |
Base DN for user search |
user_filter |
LDAP filter to locate user entry |
group_attribute |
Attribute containing group memberships |
Static API key + secret authentication for service-to-service and programmatic access.
Features:
- Key ID + SHA-256-hashed secret credential pairs
- Per-key role assignment and optional expiry
- Constant-time comparison to prevent timing attacks
- Audit logging of every authentication attempt (PII-redacted)
Usage:
#include "auth/api_key_authenticator.h"
using namespace themis::auth;
ApiKeyAuthenticator auth;
ApiKeyAuthenticator::ApiKeyCredential cred;
cred.key_id = "svc-reports";
// Hash the secret before storing — never persist the raw secret
cred.key_secret_hash = sha256hex(raw_secret);
cred.roles = {"reports:read"};
auth.addCredential(cred);
// authenticate() compares the provided secret against the stored hash
auto result = auth.authenticate(key_id, raw_secret);
if (result.success) { /* use result.roles */ }Certificate-based mutual TLS authentication for service-to-service calls.
Features:
- Full X.509 certificate chain validation using OpenSSL
- Subject CN / SAN extraction as service identity
- Configurable CA certificate bundle and CRL checking
- Optional certificate expiry enforcement
- Maps certificate subject to roles via configurable mappings
Configuration (MTLSAuthenticator::Config):
| Field | Description |
|---|---|
ca_cert_pem |
PEM-encoded CA certificate(s) used to verify client certs |
require_client_cert |
Reject connections without a client certificate |
verify_expiry |
Enforce notAfter (default: true) |
principal_mappings |
Map CN/SAN patterns to internal role names |
SAML 2.0 Service Provider for SP-initiated and IdP-initiated SSO.
Features:
- SP-initiated AuthnRequest with HTTP-Redirect binding
- IdP-initiated Response with HTTP-POST binding
- XML signature verification (RSA-SHA256 / RSA-SHA512) via OpenSSL
- Encrypted SAML assertions (AES-256-CBC / AES-128-CBC)
- Attribute extraction and mapping to internal roles
- Single Logout (SLO) support
- Configurable AuthnContextClassRef (password, Kerberos, smart card, etc.)
- Thread-safe after construction
Configuration (SAMLConfig):
| Field | Description |
|---|---|
sp_entity_id |
SP entity ID URI |
sp_acs_url |
Assertion Consumer Service URL |
sp_slo_url |
Single Logout URL |
idp_entity_id |
IdP entity ID URI |
idp_sso_url |
IdP SSO endpoint |
idp_cert_pem |
PEM-encoded IdP signing certificate |
sp_private_key_pem |
SP private key for assertion decryption |
attribute_mappings |
Map SAML attributes to role names |
Hardware security key and platform authenticator (passkey) support via the WebAuthn standard.
Features:
- FIDO2 registration and authentication ceremonies
- ES256 (P-256) and RS256 public-key credential verification
- CBOR-encoded authenticatorData parsing
- Attestation verification (
none,packed,fido-u2f) - Replay protection via stored challenge nonces
- Supports YubiKey, Titan Key, TPM, Touch ID, Face ID, Windows Hello
- Thread-safe credential store
Registration flow:
- Server calls
startRegistration(user_id)→ returnsCredentialCreationOptions - Client creates credential (browser / native) and returns
AttestationObject - Server calls
completeRegistration(user_id, attestation_response)
Authentication flow:
- Server calls
startAuthentication(user_id)→ returnsCredentialRequestOptions - Client signs challenge → returns
AssertionResponse - Server calls
completeAuthentication(user_id, assertion_response)
Auto-configures JWT validation from any OIDC provider's discovery endpoint (RFC 8414).
Features:
- Fetches and caches
/.well-known/openid-configuration - Builds a
JWTValidatorfrom the discovered JWKS URI, issuer, and algorithms - Factory methods
createDeviceFlow()andcreatePKCEFlow()for OAuth 2.0 flows - Supports Keycloak, Okta, Auth0, Azure AD, and any standards-compliant provider
Usage:
#include "auth/oidc_provider.h"
using namespace themis::auth;
OIDCProviderConfig cfg;
cfg.discovery_url = "https://accounts.google.com/.well-known/openid-configuration";
cfg.client_id = "my-app";
OIDCProvider provider(cfg);
auto validator = provider.createJWTValidator();
auto claims = validator->parseAndValidate(bearer_token);Bridges multiple identity realms into a unified principal namespace.
Features:
- Register multiple
OIDCProviderorSAMLAuthenticatorinstances under named realms - Unified
authenticate(realm, token)entry point - Cross-realm principal normalization
- Per-realm role-mapping override
- Thread-safe realm registry
Configurable password complexity and history enforcement.
Features:
- Minimum length, uppercase, lowercase, digit, and symbol requirements
- Password history (prevents reuse of N previous passwords)
- Bcrypt / Argon2id hashing for stored passwords
- Configurable maximum age and forced rotation
- Pluggable into any local-credential authentication flow
┌────────┐ ┌─────────┐ ┌──────────┐
│ Client │ │ ThemisDB│ │ Keycloak │
└───┬────┘ └────┬────┘ └────┬─────┘
│ │ │
│ 1. Authenticate │ │
├──────────────────────────►│ │
│ (no token) │ │
│ │ │
│ 2. 401 Unauthorized │ │
│◄──────────────────────────┤ │
│ │ │
│ 3. Login │ │
├───────────────────────────┼──────────────────────────►│
│ │ │
│ 4. JWT Token │ │
│◄──────────────────────────┼───────────────────────────┤
│ │ │
│ 5. Request + Bearer Token│ │
├──────────────────────────►│ │
│ │ │
│ │ 6. Fetch JWKS (if cached │
│ │ expired) │
│ ├──────────────────────────►│
│ │ │
│ │ 7. JWKS Response │
│ │◄──────────────────────────┤
│ │ │
│ │ 8. Verify Signature │
│ │ 9. Extract Claims │
│ │ 10. Map to Roles │
│ │ │
│ 11. Success Response │ │
│◄──────────────────────────┤ │
│ │ │
┌────────┐ ┌─────────┐ ┌─────┐ ┌──────────┐
│ Client │ │ ThemisDB│ │ KDC │ │ LDAP │
└───┬────┘ └────┬────┘ └──┬──┘ └────┬─────┘
│ │ │ │
│ 1. kinit │ │ │
├─────────────────────┼──────────────────►│ │
│ │ │ │
│ 2. TGT │ │ │
│◄────────────────────┼───────────────────┤ │
│ │ │ │
│ 3. Request Service │ │ │
│ Ticket │ │ │
├─────────────────────┼──────────────────►│ │
│ │ │ │
│ 4. Service Ticket │ │ │
│◄────────────────────┼───────────────────┤ │
│ │ │ │
│ 5. Negotiate Header│ │ │
│ + Service Ticket│ │ │
├────────────────────►│ │ │
│ │ │ │
│ │ 6. Verify Ticket │ │
│ │ (keytab) │ │
│ │ │ │
│ │ 7. Query Groups │ │
│ ├───────────────────┼──────────────────►│
│ │ │ │
│ │ 8. Group List │ │
│ │◄──────────────────┼───────────────────┤
│ │ │ │
│ │ 9. Map to Roles │ │
│ │ │ │
│ 10. Success │ │ │
│◄────────────────────┤ │ │
│ │ │ │
┌────────┐ ┌─────────┐
│ User │ │ ThemisDB│
└───┬────┘ └────┬────┘
│ │
│ 1. Enable MFA Request │
├───────────────────────────────────────►│
│ │
│ │ 2. Generate Secret
│ │ 3. Generate Recovery Codes
│ │
│ 4. Secret + QR Code + Recovery Codes │
│◄───────────────────────────────────────┤
│ (otpauth://totp/...) │
│ │
│ 5. Scan QR with Authenticator App │
│ (Google Authenticator, Authy...) │
│ │
│ 6. Enter TOTP Code │
├───────────────────────────────────────►│
│ │
│ │ 7. Validate TOTP
│ │ (time window ±1)
│ │
│ 8. MFA Enabled Confirmation │
│◄───────────────────────────────────────┤
│ │
│ === Future Logins === │
│ │
│ 9. Login (username + password) │
├───────────────────────────────────────►│
│ │
│ 10. MFA Challenge │
│◄───────────────────────────────────────┤
│ │
│ 11. TOTP Code │
├───────────────────────────────────────►│
│ │
│ │ 12. Verify TOTP
│ │ (rate limiting)
│ │
│ 13. Login Success + Session Token │
│◄───────────────────────────────────────┤
│ │
┌─────────────┐ ┌─────────────┐ ┌──────────────────┐
│ CLI/Device │ │ ThemisDB │ │ Authorization │
│ (headless) │ │ (client) │ │ Server (OAuth AS)│
└──────┬──────┘ └──────┬──────┘ └────────┬─────────┘
│ │ │
│ 1. Login request │ │
├─────────────────────►│ │
│ │ │
│ │ 2. POST /device_auth │
│ ├───────────────────────►│
│ │ │
│ │ 3. device_code, │
│ │ user_code, │
│ │◄───────────────────────┤
│ │ verification_uri │
│ │ │
│ 4. Display user_code│ │
│◄─────────────────────┤ │
│ + verify URL │ │
│ │ │
│ [User opens browser, visits URL, enters code] │
│ │ │
│ │ 5. Poll POST /token │
│ ├───────────────────────►│
│ │ (authorization_pending│
│ │ or slow_down) │
│ │◄───────────────────────┤
│ │ │
│ │ 6. Poll POST /token │
│ ├───────────────────────►│
│ │ │
│ │ 7. access_token + │
│ │◄───────────────────────┤
│ │ id_token │
│ │ │
│ │ 8. Validate id_token │
│ │ (JWKS/JWTValidator)│
│ │ │
│ 9. JWTClaims (sub, │ │
│◄─────────────────────┤ │
│ email, roles) │ │
│ │ │
#include "auth/jwt_validator.h"
using namespace themis::auth;
// Basic configuration
JWTValidator jwt_validator("https://keycloak.example.com/realms/production/protocol/openid-connect/certs");
// Advanced configuration
JWTValidatorConfig config;
config.jwks_url = "https://keycloak.example.com/realms/production/protocol/openid-connect/certs";
config.expected_issuer = "https://keycloak.example.com/realms/production";
config.expected_audience = "themisdb-api";
config.cache_ttl = std::chrono::seconds(600); // Cache JWKS for 10 minutes
config.clock_skew = std::chrono::seconds(60); // Allow 60s clock skew
// Optional: require every token to carry a jti claim.
// When true, tokens without a jti are rejected with std::runtime_error("Missing required jti claim").
// Enable this in deployments where per-token revocation via TokenBlacklist is mandatory.
// When false (default), tokens without a jti are accepted but cannot be individually revoked;
// a warning is logged whenever a TokenBlacklist is attached and an incoming token has no jti.
config.require_jti = false;
JWTValidator jwt_validator(config);#include "auth/gssapi_authenticator.h"
using namespace themis::auth;
KerberosConfig krb_config;
krb_config.enabled = true;
krb_config.service_principal = "themisdb/db.example.com@EXAMPLE.COM";
krb_config.keytab_file = "/etc/themisdb/themisdb.keytab";
krb_config.krb5_config = "/etc/krb5.conf"; // Optional
krb_config.fallback_to_basic = true; // Allow fallback
// Principal-to-role mapping
krb_config.principal_mappings = {
{"admin@EXAMPLE.COM", "admin"},
{"*@EXAMPLE.COM", "user"},
{"service/*@EXAMPLE.COM", "service"},
{"dba/*@CORP.EXAMPLE.COM", "dba"}
};
GSSAPIAuthenticator gssapi_auth;
gssapi_auth.initialize(krb_config);#include "auth/mfa_authenticator.h"
using namespace themis::auth;
MFAAuthenticator::Config mfa_config;
mfa_config.time_step_seconds = 30; // 30-second time step (RFC 6238)
mfa_config.code_length = 6; // 6-digit codes
mfa_config.time_window = 1; // Accept ±1 time step (±30s)
mfa_config.recovery_codes_count = 8; // 8 recovery codes
mfa_config.issuer = "ThemisDB Production";
MFAAuthenticator mfa(mfa_config);#include "auth/oauth_device_flow.h"
using namespace themis::auth;
OAuthDeviceFlow::Config cfg;
cfg.device_authorization_endpoint = "https://auth.example.com/realms/prod/protocol/openid-connect/auth/device";
cfg.token_endpoint = "https://auth.example.com/realms/prod/protocol/openid-connect/token";
cfg.client_id = "themisdb-cli";
cfg.client_secret = ""; // empty for public clients
cfg.scopes = {"openid", "email", "profile"};
cfg.jwks_url = "https://auth.example.com/realms/prod/protocol/openid-connect/certs";
cfg.http_timeout_seconds = 10;
cfg.max_poll_interval_seconds = 30;
OAuthDeviceFlow flow(cfg);#include "auth/oauth_pkce_flow.h"
using namespace themis::auth;
OAuthPKCEFlow::Config cfg;
cfg.authorization_endpoint = "https://auth.example.com/realms/prod/protocol/openid-connect/auth";
cfg.token_endpoint = "https://auth.example.com/realms/prod/protocol/openid-connect/token";
cfg.client_id = "myapp-public";
cfg.redirect_uri = "myapp://callback";
cfg.scopes = {"openid", "email", "profile"};
cfg.jwks_url = "https://auth.example.com/realms/prod/protocol/openid-connect/certs";
cfg.http_timeout_seconds = 10;
OAuthPKCEFlow flow(cfg);ThemisDB verifies JWT signatures using RS256 (RSA with SHA-256):
// Automatic signature verification
auto claims = jwt_validator.parseAndValidate(bearer_token);
// Token is valid and signature verified at this pointImplementation Details:
- Fetches public keys from JWKS endpoint
- Caches keys with configurable TTL
- Verifies RSA signature using OpenSSL
- Validates key ID (kid) matches
- Checks algorithm is RS256
Distributed systems may have clock drift. ThemisDB handles this:
// Default: 60 seconds tolerance
config.clock_skew = std::chrono::seconds(60);
// Token is valid if:
// current_time >= (exp - clock_skew)
// current_time >= (nbf - clock_skew)Prevents brute force attacks on TOTP codes:
// Pseudocode - internal rate limiting
if (failed_attempts_in_last_minute[user_id] > 5) {
return AuthResult::RateLimited;
}Rate Limits:
- Max 5 failed attempts per minute per user
- 15-minute lockout after 10 failed attempts
- Exponential backoff on repeated failures
TOTP Time Window:
- Each TOTP code valid for 30 seconds only
- Time window prevents replay beyond ±30s
- Used codes can be tracked (optional)
JWT Token:
- Expiration (
exp) claim enforced - Not-before (
nbf) claim enforced - Tokens cannot be reused after expiration
For field-level encryption with user-specific keys:
// Derive user-specific key from Data Encryption Key (DEK)
auto user_key = JWTValidator::deriveUserKey(
dek, // Base encryption key
claims, // User's JWT claims
"sensitive_field" // Field name
);
// Key is derived using HKDF with:
// - DEK as input key material
// - User ID (sub claim) as salt
// - Field name as contextAccess Control:
// Check if user has access to group-encrypted data
bool has_access = JWTValidator::hasAccess(
claims, // User's JWT claims
"finance-team" // Encryption context (group name)
);
// Returns true if user.groups contains "finance-team"Control Objective: Logical and physical access controls restrict access to authorized personnel.
Implementation:
- ✅ Multi-factor authentication (TOTP)
- ✅ Role-based access control (RBAC)
- ✅ Audit logging of authentication events
- ✅ Session management with timeouts
- ✅ Encrypted credential storage
- ✅ Failed login attempt tracking
- ✅ Password complexity enforcement (external IdP)
Authenticator Assurance Level (AAL2)
Requirements:
- ✅ Multi-factor authentication
- ✅ Cryptographic mechanisms (TOTP, JWT signatures)
- ✅ Authenticated protected channel (TLS)
- ✅ Verifier impersonation resistance (Kerberos mutual auth)
- ✅ Replay resistance (time-bound tokens)
- ✅ Authentication intent (user interaction required)
Password Requirements:
- ✅ Minimum 8 characters (enforced by IdP)
- ✅ No composition rules (modern NIST guidance)
- ✅ Compromised password checking (IdP feature)
- ✅ Rate limiting on authentication attempts
GDPR:
- ✅ User data minimization (only necessary claims)
- ✅ Right to erasure (MFA data can be deleted)
- ✅ Data portability (standard JWT format)
- ✅ Audit logs for authentication events
HIPAA:
- ✅ Unique user identification (sub claim)
- ✅ Emergency access procedure (recovery codes)
- ✅ Automatic logoff (token expiration)
- ✅ Encryption and decryption (TLS + JWT)
#include "auth/jwt_validator.h"
#include <iostream>
using namespace themis::auth;
int main() {
// Initialize validator
JWTValidator validator("https://keycloak.example.com/realms/prod/protocol/openid-connect/certs");
// Token from Authorization header
std::string auth_header = "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...";
try {
// Parse and validate token
auto claims = validator.parseAndValidate(auth_header);
// Extract user information
std::cout << "User ID: " << claims.sub << std::endl;
std::cout << "Email: " << claims.email << std::endl;
std::cout << "Tenant: " << claims.tenant_id << std::endl;
// Check expiration
if (claims.isExpired()) {
std::cout << "Token expired!" << std::endl;
return 1;
}
// Access roles
std::cout << "Roles: ";
for (const auto& role : claims.roles) {
std::cout << role << " ";
}
std::cout << std::endl;
// Access groups
std::cout << "Groups: ";
for (const auto& group : claims.groups) {
std::cout << group << " ";
}
std::cout << std::endl;
} catch (const std::exception& e) {
std::cerr << "Authentication failed: " << e.what() << std::endl;
return 1;
}
return 0;
}#include "auth/gssapi_authenticator.h"
#include <iostream>
using namespace themis::auth;
int main() {
// Configure Kerberos
KerberosConfig config;
config.enabled = true;
config.service_principal = "themisdb/db.example.com@EXAMPLE.COM";
config.keytab_file = "/etc/themisdb/themisdb.keytab";
config.principal_mappings = {
{"admin@EXAMPLE.COM", "admin"},
{"*@EXAMPLE.COM", "user"}
};
// Initialize authenticator
GSSAPIAuthenticator auth;
if (!auth.initialize(config)) {
std::cerr << "Failed to initialize GSSAPI" << std::endl;
return 1;
}
// Authenticate token from client (Negotiate header)
std::string negotiate_token = "YIIGfwYGKwYBBQUCoIIG...";
auto result = auth.authenticateToken(negotiate_token);
if (result.success) {
std::cout << "Authentication successful!" << std::endl;
std::cout << "Principal: " << result.principal_name << std::endl;
std::cout << "Roles: ";
for (const auto& role : result.roles) {
std::cout << role << " ";
}
std::cout << std::endl;
} else {
std::cerr << "Authentication failed: " << result.error_message << std::endl;
return 1;
}
return 0;
}#include "auth/mfa_authenticator.h"
#include <iostream>
using namespace themis::auth;
int main() {
// Initialize MFA
MFAAuthenticator::Config config;
config.issuer = "ThemisDB Production";
MFAAuthenticator mfa(config);
// Generate enrollment for user
std::string user_id = "alice@example.com";
auto enrollment = mfa.generateEnrollment(user_id);
// Display QR code URI (scan with authenticator app)
std::string qr_uri = mfa.generateProvisioningURI(enrollment);
std::cout << "Scan this QR code with your authenticator app:" << std::endl;
std::cout << qr_uri << std::endl;
// Display recovery codes (save securely!)
std::cout << "\nRecovery Codes (save these!):" << std::endl;
for (const auto& code : enrollment.recovery_codes) {
std::cout << " " << code << std::endl;
}
// Save enrollment data (encrypted!)
auto json = enrollment.to_json();
// saveToDatabase(user_id, json);
// Verify TOTP code
std::cout << "\nEnter TOTP code from app: ";
std::string totp_code;
std::cin >> totp_code;
if (mfa.validateTOTP(enrollment.secret_base32, totp_code)) {
std::cout << "TOTP code valid! MFA enabled." << std::endl;
enrollment.enabled = true;
} else {
std::cout << "Invalid TOTP code." << std::endl;
}
return 0;
}#include "auth/mfa_authenticator.h"
#include <iostream>
using namespace themis::auth;
int main() {
MFAAuthenticator mfa;
// Load enrollment from database
// auto enrollment = loadFromDatabase(user_id);
MFAAuthenticator::EnrollmentData enrollment;
enrollment.user_id = "alice@example.com";
enrollment.secret_base32 = "JBSWY3DPEHPK3PXP"; // Example secret
enrollment.enabled = true;
// Get TOTP code from user
std::cout << "Enter TOTP code: ";
std::string totp_code;
std::cin >> totp_code;
// Validate TOTP
if (mfa.validateTOTP(enrollment.secret_base32, totp_code)) {
std::cout << "Authentication successful!" << std::endl;
return 0;
}
// Try recovery code if TOTP failed
std::cout << "Invalid TOTP. Enter recovery code: ";
std::string recovery_code;
std::cin >> recovery_code;
if (mfa.validateRecoveryCode(enrollment, recovery_code)) {
std::cout << "Recovery code accepted. Update your authenticator app!" << std::endl;
// saveToDatabase(user_id, enrollment); // Save updated enrollment
return 0;
}
std::cout << "Authentication failed." << std::endl;
return 1;
}#include "auth/jwt_validator.h"
#include "security/encryption.h"
#include <iostream>
using namespace themis::auth;
using namespace themis::security;
int main() {
// Master Data Encryption Key (from key management)
std::vector<uint8_t> dek = loadDEK();
// Validate JWT and get claims
JWTValidator validator("https://keycloak.example.com/realms/prod/protocol/openid-connect/certs");
auto claims = validator.parseAndValidate(bearer_token);
// Derive user-specific key for sensitive field
auto user_key = JWTValidator::deriveUserKey(dek, claims, "ssn");
// Encrypt data with user-specific key
std::string ssn = "123-45-6789";
auto encrypted_ssn = encrypt_aes_gcm(ssn, user_key);
// Only this user can decrypt (requires same JWT claims)
// Check group access for shared data
if (JWTValidator::hasAccess(claims, "finance-team")) {
// Derive group key
auto group_key = deriveGroupKey(dek, "finance-team");
auto encrypted_salary = encrypt_aes_gcm(salary, group_key);
// All finance team members can decrypt
}
return 0;
}#include "auth/oauth_device_flow.h"
#include <iostream>
#include <thread>
using namespace themis::auth;
int main() {
// Configure device flow (Keycloak example)
OAuthDeviceFlow::Config cfg;
cfg.device_authorization_endpoint =
"https://auth.example.com/realms/prod/protocol/openid-connect/auth/device";
cfg.token_endpoint =
"https://auth.example.com/realms/prod/protocol/openid-connect/token";
cfg.client_id = "themisdb-cli";
cfg.scopes = {"openid", "email"};
cfg.jwks_url =
"https://auth.example.com/realms/prod/protocol/openid-connect/certs";
OAuthDeviceFlow flow(cfg);
try {
// authenticate() handles the full flow:
// 1. Request device code
// 2. Show instructions to user
// 3. Poll until authorized or expired
// 4. Validate id_token and return JWTClaims
auto claims = flow.authenticate(
[](const OAuthDeviceFlow::DeviceCodeResponse& resp) {
std::cout << "\nTo authenticate, visit:\n "
<< resp.verification_uri << "\n\nEnter code: "
<< resp.user_code << "\n\nWaiting...\n";
}
);
std::cout << "Authenticated as: " << claims.email << std::endl;
std::cout << "User ID: " << claims.sub << std::endl;
for (const auto& role : claims.roles) {
std::cout << "Role: " << role << std::endl;
}
} catch (const AuthException& ex) {
std::cerr << "Authentication failed: "
<< ex.error().publicMessage() << std::endl;
return 1;
}
return 0;
}Step-by-step (manual polling):
// Step 1: request device code
auto resp = flow.requestDeviceCode();
std::cout << "Visit " << resp.verification_uri
<< " and enter: " << resp.user_code << std::endl;
// Step 2: poll until authorized
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(resp.interval));
OAuthDeviceFlow::PollStatus status;
auto token = flow.pollForToken(resp.device_code, status);
if (status == OAuthDeviceFlow::PollStatus::Authorized) {
auto claims = flow.validateIdToken(token);
std::cout << "Logged in as: " << claims.email << std::endl;
break;
}
if (status == OAuthDeviceFlow::PollStatus::SlowDown) {
resp.interval += 5; // back off per RFC 8628
}
// AuthorizationPending → keep polling; AccessDenied/ExpiredToken → throws
}#include "auth/oauth_pkce_flow.h"
#include <iostream>
using namespace themis::auth;
int main() {
// Configure PKCE flow (Keycloak example, public client)
OAuthPKCEFlow::Config cfg;
cfg.authorization_endpoint =
"https://auth.example.com/realms/prod/protocol/openid-connect/auth";
cfg.token_endpoint =
"https://auth.example.com/realms/prod/protocol/openid-connect/token";
cfg.client_id = "myapp-public";
cfg.redirect_uri = "myapp://callback";
cfg.scopes = {"openid", "email"};
cfg.jwks_url =
"https://auth.example.com/realms/prod/protocol/openid-connect/certs";
OAuthPKCEFlow flow(cfg);
try {
// Step 1: generate code_verifier and code_challenge
auto challenge = flow.generateChallenge();
// Step 2: redirect the user to the authorization URL
std::string state = "random-csrf-state";
std::string auth_url = flow.buildAuthorizationUrl(challenge, state);
std::cout << "Open this URL in a browser:\n " << auth_url << std::endl;
// Step 3: receive the authorization_code from the redirect URI (out of band)
std::string auth_code;
std::cout << "Enter the authorization code from the redirect: ";
std::cin >> auth_code;
// Step 4: exchange code for tokens
auto token = flow.exchangeCode(auth_code, challenge.code_verifier);
// Step 5: validate id_token and extract identity claims
auto claims = flow.validateIdToken(token);
std::cout << "Authenticated as: " << claims.email << std::endl;
std::cout << "User ID: " << claims.sub << std::endl;
} catch (const AuthException& ex) {
std::cerr << "Authentication failed: "
<< ex.error().publicMessage() << std::endl;
return 1;
}
return 0;
}DO:
- ✅ Use short-lived tokens (15-60 minutes)
- ✅ Implement token refresh mechanism
- ✅ Validate tokens on every request
- ✅ Cache JWKS with appropriate TTL
- ✅ Use HTTPS only (never HTTP)
- ✅ Validate issuer and audience claims
- ✅ Handle token expiration gracefully
DON'T:
- ❌ Store tokens in localStorage (use httpOnly cookies)
- ❌ Send tokens in URL query parameters
- ❌ Use long-lived tokens without refresh
- ❌ Skip signature verification
- ❌ Ignore expiration claims
- ❌ Trust client-provided claims without validation
DO:
- ✅ Use dedicated service account
- ✅ Secure keytab file (0400 permissions)
- ✅ Rotate keytabs regularly
- ✅ Monitor KDC connectivity
- ✅ Implement fallback authentication
- ✅ Log all authentication attempts
- ✅ Use specific principal mappings
DON'T:
- ❌ Use user accounts for service principals
- ❌ Store keytabs in version control
- ❌ Allow wildcard mappings to admin roles
- ❌ Disable fallback in production
- ❌ Ignore Kerberos errors silently
DO:
- ✅ Encrypt TOTP secrets at rest
- ✅ Generate sufficient recovery codes (8+)
- ✅ Display QR codes securely
- ✅ Implement rate limiting
- ✅ Allow account recovery process
- ✅ Support multiple TOTP devices
- ✅ Audit MFA enrollment/changes
DON'T:
- ❌ Store TOTP secrets in plaintext
- ❌ Skip time window validation
- ❌ Allow unlimited verification attempts
- ❌ Reuse recovery codes
- ❌ Send TOTP secrets via email/SMS
- ❌ Disable MFA without verification
DO:
- ✅ Use TLS 1.2+ for all connections
- ✅ Implement comprehensive audit logging
- ✅ Monitor authentication failures
- ✅ Set up alerting for suspicious activity
- ✅ Regular security updates
- ✅ Penetration testing
- ✅ Incident response plan
DON'T:
- ❌ Log sensitive data (tokens, secrets)
- ❌ Ignore failed authentication patterns
- ❌ Disable security features in production
- ❌ Use default credentials
- ❌ Mix authentication methods insecurely
DO:
- ✅ Cache JWKS responses (10+ minutes)
- ✅ Reuse GSSAPI contexts when possible
- ✅ Implement connection pooling for KDC
- ✅ Use async validation for non-critical paths
- ✅ Monitor authentication latency
- ✅ Set appropriate timeouts
DON'T:
- ❌ Fetch JWKS on every request
- ❌ Create new Kerberos contexts unnecessarily
- ❌ Block request threads during validation
- ❌ Use infinite timeouts
- ❌ Skip performance monitoring
DO:
- ✅ Return generic error messages to clients
- ✅ Log detailed errors server-side
- ✅ Implement retry logic for transient failures
- ✅ Handle network timeouts gracefully
- ✅ Provide clear user feedback
- ✅ Monitor error rates
DON'T:
- ❌ Expose internal errors to clients
- ❌ Leak existence of users via errors
- ❌ Retry indefinitely on failures
- ❌ Ignore KDC/IdP connectivity issues
- ❌ Crash on authentication failures
DO:
- ✅ Always set
jwks_urlsoid_tokensignatures are verified - ✅ Display both
verification_urianduser_codeclearly to the user - ✅ Respect the
intervalfrom the server; back off onslow_down - ✅ Treat the
device_codeas a short-lived secret (don't log it) - ✅ Catch
AuthExceptionand surfacepublicMessage()to the user - ✅ Use public-client mode (empty
client_secret) for CLI tools
DON'T:
- ❌ Log
device_code,access_token, orrefresh_token - ❌ Poll faster than the server-specified
interval - ❌ Disable TLS certificate verification (
SSL_VERIFYPEER) - ❌ Cache access tokens beyond their
expires_inlifetime - ❌ Request broader scopes than needed (principle of least privilege)
DO:
- ✅ Always set
jwks_urlwhen usingopenidscope soid_tokensignatures are verified - ✅ Generate a fresh
PKCEChallengefor every authorization request - ✅ Keep
code_verifiersecret; store it only for the duration of the flow - ✅ Use a random
stateparameter to prevent CSRF attacks - ✅ Catch
AuthExceptionand surfacepublicMessage()to the user - ✅ Use HTTPS redirect URIs in production
DON'T:
- ❌ Log or persist
code_verifier,access_token, orrefresh_token - ❌ Reuse a
code_verifieracross multiple authorization requests - ❌ Disable TLS certificate verification (
SSL_VERIFYPEER) - ❌ Use the
plainchallenge method — onlyS256is accepted - ❌ Request broader scopes than needed (principle of least privilege)
# Run auth module tests
cd /home/runner/work/ThemisDB/ThemisDB
cmake --build build --target test_auth
# Run specific test suites
./build/tests/test_jwt_validator
./build/tests/test_mfa_authenticator
./build/tests/test_gssapi_authenticator
./build/tests/test_oauth_device_flow
./build/tests/test_oauth_pkce_flow
# Run session management tests only (32 test cases)
ctest --test-dir build -R SessionManagerTests
# or via gtest filter:
./build/tests/themis_tests --gtest_filter=SessionManagerTest*:SessionApiHandlerTest*# Test with Keycloak
export KEYCLOAK_URL=https://keycloak.example.com
export KEYCLOAK_REALM=test
./build/tests/test_auth_integration
# Test with Kerberos
export KRB5_CONFIG=/etc/krb5.conf
export KRB5_KTNAME=/etc/themisdb/test.keytab
./build/tests/test_kerberos_integration# Benchmark JWT validation
./build/benchmarks/bench_jwt_validation
# Benchmark TOTP validation
./build/benchmarks/bench_totp_validationProblem: "JWKS HTTP error: 000"
- Cause: Cannot reach JWKS endpoint
- Solution: Check network connectivity, firewall rules, JWKS URL
Problem: "Invalid signature"
- Cause: Token signed with different key or algorithm
- Solution: Verify issuer matches, check key rotation, refresh JWKS cache
Problem: "Token expired"
- Cause: Clock skew or genuinely expired token
- Solution: Increase clock_skew tolerance, implement token refresh
Problem: "Cannot find KDC for realm"
- Cause: DNS or krb5.conf misconfiguration
- Solution: Check DNS SRV records, verify krb5.conf, test with kinit
Problem: "Cannot read keytab"
- Cause: File permissions or wrong path
- Solution: Check file exists, verify permissions (0400), check ownership
Problem: "Clock skew too great"
- Cause: Time difference between client/server/KDC
- Solution: Sync clocks with NTP, configure clock skew tolerance
Problem: "TOTP code invalid"
- Cause: Clock drift, wrong time window, or code already used
- Solution: Sync clocks, verify time_step_seconds, check time window
Problem: "Rate limited"
- Cause: Too many failed attempts
- Solution: Wait for cooldown period, use recovery code, check audit logs
For more detailed documentation, see:
- JWT Validator Implementation
- Kerberos Authentication Guide
- RBAC Authorization
- Security Hardening
- API Authentication
- Security Module (
../security/): Encryption, TLS, key management - API Module (
../api/): HTTP authentication middleware - Server Module (
../server/): Request handling and routing - Governance Module (
../governance/): Audit logging and compliance
For security issues, see SECURITY.md
For general support, see SUPPORT.md
-
Jones, M., Bradley, J., & Sakimura, N. (2015). JSON Web Token (JWT). RFC 7519. IETF. https://doi.org/10.17487/RFC7519
-
Hardt, D. (2012). The OAuth 2.0 Authorization Framework. RFC 6749. IETF. https://doi.org/10.17487/RFC6749
-
Sakimura, N., Bradley, J., Jones, M. B., de Medeiros, B., & Mortimore, C. (2014). OpenID Connect Core 1.0. OpenID Foundation. https://openid.net/specs/openid-connect-core-1_0.html
-
Bonneau, J., Herley, C., van Oorschot, P. C., & Stajano, F. (2012). The Quest to Replace Passwords: A Framework for Comparative Evaluation of Web Authentication Schemes. Proceedings of the 2012 IEEE Symposium on Security and Privacy, 553–567. https://doi.org/10.1109/SP.2012.44
-
Grassi, P. A., Garcia, M. E., & Fenton, J. L. (2017). Digital Identity Guidelines: Authentication and Lifecycle Management. NIST Special Publication 800-63B. National Institute of Standards and Technology. https://doi.org/10.6028/NIST.SP.800-63b