---
title: "JWT Authentication"
description: "JSON Web Token authentication for TraceRTM API access and service-to-service communication"
---

# JWT Authentication

TraceRTM uses JSON Web Tokens (JWT) for secure, stateless authentication. JWTs are issued by the TraceRTM authorization server and can be validated by any service with access to the public key.

## JWT Overview

```
┌─────────────────────────────────────────────────────────────────────────┐
│                        JWT Structure                                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Header.Payload.Signature                                               │
│                                                                          │
│   ┌─────────────┐   ┌─────────────────┐   ┌─────────────────────────┐   │
│   │   Header    │   │     Payload     │   │       Signature         │   │
│   │ (Algorithm) │ . │ (Claims/Data)   │ . │ (Verification)          │   │
│   └─────────────┘   └─────────────────┘   └─────────────────────────┘   │
│                                                                          │
│   Base64URL encoded, separated by dots                                   │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
```

## Token Structure

### Header

```json
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key_abc123"
}
```

| Field | Description |
|-------|-------------|
| `alg` | Algorithm used (RS256, ES256) |
| `typ` | Token type (always "JWT") |
| `kid` | Key ID for signature verification |

### Payload (Claims)

```json
{
  "iss": "https://auth.tracertm.dev",
  "sub": "user_abc123",
  "aud": "https://api.tracertm.dev",
  "exp": 1704067200,
  "iat": 1704063600,
  "nbf": 1704063600,
  "jti": "token_xyz789",
  "scope": "read:projects write:items",
  "org_id": "org_456",
  "role": "editor",
  "agent_id": null
}
```

### Standard Claims

| Claim | Name | Description |
|-------|------|-------------|
| `iss` | Issuer | Token issuer (TraceRTM auth server) |
| `sub` | Subject | User or agent ID |
| `aud` | Audience | Intended recipient (API server) |
| `exp` | Expiration | Expiration timestamp (Unix) |
| `iat` | Issued At | Token creation timestamp |
| `nbf` | Not Before | Token not valid before this time |
| `jti` | JWT ID | Unique token identifier |

### Custom Claims

| Claim | Description |
|-------|-------------|
| `scope` | Permission scopes granted |
| `org_id` | Organization ID |
| `role` | User role (admin, editor, viewer) |
| `agent_id` | AI agent ID (if applicable) |
| `project_ids` | Allowed project IDs (optional) |

## Using JWTs

### API Requests

```bash
# Include JWT in Authorization header
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
  https://api.tracertm.dev/v1/projects
```

### Python SDK

```python
from tracertm import TraceRTMClient

# Using JWT directly
client = TraceRTMClient(
    access_token="eyJhbGciOiJSUzI1NiIs..."
)

# With automatic refresh
client = TraceRTMClient(
    access_token=jwt_token,
    refresh_token=refresh_token,
    auto_refresh=True
)

projects = await client.projects.list()
```

### JavaScript SDK

```typescript
import { TraceRTMClient } from '@tracertm/sdk'

const client = new TraceRTMClient({
  accessToken: 'eyJhbGciOiJSUzI1NiIs...'
})

const projects = await client.projects.list()
```

## Token Validation

### Server-Side Validation

```python
import jwt
from jwt import PyJWKClient
from datetime import datetime

# TraceRTM JWKS endpoint
JWKS_URL = "https://auth.tracertm.dev/.well-known/jwks.json"
AUDIENCE = "https://api.tracertm.dev"
ISSUER = "https://auth.tracertm.dev"

# Create JWKS client (caches keys)
jwks_client = PyJWKClient(JWKS_URL)

def validate_token(token: str) -> dict:
    """Validate JWT and return claims."""
    try:
        # Get signing key from JWKS
        signing_key = jwks_client.get_signing_key_from_jwt(token)

        # Decode and validate
        claims = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256", "ES256"],
            audience=AUDIENCE,
            issuer=ISSUER,
            options={
                "require": ["exp", "iat", "sub", "scope"],
                "verify_exp": True,
                "verify_iat": True,
                "verify_aud": True,
                "verify_iss": True
            }
        )

        return claims

    except jwt.ExpiredSignatureError:
        raise AuthenticationError("Token has expired")
    except jwt.InvalidAudienceError:
        raise AuthenticationError("Invalid token audience")
    except jwt.InvalidIssuerError:
        raise AuthenticationError("Invalid token issuer")
    except jwt.InvalidTokenError as e:
        raise AuthenticationError(f"Invalid token: {e}")

# Usage
claims = validate_token(request.headers["Authorization"].split(" ")[1])
user_id = claims["sub"]
scopes = claims["scope"].split(" ")
```

### FastAPI Middleware

```python
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

app = FastAPI()
security = HTTPBearer()

async def get_current_user(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
    """Validate JWT and return user claims."""
    try:
        claims = validate_token(credentials.credentials)
        return claims
    except AuthenticationError as e:
        raise HTTPException(status_code=401, detail=str(e))

def require_scope(required_scope: str):
    """Dependency to require specific scope."""
    async def check_scope(claims: dict = Depends(get_current_user)):
        scopes = claims.get("scope", "").split(" ")
        if required_scope not in scopes and "*" not in scopes:
            raise HTTPException(
                status_code=403,
                detail=f"Missing required scope: {required_scope}"
            )
        return claims
    return check_scope

# Protected endpoints
@app.get("/projects")
async def list_projects(
    claims: dict = Depends(require_scope("read:projects"))
):
    user_id = claims["sub"]
    return await get_user_projects(user_id)

@app.post("/projects/{project_id}/items")
async def create_item(
    project_id: str,
    data: ItemCreate,
    claims: dict = Depends(require_scope("write:items"))
):
    return await create_project_item(project_id, data, claims["sub"])
```

### Node.js Validation

```typescript
import jwt from 'jsonwebtoken'
import jwksClient from 'jwks-rsa'

const client = jwksClient({
  jwksUri: 'https://auth.tracertm.dev/.well-known/jwks.json',
  cache: true,
  rateLimit: true
})

function getKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) {
  client.getSigningKey(header.kid!, (err, key) => {
    if (err) return callback(err)
    const signingKey = key?.getPublicKey()
    callback(null, signingKey)
  })
}

export function validateToken(token: string): Promise<jwt.JwtPayload> {
  return new Promise((resolve, reject) => {
    jwt.verify(
      token,
      getKey,
      {
        algorithms: ['RS256', 'ES256'],
        audience: 'https://api.tracertm.dev',
        issuer: 'https://auth.tracertm.dev'
      },
      (err, decoded) => {
        if (err) reject(err)
        else resolve(decoded as jwt.JwtPayload)
      }
    )
  })
}

// Express middleware
export async function authMiddleware(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const authHeader = req.headers.authorization

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' })
  }

  const token = authHeader.split(' ')[1]

  try {
    const claims = await validateToken(token)
    req.user = claims
    next()
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' })
  }
}
```

## Creating JWTs (Service Accounts)

For service-to-service authentication, you can create your own JWTs:

```python
import jwt
from datetime import datetime, timedelta
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend

# Load your private key (provided by TraceRTM)
with open("private_key.pem", "rb") as f:
    private_key = serialization.load_pem_private_key(
        f.read(),
        password=None,
        backend=default_backend()
    )

def create_service_jwt(
    service_id: str,
    scopes: list[str],
    expires_in_minutes: int = 60
) -> str:
    """Create a signed JWT for service authentication."""
    now = datetime.utcnow()

    payload = {
        "iss": service_id,
        "sub": service_id,
        "aud": "https://api.tracertm.dev",
        "iat": now,
        "exp": now + timedelta(minutes=expires_in_minutes),
        "scope": " ".join(scopes),
        "type": "service"
    }

    return jwt.encode(
        payload,
        private_key,
        algorithm="RS256",
        headers={"kid": "your_key_id"}
    )

# Usage
token = create_service_jwt(
    service_id="service_cicd",
    scopes=["read:projects", "write:items"]
)
```

## Token Refresh

```python
from tracertm.oauth import OAuthClient

oauth = OAuthClient(
    client_id="your_client_id",
    client_secret="your_client_secret"
)

# Refresh an expired access token
new_tokens = await oauth.refresh_token(refresh_token)

print(f"New access token: {new_tokens.access_token}")
print(f"Expires in: {new_tokens.expires_in} seconds")
```

### Automatic Refresh

```python
from tracertm import TraceRTMClient

# SDK handles refresh automatically
client = TraceRTMClient(
    access_token=access_token,
    refresh_token=refresh_token,
    client_id="your_client_id",
    client_secret="your_client_secret",
    auto_refresh=True,
    refresh_threshold_seconds=300  # Refresh 5 min before expiry
)

# Token is automatically refreshed when needed
projects = await client.projects.list()
```

## Token Introspection

Check token validity without decoding:

```python
# Introspect a token
info = await oauth.introspect(access_token)

print(f"Active: {info['active']}")
print(f"Subject: {info['sub']}")
print(f"Scopes: {info['scope']}")
print(f"Expires: {info['exp']}")
print(f"Client: {info['client_id']}")
```

## Security Best Practices

### 1. Always Validate Tokens

```python
# ALWAYS verify signature, expiration, and claims
claims = validate_token(token)

# NEVER trust claims without validation
# BAD:
claims = jwt.decode(token, options={"verify_signature": False})  # INSECURE!
```

### 2. Use Short Expiration Times

```python
# Good: Short-lived access tokens
access_token_lifetime = timedelta(hours=1)

# Use refresh tokens for longer sessions
refresh_token_lifetime = timedelta(days=30)
```

### 3. Secure Token Storage

```typescript
// Browser: Use httpOnly cookies or session storage
// GOOD:
document.cookie = `token=${jwt}; Secure; HttpOnly; SameSite=Strict`

// BAD: localStorage is vulnerable to XSS
localStorage.setItem('token', jwt)  // Avoid if possible
```

### 4. Validate All Claims

```python
def validate_claims(claims: dict, required_scope: str):
    # Verify issuer
    if claims["iss"] != "https://auth.tracertm.dev":
        raise SecurityError("Invalid issuer")

    # Verify audience
    if claims["aud"] != "https://api.tracertm.dev":
        raise SecurityError("Invalid audience")

    # Verify scope
    scopes = claims["scope"].split(" ")
    if required_scope not in scopes:
        raise SecurityError(f"Missing scope: {required_scope}")

    # Verify not before
    if claims.get("nbf") and datetime.utcnow().timestamp() < claims["nbf"]:
        raise SecurityError("Token not yet valid")
```

### 5. Handle Token Revocation

```python
# Check revocation list for sensitive operations
async def is_token_revoked(jti: str) -> bool:
    return await redis.sismember("revoked_tokens", jti)

async def validate_with_revocation(token: str) -> dict:
    claims = validate_token(token)

    if await is_token_revoked(claims["jti"]):
        raise AuthenticationError("Token has been revoked")

    return claims
```

## JWKS Endpoint

TraceRTM publishes public keys at the JWKS endpoint:

```bash
curl https://auth.tracertm.dev/.well-known/jwks.json
```

Response:

```json
{
  "keys": [
    {
      "kty": "RSA",
      "kid": "key_abc123",
      "use": "sig",
      "alg": "RS256",
      "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmb...",
      "e": "AQAB"
    }
  ]
}
```

## Related Documentation

- [JWT Claims](/docs/api-reference/authentication/jwt-claims) - Complete claims reference
- [OAuth 2.0](/docs/api-reference/authentication/oauth) - OAuth flows
- [API Keys](/docs/api-reference/authentication/api-keys) - Alternative authentication
- [Security](/docs/wiki/guides/security) - Security best practices

