PermDock
Standards

JOSE (JWT, JWS, JWE, JWK, JWA)

The JSON Object Signing and Encryption family as PermDock consumes it (bearer JWTs, JWKS, cnf bindings) and produces it (JWS-signed snapshots, approval tokens, decision exports), with the RFC 8725 / rfc8725bis checklist permdock/jwt follows and the interoperability contract that lets any language verify what PermDock signs.

permdock/jwt holds the consuming half (joseTokenVerifier, the algorithm allow-list, JWKS and Discovery caching, the JWE posture, subjectFromIntrospection) and the producing half (joseTokenSigner, permdock.snapshot({ signer })). permdock/ssf uses the same verifier for SET-style events, permdock/approvals signs its optional approval token, and permdock/cloud distributes signed snapshots.

What it is

JOSE is the IETF family of formats for signing, encrypting and describing keys in JSON. Every OpenID Connect token, every RFC 9068 access token, every DPoP proof and every Shared Signals event is one of these:

RFCNameWhat it definesWhere PermDock meets it
RFC 7515JWS, JSON Web SignatureA signed payload in compact (header.payload.signature) or JSON serialization; the header parameters alg, kid, typ, cty, jku, jwk, x5u, x5c, x5t#S256, critEvery token permdock/jwt verifies and everything PermDock signs
RFC 7516JWE, JSON Web EncryptionAn encrypted payload with alg (key management) and enc (content encryption); a nested JWT is a JWS inside a JWE with cty: JWTAccepted only when decryptionKeys is configured
RFC 7517JWK, JSON Web KeyA key as JSON (kty, use, key_ops, alg, kid, x5c) and a JWK Set (keys)The jwks option, the jwks_uri Discovery returns, cnf.jwk, the /.well-known/jwks.json PermDock Cloud publishes
RFC 7518JWA, JSON Web AlgorithmsThe alg and enc identifiers (RS256, PS256, ES256, HS256, none, RSA-OAEP, A256GCM) and the key types RSA, EC, octThe algorithms allow-list
RFC 8037CFRG curves in JOSEkty: OKP with crv: Ed25519, Ed448, X25519, X448; the polymorphic alg: EdDSAEd25519 keys in JWKS
RFC 9864Fully-specified algorithms for JOSE and COSERegisters Ed25519 and Ed448 as alg values and deprecates EdDSA, whose meaning depended on the keyThe value the allow-list uses; the reason permdock doctor warns on EdDSA
RFC 7519JWT, JSON Web TokenRegistered claims iss, sub, aud, exp, nbf, iat, jti; the typ: JWT header; NumericDateThe claim set subjectFromJwt maps and every payload PermDock signs
RFC 7800Proof-of-possession key semanticsThe cnf claim with jwk, jwe, kid, jku; DPoP adds jkt (RFC 9449), mTLS adds x5t#S256 (RFC 8705)Binding on the subject
RFC 8725 and rfc8725bisJWT Best Current PracticesThe checklist belowWhat permdock/jwt enforces by default
RFC 9068JWT profile for OAuth 2.0 access tokenstyp: at+jwt, required iss, exp, aud, sub, client_id, iat, jti; the roles, groups, entitlements claimsThe default accept: 'access-token' shape
draft-ietf-jose-deprecate-none-rsa15Deprecating none and RSA1_5Marks the unsigned JWS algorithm and the PKCS#1 v1.5 JWE key-management algorithm deprecated in the registryBoth already refused; the registry status is recorded here

The JOSE working group publishes the registries at IANA; every alg, enc, kty, header parameter and typ media type PermDock reads or writes is registered there or in the IANA media types registry.

Why it matters for PermDock

A permissions library that never verified a signature would still depend on JOSE, because the identity it reasons about arrives inside a JWT. PermDock goes one step further in both directions:

  • Consuming. permdock/jwt is the one place in the package that verifies a signature. Every default there (algorithm allow-list, typ check, ignored jku / x5u / jwk headers, key selection by kid only, JWE refused without keys) is a line of the RFC 8725 checklist, so a deployment that turns the adapter on gets the Best Current Practice without reading it.
  • Producing. Snapshots cross a trust boundary (server to browser, Cloud to edge, one service to another). Plain JSON is enough when the transport is authenticated; when it is not, or when a consumer is written in another language, the snapshot needs a signature that consumer can check without PermDock. JOSE is the only signature format every language already has a library for, so PermDock signs with compact JWS and registered header parameters and never invents a signature envelope (wire formats).

Core stays free of all of it. permdock core has one runtime dependency, @standard-schema/spec; the JOSE implementation lives behind two interfaces, TokenVerifier and TokenSigner (extension interfaces), and permdock/jwt implements them with jose as an optional peer.

How PermDock uses it

What PermDock consumes

InputFormatWhere
Bearer or DPoP-bound access tokenCompact JWS, typ: at+jwt or JWT; optionally a nested JWS inside a JWE (cty: JWT)subjectFromJwt, createJwtSubjectResolver
DPoP proofCompact JWS, typ: dpop+jwt, header jwk, claims htm, htu, iat, jti, athverifyDpopProof
Key setJWK Set from jwks, or from jwks_uri after DiscoveryThe JWKS cache
Sender bindingcnf.jkt (DPoP), cnf.x5t#S256 (mTLS), cnf.jwk, cnf.kidBinding on the subject
Security Event TokensCompact JWS, typ: secevent+jwt (RFC 8417) for SSF and CAEP; typ: logout+jwt for Back-Channel Logoutpermdock/ssf
GNAP access claimRegistered claim on an RFC 9767 JWTdelegation.access
Introspection responseJSON, not JOSE; the RFC 9767 shapesubjectFromIntrospection

The RFC 8725 / rfc8725bis checklist

Each row is a default of permdock/jwt; the "reason" column is the on('auth') code the adapter emits when a token fails it (JWT adapter).

Practice (RFC 8725 section)permdock/jwt behaviourReason on failure
3.1 Perform algorithm verificationalg must be in the configured algorithms; the header's alg never selects the verification routine on its ownalg-not-allowed
3.1 / bis Reject nonealg: none is refused before any other check, signature present or notalg-none
3.2 Use appropriate algorithmsDefault allow-list PS256, ES256, Ed25519, plus RS256 outside profile: 'fapi2'; the HS* family only with an explicit { secret }; RSA1_5 neveralg-not-allowed
3.3 Validate all cryptographic operationsA key that fails to import or a signature that fails to verify is a failure, never a fallthroughinvalid-signature
3.4 Validate cryptographic inputsEC points on the curve, RSA keys at least 2048 bits, EC keys at least 224 bits under fapi2; undersized keys skipped at JWKS loadunknown-kid for tokens that used a skipped key
3.5 Ensure cryptographic keys have sufficient entropy{ secret } must be at least 256 bits for HS256Configuration error at resolver creation
3.6 Avoid compression of data containing secretszip in a JWE header is rejectedencrypted-token
3.7 Use UTF-8Payload decoded as UTF-8 onlymalformed
3.8 Validate issuer and subjectiss equals issuer (or the Discovery document's issuer); sub present and a stringwrong-issuer, invalid-claims
3.9 Use and validate audienceaud contains audience; accept: 'id-token' additionally checks azpwrong-audience
3.10 Do not trust received claimsjku, x5u, jwk and x5c headers are ignored; kid selects only from the configured key set; crit with an unknown parameter is rejectednone (logged), malformed
3.11 Use explicit typingtyp is checked: at+jwt (RFC 9068) or JWT for access tokens, at+jwt only under fapi2, logout+jwt and secevent+jwt in permdock/ssf, PermDock's own permdock-*+jwt values on its outputs, and only permdock-capability+jwt in subjectFromCapability, which no access-token resolver acceptswrong-token-type
3.12 Use mutually exclusive validation rules for different kinds of JWTsOne resolver accepts one accept value; an ID token is never verified by an access-token resolver and vice versawrong-token-type
bis: prefer fully-specified algorithmsEd25519 is the allow-list value; alg: EdDSA is accepted only when the selected key is OKP / crv: Ed25519, and permdock doctor reports italg-not-allowed for EdDSA on any other curve
bis: exp, nbf, iatAll three honoured within clockTolerance; a token without exp is rejected, except a typ: secevent+jwt SET (RFC 8417 section 2.2), which must carry iat instead; the SSF receiver bounds it by iat and its jti replay storeexpired, not-yet-valid, invalid-claims

What PermDock produces

Every output is compact JWS, signed through a TokenSigner, with a registered typ and registered claim names. The payload PermDock adds sits under one private claim named after the format so it never collides with a registered claim:

OutputtypClaimsWhere
Signed snapshotpermdock-snapshot+jwtiss, aud, sub (= principal.id), iat, exp (= expiresAt), jti, snapshot (the unchanged snapshot object, v: 1)permdock.snapshot({ signer }), SnapshotSource implementations, permdock/cloud
Signed approval tokenpermdock-approval+jwtiss, aud, sub, iat, exp, jti, approval (the bound hash token and its request id)approvalsHandler({ signer }) for cross-service resume
Signed decision exportpermdock-decisions+jwtiss, iat, jti, events (an array of CloudEvents-shaped decision events)A DecisionSink option for batch export and permdock/cloud
Signed policy documentpermdock-policy+jwtiss and aud (both the Cloud environment URL <PERMDOCK_CLOUD_URL>/v1/environments/<env>), iat, exp (= iat + 24 hours), jti, policy (a PolicyDocument, v: 1)PermDock Cloud for hosted grants; verified by cloud().policies with iss and aud checked against the environment URL
Link capabilitypermdock-capability+jwtiss, aud, sub (= the link id, capability.id), iat, exp (= capability.expiresAt), jti (claimed once for a one-time link), capability (a Capability, v: 1)signCapability in the application; verified by subjectFromCapability in permdock/jwt (link capabilities)

The specification of each payload is on wire formats; the five typ values are listed on the OpenAPI registry page next to the x-permdock-* extensions so they are registered together.

The interoperability contract

  1. Compact JWS only. No JSON serialization, no detached payloads, no custom envelope. An export that needs two signatures (a Cloud-side and a tenant-side key) carries one compact JWS per key. jose in TypeScript and any of the Go, Python, Java, .NET, Rust, Ruby or PHP libraries on the OpenID Foundation's implementations list verify it without knowing PermDock exists.
  2. Registered header parameters only. alg, kid, typ, optionally cty and x5t#S256. PermDock never writes jku, x5u or jwk into a header it signs, so a verifier that follows RFC 8725 has nothing to ignore.
  3. Algorithms from the allow-list. Ed25519 by default, ES256 and PS256 available; never HS* on anything that leaves the process, never none.
  4. Registered claim names with registered semantics. iss, aud, sub, iat, exp, nbf, jti mean what RFC 7519 says; PermDock's own data lives in exactly one private claim per format (snapshot, approval, events, policy, capability). Times inside those claims are NumericDate seconds too (the snapshot's issuedAt and expiresAt), so the envelope's exp equals the snapshot's expiresAt.
  5. Keys are published as a JWK Set. A signer that serves consumers outside the process publishes /.well-known/jwks.json; permdock/cloud does. Rotation follows the JWKS rules the consumer already applies to identity providers: new kid first, old key kept until every signed artefact with it has expired.
  6. Verification is the consumer's TokenVerifier. In TypeScript, joseTokenVerifier({ jwks, algorithms, typ }) from permdock/jwt; elsewhere, any JOSE library with typ and alg pinned.

Mapping table

JOSE conceptPermDock concept
JWS compact serializationThe wire form of every token verified and every artefact signed
alg allow-listalgorithms option; PS256, ES256, Ed25519 (+ RS256 outside fapi2)
kidKey selection in the JWKS cache; kid on signed outputs
typaccept option (at+jwt, JWT, or ID token rules); permdock-*+jwt on outputs
JWK Setjwks option, Discovery jwks_uri, Cloud environment /v1/environments/<env>/.well-known/jwks.json
JWE (alg + enc)Accepted only with decryptionKeys; nested JWS-in-JWE with cty: JWT; never produced, because a snapshot carries nothing secret (snapshots)
x5c certificate chainsIgnored by joseTokenVerifier; an issuer that publishes only certificates needs a custom TokenVerifier
cnf (jkt, x5t#S256, jwk, kid)Binding on principal or actor
Registered claims iss, sub, aud, exp, nbf, iat, jtiprincipal.issuer, principal.id, audience, expiresAt, verified-only, verified-only, replay id on signed outputs
SigningTokenSigner; joseTokenSigner({ key, alg })
VerificationTokenVerifier; joseTokenVerifier({ jwks }) or joseTokenVerifier({ discovery })
Deprecated none, RSA1_5, polymorphic EdDSARefused, refused, accepted for crv: Ed25519 with a doctor warning

Sources

Last updated on

On this page