Claude
Skills
Sign in
Back

platxa-jwt-auth

Included with Lifetime
$97 forever

Generate RS256 JWT authentication with JWKS endpoint for Platxa services. Creates secure token infrastructure with key rotation support.

General

What this skill does


# Platxa JWT Authentication

Builder skill for generating RS256 JWT authentication infrastructure with JWKS endpoints.

## Overview

This skill generates production-ready JWT authentication components:

| Component | What Gets Generated |
|-----------|-------------------|
| **Key Infrastructure** | RSA key pair, Fernet encryption, secure storage |
| **Token Service** | Create/verify tokens for access, editor, service |
| **JWKS Endpoint** | Public key endpoint at `/.well-known/jwks.json` |
| **Key Rotation** | Rotation commands with 24-hour grace period |

## Workflow

### Step 1: Analyze Requirements

Determine which token types are needed:

| Token Type | Purpose | Default Expiry |
|------------|---------|----------------|
| **Access** | User authentication | 1 hour |
| **Editor** | Sidecar communication | 8 hours |
| **Service** | Service-to-service | 5 minutes |

### Step 2: Generate Key Infrastructure

Create RSA key pair with secure storage:

```python
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.fernet import Fernet
import base64
import os

def generate_key_pair():
    """Generate RSA key pair for JWT signing."""
    private_key = rsa.generate_private_key(
        public_exponent=65537,
        key_size=2048,
    )

    # Serialize private key
    private_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )

    # Serialize public key
    public_key = private_key.public_key()
    public_pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )

    return private_pem, public_pem

def encrypt_private_key(private_pem: bytes) -> str:
    """Encrypt private key with Fernet."""
    key = os.environ.get('JWT_ENCRYPTION_KEY')
    if not key:
        key = Fernet.generate_key().decode()
        print(f"Generated encryption key: {key}")
        print("Set JWT_ENCRYPTION_KEY environment variable!")

    fernet = Fernet(key.encode() if isinstance(key, str) else key)
    encrypted = fernet.encrypt(private_pem)
    return base64.b64encode(encrypted).decode()
```

### Step 3: Create Token Service

Implement token creation and verification:

```python
import jwt
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import uuid

class TokenService:
    def __init__(self, private_key: bytes, public_key: bytes, issuer: str):
        self.private_key = private_key
        self.public_key = public_key
        self.issuer = issuer
        self.algorithm = "RS256"

    def create_access_token(
        self,
        user_id: int,
        partner_id: int,
        instance_ids: list[int],
        roles: list[str],
        expires_delta: timedelta = timedelta(hours=1)
    ) -> str:
        """Create access token for user authentication."""
        now = datetime.utcnow()
        payload = {
            "iss": self.issuer,
            "sub": str(user_id),
            "aud": "platxa-api",
            "exp": now + expires_delta,
            "nbf": now,
            "iat": now,
            "jti": str(uuid.uuid4()),
            "type": "access",
            "user_id": user_id,
            "partner_id": partner_id,
            "instance_ids": instance_ids,
            "roles": roles,
        }
        return jwt.encode(payload, self.private_key, algorithm=self.algorithm)

    def create_editor_token(
        self,
        instance_name: str,
        user_id: int,
        permissions: list[str],
        expires_delta: timedelta = timedelta(hours=8)
    ) -> str:
        """Create editor token for Sidecar communication."""
        now = datetime.utcnow()
        payload = {
            "iss": self.issuer,
            "sub": f"editor:{instance_name}",
            "aud": f"sidecar-{instance_name}",
            "exp": now + expires_delta,
            "nbf": now,
            "iat": now,
            "jti": str(uuid.uuid4()),
            "type": "editor",
            "instance_name": instance_name,
            "user_id": user_id,
            "permissions": permissions,
        }
        return jwt.encode(payload, self.private_key, algorithm=self.algorithm)

    def create_service_token(
        self,
        service_name: str,
        allowed_endpoints: list[str],
        expires_delta: timedelta = timedelta(minutes=5)
    ) -> str:
        """Create service token for service-to-service auth."""
        now = datetime.utcnow()
        payload = {
            "iss": self.issuer,
            "sub": f"service:{service_name}",
            "aud": "platxa-internal",
            "exp": now + expires_delta,
            "nbf": now,
            "iat": now,
            "jti": str(uuid.uuid4()),
            "type": "service",
            "service_name": service_name,
            "allowed_endpoints": allowed_endpoints,
        }
        return jwt.encode(payload, self.private_key, algorithm=self.algorithm)

    def verify_token(
        self,
        token: str,
        expected_type: Optional[str] = None,
        expected_audience: Optional[str] = None
    ) -> Dict[str, Any]:
        """Verify and decode a JWT token."""
        try:
            payload = jwt.decode(
                token,
                self.public_key,
                algorithms=[self.algorithm],
                audience=expected_audience,
                issuer=self.issuer,
            )

            if expected_type and payload.get("type") != expected_type:
                raise jwt.InvalidTokenError(
                    f"Expected {expected_type} token, got {payload.get('type')}"
                )

            return payload
        except jwt.ExpiredSignatureError:
            raise ValueError("Token has expired")
        except jwt.InvalidTokenError as e:
            raise ValueError(f"Invalid token: {e}")
```

### Step 4: Implement JWKS Endpoint

Create the public key endpoint:

```python
from fastapi import FastAPI
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
import base64

app = FastAPI()

def get_jwk(public_key: RSAPublicKey, kid: str) -> dict:
    """Convert RSA public key to JWK format."""
    public_numbers = public_key.public_numbers()

    def int_to_base64url(n: int, length: int) -> str:
        data = n.to_bytes(length, byteorder='big')
        return base64.urlsafe_b64encode(data).rstrip(b'=').decode('ascii')

    return {
        "kty": "RSA",
        "use": "sig",
        "alg": "RS256",
        "kid": kid,
        "n": int_to_base64url(public_numbers.n, 256),
        "e": int_to_base64url(public_numbers.e, 3),
    }

@app.get("/.well-known/jwks.json")
async def jwks():
    """Return JSON Web Key Set with all active public keys."""
    keys = []
    for key_record in get_active_keys():  # From database
        keys.append(get_jwk(key_record.public_key, key_record.kid))

    return {
        "keys": keys
    }
```

### Step 5: Add Key Rotation

Implement rotation with grace period:

```python
from datetime import datetime, timedelta
import hashlib

def rotate_keys(db_session):
    """Rotate JWT signing keys with 24-hour grace period."""
    # Generate new key pair
    private_pem, public_pem = generate_key_pair()

    # Generate key ID from public key hash
    kid = hashlib.sha256(public_pem).hexdigest()[:16]

    # Encrypt and store
    encrypted_private = encrypt_private_key(private_pem)

    new_key = JWTKey(
        kid=kid,
        encrypted_private_key=encrypted_private,
        public_key=public_pem.decode(),
        created_at=datetime.utcnow(),
        expires_at=datetime.utcnow() + timedelta(days=30),
        is_active=True,
    )
    db_session.add(new_key)

    # Mark old keys for expiry (24-hour grace period)
    grace_period = datetime.utcnow() + timedelta(hours=24)
    db_session.query(JWTKey).filter(
        JWTKey.kid != kid,
   

Related in General