platxa-jwt-auth
Generate RS256 JWT authentication with JWKS endpoint for Platxa services. Creates secure token infrastructure with key rotation support.
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
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.