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:
| RFC | Name | What it defines | Where PermDock meets it |
|---|---|---|---|
| RFC 7515 | JWS, JSON Web Signature | A signed payload in compact (header.payload.signature) or JSON serialization; the header parameters alg, kid, typ, cty, jku, jwk, x5u, x5c, x5t#S256, crit | Every token permdock/jwt verifies and everything PermDock signs |
| RFC 7516 | JWE, JSON Web Encryption | An encrypted payload with alg (key management) and enc (content encryption); a nested JWT is a JWS inside a JWE with cty: JWT | Accepted only when decryptionKeys is configured |
| RFC 7517 | JWK, JSON Web Key | A 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 7518 | JWA, JSON Web Algorithms | The alg and enc identifiers (RS256, PS256, ES256, HS256, none, RSA-OAEP, A256GCM) and the key types RSA, EC, oct | The algorithms allow-list |
| RFC 8037 | CFRG curves in JOSE | kty: OKP with crv: Ed25519, Ed448, X25519, X448; the polymorphic alg: EdDSA | Ed25519 keys in JWKS |
| RFC 9864 | Fully-specified algorithms for JOSE and COSE | Registers Ed25519 and Ed448 as alg values and deprecates EdDSA, whose meaning depended on the key | The value the allow-list uses; the reason permdock doctor warns on EdDSA |
| RFC 7519 | JWT, JSON Web Token | Registered claims iss, sub, aud, exp, nbf, iat, jti; the typ: JWT header; NumericDate | The claim set subjectFromJwt maps and every payload PermDock signs |
| RFC 7800 | Proof-of-possession key semantics | The 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 rfc8725bis | JWT Best Current Practices | The checklist below | What permdock/jwt enforces by default |
| RFC 9068 | JWT profile for OAuth 2.0 access tokens | typ: at+jwt, required iss, exp, aud, sub, client_id, iat, jti; the roles, groups, entitlements claims | The default accept: 'access-token' shape |
| draft-ietf-jose-deprecate-none-rsa15 | Deprecating none and RSA1_5 | Marks the unsigned JWS algorithm and the PKCS#1 v1.5 JWE key-management algorithm deprecated in the registry | Both 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/jwtis the one place in the package that verifies a signature. Every default there (algorithm allow-list,typcheck, ignoredjku/x5u/jwkheaders, key selection bykidonly, 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
| Input | Format | Where |
|---|---|---|
| Bearer or DPoP-bound access token | Compact JWS, typ: at+jwt or JWT; optionally a nested JWS inside a JWE (cty: JWT) | subjectFromJwt, createJwtSubjectResolver |
| DPoP proof | Compact JWS, typ: dpop+jwt, header jwk, claims htm, htu, iat, jti, ath | verifyDpopProof |
| Key set | JWK Set from jwks, or from jwks_uri after Discovery | The JWKS cache |
| Sender binding | cnf.jkt (DPoP), cnf.x5t#S256 (mTLS), cnf.jwk, cnf.kid | Binding on the subject |
| Security Event Tokens | Compact JWS, typ: secevent+jwt (RFC 8417) for SSF and CAEP; typ: logout+jwt for Back-Channel Logout | permdock/ssf |
GNAP access claim | Registered claim on an RFC 9767 JWT | delegation.access |
| Introspection response | JSON, not JOSE; the RFC 9767 shape | subjectFromIntrospection |
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 behaviour | Reason on failure |
|---|---|---|
| 3.1 Perform algorithm verification | alg must be in the configured algorithms; the header's alg never selects the verification routine on its own | alg-not-allowed |
3.1 / bis Reject none | alg: none is refused before any other check, signature present or not | alg-none |
| 3.2 Use appropriate algorithms | Default allow-list PS256, ES256, Ed25519, plus RS256 outside profile: 'fapi2'; the HS* family only with an explicit { secret }; RSA1_5 never | alg-not-allowed |
| 3.3 Validate all cryptographic operations | A key that fails to import or a signature that fails to verify is a failure, never a fallthrough | invalid-signature |
| 3.4 Validate cryptographic inputs | EC points on the curve, RSA keys at least 2048 bits, EC keys at least 224 bits under fapi2; undersized keys skipped at JWKS load | unknown-kid for tokens that used a skipped key |
| 3.5 Ensure cryptographic keys have sufficient entropy | { secret } must be at least 256 bits for HS256 | Configuration error at resolver creation |
| 3.6 Avoid compression of data containing secrets | zip in a JWE header is rejected | encrypted-token |
| 3.7 Use UTF-8 | Payload decoded as UTF-8 only | malformed |
| 3.8 Validate issuer and subject | iss equals issuer (or the Discovery document's issuer); sub present and a string | wrong-issuer, invalid-claims |
| 3.9 Use and validate audience | aud contains audience; accept: 'id-token' additionally checks azp | wrong-audience |
| 3.10 Do not trust received claims | jku, x5u, jwk and x5c headers are ignored; kid selects only from the configured key set; crit with an unknown parameter is rejected | none (logged), malformed |
| 3.11 Use explicit typing | typ 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 accepts | wrong-token-type |
| 3.12 Use mutually exclusive validation rules for different kinds of JWTs | One resolver accepts one accept value; an ID token is never verified by an access-token resolver and vice versa | wrong-token-type |
| bis: prefer fully-specified algorithms | Ed25519 is the allow-list value; alg: EdDSA is accepted only when the selected key is OKP / crv: Ed25519, and permdock doctor reports it | alg-not-allowed for EdDSA on any other curve |
bis: exp, nbf, iat | All 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 store | expired, 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:
| Output | typ | Claims | Where |
|---|---|---|---|
| Signed snapshot | permdock-snapshot+jwt | iss, aud, sub (= principal.id), iat, exp (= expiresAt), jti, snapshot (the unchanged snapshot object, v: 1) | permdock.snapshot({ signer }), SnapshotSource implementations, permdock/cloud |
| Signed approval token | permdock-approval+jwt | iss, aud, sub, iat, exp, jti, approval (the bound hash token and its request id) | approvalsHandler({ signer }) for cross-service resume |
| Signed decision export | permdock-decisions+jwt | iss, iat, jti, events (an array of CloudEvents-shaped decision events) | A DecisionSink option for batch export and permdock/cloud |
| Signed policy document | permdock-policy+jwt | iss 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 capability | permdock-capability+jwt | iss, 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
- 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.
josein 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. - Registered header parameters only.
alg,kid,typ, optionallyctyandx5t#S256. PermDock never writesjku,x5uorjwkinto a header it signs, so a verifier that follows RFC 8725 has nothing to ignore. - Algorithms from the allow-list.
Ed25519by default,ES256andPS256available; neverHS*on anything that leaves the process, nevernone. - Registered claim names with registered semantics.
iss,aud,sub,iat,exp,nbf,jtimean 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'sissuedAtandexpiresAt), so the envelope'sexpequals the snapshot'sexpiresAt. - Keys are published as a JWK Set. A signer that serves consumers outside the process publishes
/.well-known/jwks.json;permdock/clouddoes. Rotation follows the JWKS rules the consumer already applies to identity providers: newkidfirst, old key kept until every signed artefact with it has expired. - Verification is the consumer's TokenVerifier. In TypeScript,
joseTokenVerifier({ jwks, algorithms, typ })frompermdock/jwt; elsewhere, any JOSE library withtypandalgpinned.
Mapping table
| JOSE concept | PermDock concept |
|---|---|
| JWS compact serialization | The wire form of every token verified and every artefact signed |
alg allow-list | algorithms option; PS256, ES256, Ed25519 (+ RS256 outside fapi2) |
kid | Key selection in the JWKS cache; kid on signed outputs |
typ | accept option (at+jwt, JWT, or ID token rules); permdock-*+jwt on outputs |
| JWK Set | jwks 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 chains | Ignored 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, jti | principal.issuer, principal.id, audience, expiresAt, verified-only, verified-only, replay id on signed outputs |
| Signing | TokenSigner; joseTokenSigner({ key, alg }) |
| Verification | TokenVerifier; joseTokenVerifier({ jwks }) or joseTokenVerifier({ discovery }) |
Deprecated none, RSA1_5, polymorphic EdDSA | Refused, refused, accepted for crv: Ed25519 with a doctor warning |
Sources
- RFC 7515, 7516, 7517, 7518, 7519 (May 2015), RFC 7800 (Apr 2016), RFC 8037 (Jan 2017), RFC 8725 (Feb 2020), RFC 9068 (Oct 2021), RFC 9864, linked in the table above.
- draft-ietf-oauth-rfc8725bis (RFC Editor queue, Aug 2026) and draft-ietf-jose-deprecate-none-rsa15.
- IANA JOSE registries and the JWT claims registry.
- OpenID Foundation, JWT / JWS / JWE / JWK / JWA implementations, the list of libraries the interoperability contract is written for.
joseon GitHub: the optional peerpermdock/jwtuses (jwtVerify,createRemoteJWKSet,SignJWT,typandrequiredClaimsoptions).
Related
- JWT adapter: every option the checklist refers to.
- OpenID Connect: the identity claims carried in these formats and Discovery.
- FAPI 2.0: the profile that fixes the algorithm list.
- Extension interfaces:
TokenVerifier,TokenSigner,testTokenVerifier. - Wire formats: the signed snapshot, approval and decision-export payloads.
- Threat model: algorithm confusion, JWE without integrity, key rotation rows.
- Shared Signals and CAEP: SETs as JWS.
Last updated on
OpenID Connect
Where PermDock sits in an OpenID Connect deployment: the relying party or resource server runs Discovery and verifies the token, permdock/jwt maps the verified claims to a Subject, and every OIDC claim that reaches the principal has one documented home.
JWT authorization claims (RFC 9068, SCIM)
How PermDock reads the registered roles, groups and entitlements JWT claims (RFC 9068 section 2.2.3.1, SCIM RFC 7643 encoding) into global roles, team memberships and entitlement roles, how vendor tenant claims map to the active tenant, and the AuthZEN claims draft that makes a PDP a claim source.