Skip to content

Middleware

The SDK provides two middleware classes. Use AuthzMiddleware in authz mode (dual-token) or JWTAuthMiddleware in proxy mode (single JWT). If you use the Sentinel class, protect() adds the correct one automatically.

AuthzMiddleware (AuthZ Mode)

Validates both an IdP token and a Sentinel authorization token on each request. Checks that the sub claims match across tokens (binding verification).

Headers

Header Content
Authorization Bearer <idp_token>
X-Authz-Token <sentinel_authz_token>

Setup

from sentinel_auth.authz_middleware import AuthzMiddleware

app.add_middleware(
    AuthzMiddleware,
    idp_jwks_url="https://www.googleapis.com/oauth2/v3/certs",
    sentinel_public_key=sentinel_pem,
    exclude_paths=["/health", "/docs", "/openapi.json"],
)

Or pass a Sentinel instance (keys are read lazily -- safe to call before lifespan):

app.add_middleware(
    AuthzMiddleware,
    sentinel_instance=sentinel,
)

Constructor Parameters

Parameter Type Default Description
idp_public_key str \| None None PEM key for IdP token validation
idp_jwks_url str \| None None JWKS endpoint for IdP tokens (handles key rotation)
sentinel_public_key str \| None None PEM key for authz token validation
sentinel_instance Sentinel \| None None Sentinel instance (reads keys lazily)
idp_algorithm str "RS256" IdP token signing algorithm
sentinel_algorithm str "RS256" Authz token signing algorithm
sentinel_audience str "sentinel:authz" Expected aud claim in authz token
exclude_paths list[str] \| None ["/health", "/docs", "/openapi.json"] Paths that bypass authentication

Either sentinel_public_key or sentinel_instance is required. For IdP validation, the middleware uses idp_jwks_url or idp_public_key (from the params or from the Sentinel instance).

Request State

After successful validation, the middleware sets:

Attribute Type Description
request.state.user AuthenticatedUser User built from authz token claims + IdP email/name
request.state.token str The Sentinel authz token
request.state.idp_token str The original IdP token

Validation Steps

  1. Extract IdP token from Authorization: Bearer ...
  2. Extract authz token from X-Authz-Token
  3. Validate IdP token (signature, expiry) via JWKS or static key
  4. Validate authz token (signature, expiry, audience sentinel:authz)
  5. Verify binding: IdP sub must match authz idp_sub
  6. Build AuthenticatedUser and set on request.state

OPTIONS requests are passed through without validation.


JWTAuthMiddleware (Proxy Mode)

Validates a single Sentinel-issued JWT. Used when Sentinel handles the full OAuth flow.

Setup

from sentinel_auth.middleware import JWTAuthMiddleware

# Recommended: fetch key from JWKS automatically
app.add_middleware(
    JWTAuthMiddleware,
    base_url="https://sentinel.example.com",
)

# Alternative: static PEM key
app.add_middleware(
    JWTAuthMiddleware,
    public_key=Path("keys/public.pem").read_text(),
)

Constructor Parameters

Parameter Type Default Description
base_url str \| None None Sentinel URL. JWKS endpoint derived as {base_url}/.well-known/jwks.json
public_key str \| None None RSA PEM key for air-gapped deployments
jwks_url str \| None None Explicit JWKS URL (non-standard paths only)
algorithm str "RS256" JWT signing algorithm
audience str "sentinel:access" Expected aud claim
exclude_paths list[str] \| None ["/health", "/docs", "/openapi.json"] Paths that bypass authentication
allowed_workspaces set[str] \| None None Workspace IDs permitted. None allows all

One of base_url, jwks_url, or public_key is required. The JWKS key is fetched lazily on first request and cached.

Request State

Attribute Type Description
request.state.user AuthenticatedUser User built from JWT claims
request.state.token str The raw JWT string

Excluded Paths

Both middleware classes skip authentication for excluded paths. Matching uses exact match or prefix with / boundary:

# Path "/health" matches:     /health, /health/ready
# Path "/docs" matches:       /docs, /docs/oauth2-redirect
# Path "/docs" does NOT match: /documents

Error Responses

Both middleware classes return JSON errors:

Status Detail When
401 Missing IdP token / Missing or invalid Authorization header No Authorization: Bearer header
401 Missing authz token No X-Authz-Token header (authz mode only)
401 IdP token expired / Token has expired Token exp in the past
401 Invalid IdP token / Invalid token Bad signature, malformed, wrong audience
401 Token binding mismatch: idp_sub does not match IdP sub != authz idp_sub (authz mode)
401 Invalid token claims Missing required claims in payload
403 Workspace not permitted for this service Workspace not in allowed_workspaces
500 Authentication service unavailable JWKS fetch failed (proxy mode)