Skip to main content

JWT Authentication

For Data Analysts

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:

ClaimDescriptionExample
subSubject (user ID)"user123"
emailUser email"analyst@company.com"
usernameDisplay name"Jane Analyst"
rolesUser roles["analyst", "finance"]
groupsUser groups["north_america"]
attributesCustom attributes{"department": "Finance"}
expExpiration time1706180000
iatIssued at time1706176400
session_idSession 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

  1. Use HTTPS in production - Never transmit tokens over unencrypted connections
  2. Keep access tokens short-lived - 15-60 minutes is recommended
  3. Store refresh tokens securely - Use secure, HTTP-only cookies for web apps
  4. Implement token rotation - Issue new refresh tokens on each refresh
  5. Use RSA keys in production - Symmetric secrets are harder to rotate
  6. Validate all claims - Verify issuer, audience, and expiration

Next Steps