← Week 1: The SPIFFE Model

Day 5: JWT-SVIDs — Federated Identity for HTTP/API Contexts

Phase 4 · August 9, 2026

← Week 1: The SPIFFE Model

Agenda (2–3 hours)

  • Read (45 min): JWT-SVID spec; JWT-SVID section in SPIRE docs
  • Study (45 min): JWT structure, validation, audience claim
  • Challenge (60 min): Parse and validate a JWT-SVID structure in Rust
← Week 1: The SPIFFE Model

Why JWT-SVIDs?

X.509-SVIDs are ideal for mTLS — the cert is presented during the TLS handshake.

But many HTTP APIs use Bearer tokens, not mTLS:

  • API Gateway + Lambda (no mTLS at the API layer)
  • Service-to-service REST calls over HTTPS (server auth only)
  • Any context where you can't control TLS client cert presentation

JWT-SVIDs solve this: a short-lived signed JWT carrying the SPIFFE ID,
presented as a Bearer token in the Authorization header.

Your provisioning Lambda probably falls in this category for some of its calls.

← Week 1: The SPIFFE Model

JWT-SVID Structure

A JWT-SVID is a standard JWS (JSON Web Signature) with specific claims:

Header:
{
  "alg": "ES256",
  "typ": "JWT",
  "kid": "<key ID of the signing key>"
}

Payload:
{
  "sub": "spiffe://leo.amazon.com/ns/prod/svc/provisioning",
  "aud": ["spiffe://leo.amazon.com/ns/prod/svc/ca-wrapper"],
  "exp": 1754700000,
  "iat": 1754696400
}

Key constraints from the spec:

  • sub MUST be the SPIFFE ID
  • aud MUST be present; MUST match the expected audience on validation
  • exp MUST be present; validity SHOULD be ≤ 5 minutes
  • Algorithm MUST be ES256, ES384, ES512, or PS256/PS384/PS512 (no none, no HS256)
← Week 1: The SPIFFE Model

JWT-SVID vs. X.509-SVID

Property X.509-SVID JWT-SVID
Transport TLS handshake (mTLS) HTTP Bearer token
Validity ~1 hour ~5 minutes
Revocability Short validity Short validity
Verification requires Trust bundle (CA certs) JWKS endpoint or bundle
Key type Asymmetric (ECDSA) Asymmetric (ECDSA/RSA)
Typical use Service mesh / gRPC HTTP API calls
Replay window mTLS prevents replay aud + exp limits blast radius
← Week 1: The SPIFFE Model

Fetching a JWT-SVID

Via the Workload API:

// spiffe crate
let jwt_svid = client.fetch_jwt_svid(
    &["spiffe://leo.amazon.com/ns/prod/svc/ca-wrapper"], // audience
).await?;

let token: &str = jwt_svid.token(); // the raw JWT string
// Include in HTTP header:
// Authorization: Bearer <token>

The audience is the SPIFFE ID of the recipient service.
The recipient validates that its own SPIFFE ID appears in aud.

← Week 1: The SPIFFE Model

Validating a JWT-SVID

The recipient service:

use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize)]
struct SvidClaims {
    sub: String,   // the caller's SPIFFE ID
    aud: Vec<String>,
    exp: u64,
    iat: u64,
}

fn validate_jwt_svid(token: &str, expected_aud: &str, jwks: &Jwks)
    -> Result<SvidClaims, Error>
{
    let header = decode_header(token)?;
    let key = jwks.key_for_kid(header.kid.as_deref().unwrap_or(""))?;

    let mut validation = Validation::new(Algorithm::ES256);
    validation.set_audience(&[expected_aud]);
    
    let data = decode::<SvidClaims>(token, &DecodingKey::from_ec_pem(&key)?, &validation)?;
    Ok(data.claims)
}
← Week 1: The SPIFFE Model

JWT-SVID and Lambda

Lambda + API Gateway is a natural JWT-SVID use case:

  • API Gateway doesn't support client certs (mTLS) by default for Lambda
  • A Lambda invoking another Lambda can attach a JWT-SVID Bearer token
  • The receiving Lambda validates the JWT against the trust bundle's JWKS

This is how SPIFFE extends to serverless — the agent runs as a sidecar or
the Lambda fetches via an OIDC bridge (covered in Week 3).

For now: understand the JWT-SVID structure and validation; you'll integrate
with Lambda's actual mechanism in Phase 4, Week 3.

← Week 1: The SPIFFE Model

Challenge Assignment

Add a jwt module to spiffe-demo:

  1. Define a JwtSvid struct with token: String, spiffe_id: String, audience: Vec<String>, expiry: i64
  2. Implement JwtSvid::parse(token: &str) -> Result<JwtSvid>:
    • Decode without signature verification (split on ., base64-decode payload)
    • Extract sub, aud, exp claims
    • Return them in the struct
  3. Add a jwt-parse subcommand to spiffe-demo that:
    • Accepts a JWT string as an argument
    • Prints the SPIFFE ID, audience, and expiry
    • Validates that sub starts with spiffe://
  4. Generate a test JWT by hand (base64-encode a valid payload + dummy header) and test your parser against it

Note: no signature verification in this exercise — that requires the trust bundle JWKS.
Day 17 covers full SVID validation.

← Week 1: The SPIFFE Model

Resources

  • JWT-SVID spec: github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md
  • JOSE/JWT overview: RFC 7519 (JWT), RFC 7515 (JWS)
  • jsonwebtoken Rust crate: docs.rs/jsonwebtoken
  • spiffe crate JWT methods: docs.rs/spiffe (fetch_jwt_svid)