JWT Authentication
JWT (JSON Web Token) authentication provides secure, stateless authentication for user-facing applications and single sign-on (SSO) integrations.
Overview
JWT authentication in Olytix Core:
- Provides short-lived access tokens with automatic refresh
- Supports user identity and claims for RLS/masking policies
- Integrates with external identity providers
- Enables stateless authentication across distributed deployments
Token Flow
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Client │ │ Olytix Core │ │ Database │
└───────┬────────┘ └───────┬────────┘ └───────┬────────┘
│ │ │
│ 1. Login Request │ │
│ (username/password) │ │
│─────────────────────────►│ │
│ │ │
│ 2. Access + Refresh │ │
│ Tokens │ │
│◄─────────────────────────│ │
│ │ │
│ 3. API Request │ │
│ (Bearer token) │ │
│─────────────────────────►│ │
│ │ 4. Query with │
│ │ Security Context │
│ │─────────────────────────►│
│ │ │
│ 5. Response │◄─────────────────────────│
│◄─────────────────────────│ │
│ │ │
Obtaining Tokens
Direct Authentication
curl -X POST http://localhost:8000/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"username": "analyst@company.com",
"password": "secure-password"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZW1haWwiOiJhbmFseXN0QGNvbXBhbnkuY29tIiwicm9sZXMiOlsiYW5hbHlzdCJdLCJleHAiOjE3MDYxODAwMDB9.abc123",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "refresh_abc123xyz789"
}
Token Claims
Olytix Core JWT tokens include the following claims:
| Claim | Description | Example |
|---|---|---|
sub | Subject (user ID) | "user123" |
email | User email | "analyst@company.com" |
username | Display name | "Jane Analyst" |
roles | User roles | ["analyst", "finance"] |
groups | User groups | ["north_america"] |
attributes | Custom attributes | {"department": "Finance"} |
exp | Expiration time | 1706180000 |
iat | Issued at time | 1706176400 |
session_id | Session identifier | "sess_abc123" |
Using JWT Tokens
API Requests
curl -X POST http://localhost:8000/api/v1/query \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"metrics": ["monthly_revenue"],
"dimensions": ["orders.region"]
}'
Refreshing Tokens
curl -X POST http://localhost:8000/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "refresh_abc123xyz789"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.new-token...",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "refresh_new456def"
}
Revoking Tokens
curl -X POST http://localhost:8000/api/v1/auth/revoke \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "refresh_abc123xyz789"
}'
Configuration
Environment Variables
# JWT secret key (required)
OLYTIX_SECURITY__JWT_SECRET=your-256-bit-secret-key
# Token algorithm
OLYTIX_SECURITY__JWT_ALGORITHM=HS256
# Access token expiration (seconds)
OLYTIX_SECURITY__JWT_ACCESS_EXPIRY=3600
# Refresh token expiration (seconds)
OLYTIX_SECURITY__JWT_REFRESH_EXPIRY=604800
# Issuer claim
OLYTIX_SECURITY__JWT_ISSUER=olytix-core
# Audience claim
OLYTIX_SECURITY__JWT_AUDIENCE=olytix-core-api
Project Configuration
# olytix-core_project.yml
security:
jwt:
enabled: true
secret: ${JWT_SECRET}
algorithm: HS256
# Token lifetimes
access_token_expiry: 3600 # 1 hour
refresh_token_expiry: 604800 # 7 days
# Token validation
issuer: "olytix-core"
audience: "olytix-core-api"
verify_exp: true
verify_iat: true
# Claims mapping
claims:
user_id: "sub"
email: "email"
roles: "roles"
groups: "groups"
attributes: "attributes"
RSA Key Pair (Production)
For production deployments, use RSA key pairs instead of symmetric secrets:
# olytix-core_project.yml
security:
jwt:
algorithm: RS256
private_key_path: /secrets/jwt-private.pem
public_key_path: /secrets/jwt-public.pem
Generate keys:
# Generate private key
openssl genrsa -out jwt-private.pem 2048
# Extract public key
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem
External Identity Providers
Generic OIDC Integration
# olytix-core_project.yml
security:
jwt:
# Accept tokens from external IdP
external_jwks_url: "https://idp.company.com/.well-known/jwks.json"
issuer: "https://idp.company.com"
audience: "olytix-core-production"
# Map external claims to Olytix Core claims
claims_mapping:
user_id: "sub"
email: "email"
roles: "realm_access.roles"
groups: "groups"
attributes: "custom_attributes"
Okta Integration
security:
jwt:
external_jwks_url: "https://company.okta.com/oauth2/default/v1/keys"
issuer: "https://company.okta.com/oauth2/default"
audience: "api://olytix-core"
claims_mapping:
user_id: "sub"
email: "email"
roles: "groups"
Azure AD Integration
security:
jwt:
external_jwks_url: "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys"
issuer: "https://login.microsoftonline.com/{tenant-id}/v2.0"
audience: "api://{client-id}"
claims_mapping:
user_id: "oid"
email: "preferred_username"
roles: "roles"
groups: "groups"
Keycloak Integration
security:
jwt:
external_jwks_url: "https://keycloak.company.com/realms/olytix-core/protocol/openid-connect/certs"
issuer: "https://keycloak.company.com/realms/olytix-core"
audience: "olytix-core-client"
claims_mapping:
user_id: "sub"
email: "email"
roles: "realm_access.roles"
groups: "groups"
Security Context from JWT
JWT claims are automatically mapped to the security context:
# JWT Token Claims
{
"sub": "user123",
"email": "analyst@company.com",
"roles": ["analyst", "finance_team"],
"groups": ["north_america"],
"attributes": {
"department": "Finance",
"region": "NA",
"cost_center": "CC100"
}
}
# Resulting Security Context
SecurityContext:
user:
user_id: "user123"
email: "analyst@company.com"
roles: ["analyst", "finance_team"]
groups: ["north_america"]
attributes:
department: "Finance"
region: "NA"
cost_center: "CC100"
auth_method: "jwt"
This context is used for evaluating RLS and masking policies.
Error Handling
Expired Token
{
"error": {
"code": "TOKEN_EXPIRED",
"message": "Access token has expired",
"expired_at": "2025-01-20T12:00:00Z"
}
}
Solution: Use the refresh token to obtain a new access token.
Invalid Token
{
"error": {
"code": "INVALID_TOKEN",
"message": "Token signature verification failed"
}
}
Causes:
- Token was tampered with
- Wrong signing key
- Token from different environment
Invalid Issuer/Audience
{
"error": {
"code": "INVALID_TOKEN",
"message": "Token issuer does not match expected value"
}
}
Solution: Verify the IdP configuration matches Olytix Core settings.
Integration Examples
Python
import requests
from datetime import datetime, timedelta
class Olytix CoreClient:
def __init__(self, base_url: str, username: str, password: str):
self.base_url = base_url
self.username = username
self.password = password
self.access_token = None
self.refresh_token = None
self.token_expiry = None
def authenticate(self):
response = requests.post(
f"{self.base_url}/api/v1/auth/token",
json={"username": self.username, "password": self.password}
)
data = response.json()
self.access_token = data["access_token"]
self.refresh_token = data["refresh_token"]
self.token_expiry = datetime.now() + timedelta(seconds=data["expires_in"])
def refresh(self):
response = requests.post(
f"{self.base_url}/api/v1/auth/refresh",
json={"refresh_token": self.refresh_token}
)
data = response.json()
self.access_token = data["access_token"]
self.refresh_token = data["refresh_token"]
self.token_expiry = datetime.now() + timedelta(seconds=data["expires_in"])
def query(self, metrics, dimensions=None):
# Auto-refresh if token is expiring soon
if datetime.now() > self.token_expiry - timedelta(minutes=5):
self.refresh()
response = requests.post(
f"{self.base_url}/api/v1/query",
headers={"Authorization": f"Bearer {self.access_token}"},
json={"metrics": metrics, "dimensions": dimensions or []}
)
return response.json()
# Usage
client = Olytix CoreClient("http://localhost:8000", "analyst@company.com", "password")
client.authenticate()
data = client.query(["monthly_revenue"], ["orders.region"])
JavaScript
class Olytix CoreClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.accessToken = null;
this.refreshToken = null;
this.tokenExpiry = null;
}
async authenticate(username, password) {
const response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await response.json();
this.accessToken = data.access_token;
this.refreshToken = data.refresh_token;
this.tokenExpiry = Date.now() + (data.expires_in * 1000);
}
async refresh() {
const response = await fetch(`${this.baseUrl}/api/v1/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: this.refreshToken })
});
const data = await response.json();
this.accessToken = data.access_token;
this.refreshToken = data.refresh_token;
this.tokenExpiry = Date.now() + (data.expires_in * 1000);
}
async query(metrics, dimensions = []) {
// Auto-refresh if needed
if (Date.now() > this.tokenExpiry - 300000) {
await this.refresh();
}
const response = await fetch(`${this.baseUrl}/api/v1/query`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ metrics, dimensions })
});
return response.json();
}
}
// Usage
const client = new Olytix CoreClient('http://localhost:8000');
await client.authenticate('analyst@company.com', 'password');
const data = await client.query(['monthly_revenue'], ['orders.region']);
Security Best Practices
- Use HTTPS in production - Never transmit tokens over unencrypted connections
- Keep access tokens short-lived - 15-60 minutes is recommended
- Store refresh tokens securely - Use secure, HTTP-only cookies for web apps
- Implement token rotation - Issue new refresh tokens on each refresh
- Use RSA keys in production - Symmetric secrets are harder to rotate
- Validate all claims - Verify issuer, audience, and expiration