···11+ISC License
22+33+Copyright (c) 2026 Anil Madhavapeddy <anil@recoil.org>
44+55+Permission to use, copy, modify, and distribute this software for any
66+purpose with or without fee is hereby granted, provided that the above
77+copyright notice and this permission notice appear in all copies.
88+99+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+151
ocaml-jsonwt/TODO.md
···11+# JWT Implementation TODO
22+33+RFC 7519 compliance tracking for ocaml-jwt.
44+55+## Implementation Status
66+77+- [x] Type definitions for all registered claims
88+- [x] Type definitions for JOSE header parameters
99+- [x] Type definitions for algorithms
1010+- [x] Base64url encoding/decoding
1111+- [x] Basic JWT structure parsing (3-part split)
1212+- [x] JSON parsing with jsont
1313+- [x] Signature creation and verification (HMAC, ECDSA, EdDSA)
1414+- [x] Claims validation
1515+- [x] JWK parsing and serialization
1616+- [x] Structured error types
1717+- [x] Comprehensive tests (30 tests passing)
1818+1919+---
2020+2121+## Completed Phases
2222+2323+### Phase 0: Error Types and Core Infrastructure - DONE
2424+2525+- [x] Structured error type with all RFC-compliant variants
2626+- [x] `pp_error` and `error_to_string` functions
2727+- [x] StringOrURI validation (validates URIs per RFC 3986)
2828+2929+### Phase 1: JSON Parsing with jsont - DONE
3030+3131+- [x] Header JSON codec (`Header.of_json`, `Header.to_json`)
3232+- [x] Claims JSON codec with strict mode for duplicate detection
3333+- [x] JWK JSON codec for all key types (Oct, RSA, EC, OKP)
3434+3535+### Phase 2: Signature Operations - PARTIALLY DONE
3636+3737+- [x] **HMAC Signatures** (HS256, HS384, HS512) - using digestif
3838+- [x] **ECDSA Signatures** (ES256, ES384, ES512) - using mirage-crypto-ec
3939+- [x] **EdDSA Signatures** (Ed25519) - using mirage-crypto-ec
4040+- [x] **Unsecured JWT** ("none") - with explicit `~allow_none:true` opt-in
4141+- [x] **Nested JWT Support** - `parse_nested` with max_depth protection
4242+- [ ] **RSA Signatures** (RS256, RS384, RS512) - STUBBED, needs JWK-to-RSA key parsing
4343+4444+### Phase 3: Claims Validation - DONE
4545+4646+- [x] Time-based claims (exp, nbf) with leeway support
4747+- [x] Issuer validation (iss)
4848+- [x] Audience validation (aud)
4949+- [x] `is_expired` helper function
5050+- [x] `time_to_expiry` helper function
5151+5252+### Phase 4: Full JWT Creation Flow - DONE
5353+5454+- [x] `create` function for signing JWTs
5555+- [x] Algorithm/key type validation
5656+5757+### Phase 5: Tests - DONE (30 tests passing)
5858+5959+#### RFC Test Vectors
6060+- [x] RFC 7519 Section 3.1 HS256 JWT
6161+- [x] RFC 7519 Section 6.1 Unsecured JWT
6262+6363+#### Algorithm Coverage
6464+- [x] HS256 sign/verify
6565+- [x] HS384 sign/verify
6666+- [x] HS512 sign/verify
6767+- [ ] RS256 sign/verify (stubbed)
6868+- [ ] RS384 sign/verify (stubbed)
6969+- [ ] RS512 sign/verify (stubbed)
7070+- [x] ES256 sign/verify
7171+- [x] ES384 sign/verify
7272+- [x] ES512 sign/verify
7373+- [x] EdDSA sign/verify
7474+- [x] none (unsecured) with opt-in
7575+7676+#### Validation Tests
7777+- [x] Expired token rejection
7878+- [x] Not-yet-valid token rejection
7979+- [x] Issuer mismatch rejection
8080+- [x] Audience mismatch rejection
8181+- [x] Leeway handling
8282+8383+#### Error Cases
8484+- [x] Invalid base64url
8585+- [x] Invalid JSON
8686+- [x] Wrong number of parts
8787+- [x] Signature mismatch
8888+- [x] Algorithm not in allowed list
8989+- [x] Unsecured JWT without allow_none
9090+9191+---
9292+9393+## Remaining Work
9494+9595+### RSA Signatures (RS256, RS384, RS512)
9696+9797+**Status:** Stubbed - returns `Key_type_mismatch "RSA signing/verification not yet implemented"`
9898+9999+**Required:**
100100+1. Implement JWK-to-RSA key parsing for `n`, `e` (public) and `d`, `p`, `q`, `dp`, `dq`, `qi` (private) fields
101101+2. Use `mirage-crypto-pk` for RSASSA-PKCS1-v1_5 signatures
102102+3. Add tests with RFC test vectors
103103+104104+### Future Work (Not in Current Scope)
105105+106106+1. **JWK Set (JWKS)**: RFC 7517 Section 5 support for multiple keys
107107+ - Useful for key rotation and fetching keys from JWKS endpoints
108108+ - Example: `/.well-known/jwks.json`
109109+ - Consider adding `Jwks.t` type and `Jwks.find_key : kid:string -> Jwks.t -> Jwk.t option`
110110+111111+2. **JWE Support**: RFC 7516 JSON Web Encryption
112112+ - Required for encrypted JWTs (as opposed to signed JWTs)
113113+ - Lower priority unless needed for specific use cases
114114+115115+---
116116+117117+## Design Decisions (Implemented)
118118+119119+1. **StringOrURI validation**: YES - Validates that `iss`/`sub` values containing ":" are valid URIs per RFC 3986.
120120+121121+2. **Duplicate claims**: STRICT MODE - Rejects JWTs with duplicate claim names by default. `~strict:false` allows lenient parsing.
122122+123123+3. **"none" algorithm**: REQUIRE OPT-IN - `~allow_none:bool` parameter to `verify` (default false). Unsecured JWTs rejected unless explicitly allowed.
124124+125125+4. **Error types**: STRUCTURED - Proper error variant type for pattern matching and error handling.
126126+127127+5. **Algorithm allowlist**: YES - `~allowed_algs` parameter to `verify`, defaulting to all algorithms (except none).
128128+129129+6. **Clock source**: EXPLICIT - Always requires `~now:Ptime.t` parameter. No implicit system clock usage.
130130+131131+7. **Nested JWTs**: YES - Support via `parse_nested` with `~max_depth` protection (default 2).
132132+133133+---
134134+135135+## File Summary
136136+137137+| File | Lines | Description |
138138+|------|-------|-------------|
139139+| `lib/jwt.ml` | ~1000 | Full implementation |
140140+| `lib/jwt.mli` | ~470 | Interface with RFC documentation |
141141+| `test/test_jwt.ml` | ~440 | 30 comprehensive tests |
142142+143143+---
144144+145145+## References
146146+147147+- [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) - JSON Web Token (JWT)
148148+- [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515) - JSON Web Signature (JWS)
149149+- [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517) - JSON Web Key (JWK)
150150+- [RFC 7518](https://datatracker.ietf.org/doc/html/rfc7518) - JSON Web Algorithms (JWA)
151151+- [RFC 8037](https://datatracker.ietf.org/doc/html/rfc8037) - CFRG Elliptic Curve (EdDSA)
···11+(** JSON Web Token (JWT) - RFC 7519 *)
22+33+(* Error types *)
44+type error =
55+ | Invalid_json of string
66+ | Invalid_base64url of string
77+ | Invalid_structure of string
88+ | Invalid_header of string
99+ | Invalid_claims of string
1010+ | Invalid_uri of string
1111+ | Duplicate_claim of string
1212+ | Unsupported_algorithm of string
1313+ | Algorithm_not_allowed of string
1414+ | Signature_mismatch
1515+ | Token_expired
1616+ | Token_not_yet_valid
1717+ | Invalid_issuer
1818+ | Invalid_audience
1919+ | Key_type_mismatch of string
2020+ | Unsecured_not_allowed
2121+ | Nesting_too_deep
2222+2323+let pp_error fmt = function
2424+ | Invalid_json s -> Format.fprintf fmt "Invalid JSON: %s" s
2525+ | Invalid_base64url s -> Format.fprintf fmt "Invalid base64url: %s" s
2626+ | Invalid_structure s -> Format.fprintf fmt "Invalid structure: %s" s
2727+ | Invalid_header s -> Format.fprintf fmt "Invalid header: %s" s
2828+ | Invalid_claims s -> Format.fprintf fmt "Invalid claims: %s" s
2929+ | Invalid_uri s -> Format.fprintf fmt "Invalid URI: %s" s
3030+ | Duplicate_claim s -> Format.fprintf fmt "Duplicate claim: %s" s
3131+ | Unsupported_algorithm s -> Format.fprintf fmt "Unsupported algorithm: %s" s
3232+ | Algorithm_not_allowed s -> Format.fprintf fmt "Algorithm not allowed: %s" s
3333+ | Signature_mismatch -> Format.fprintf fmt "Signature mismatch"
3434+ | Token_expired -> Format.fprintf fmt "Token expired"
3535+ | Token_not_yet_valid -> Format.fprintf fmt "Token not yet valid"
3636+ | Invalid_issuer -> Format.fprintf fmt "Invalid issuer"
3737+ | Invalid_audience -> Format.fprintf fmt "Invalid audience"
3838+ | Key_type_mismatch s -> Format.fprintf fmt "Key type mismatch: %s" s
3939+ | Unsecured_not_allowed -> Format.fprintf fmt "Unsecured JWT not allowed"
4040+ | Nesting_too_deep -> Format.fprintf fmt "Nested JWT too deep"
4141+4242+let error_to_string e =
4343+ Format.asprintf "%a" pp_error e
4444+4545+(* Base64url encoding/decoding per RFC 7515 Appendix C *)
4646+let base64url_encode s =
4747+ Base64.encode_string ~pad:false ~alphabet:Base64.uri_safe_alphabet s
4848+4949+let base64url_decode s =
5050+ (* Add padding if needed *)
5151+ let len = String.length s in
5252+ let pad_len = (4 - (len mod 4)) mod 4 in
5353+ let padded = s ^ String.make pad_len '=' in
5454+ match Base64.decode ~alphabet:Base64.uri_safe_alphabet padded with
5555+ | Ok v -> Ok v
5656+ | Error (`Msg m) -> Error (Invalid_base64url m)
5757+5858+(* StringOrURI validation per RFC 7519 Section 2 *)
5959+let validate_string_or_uri s =
6060+ if String.contains s ':' then
6161+ (* Must be a valid URI - basic check for scheme *)
6262+ match String.index_opt s ':' with
6363+ | Some i when i > 0 ->
6464+ let scheme = String.sub s 0 i in
6565+ (* Check scheme is alphanumeric with +.- allowed after first char *)
6666+ let valid_scheme =
6767+ String.length scheme > 0 &&
6868+ (match scheme.[0] with 'a'..'z' | 'A'..'Z' -> true | _ -> false) &&
6969+ String.for_all (fun c ->
7070+ match c with
7171+ | 'a'..'z' | 'A'..'Z' | '0'..'9' | '+' | '-' | '.' -> true
7272+ | _ -> false
7373+ ) scheme
7474+ in
7575+ if valid_scheme then Ok s
7676+ else Error (Invalid_uri (Printf.sprintf "Invalid URI scheme in: %s" s))
7777+ | _ -> Error (Invalid_uri (Printf.sprintf "Invalid URI: %s" s))
7878+ else
7979+ Ok s
8080+8181+(* Algorithm module *)
8282+module Algorithm = struct
8383+ type t =
8484+ | None
8585+ | HS256
8686+ | HS384
8787+ | HS512
8888+ | RS256
8989+ | RS384
9090+ | RS512
9191+ | ES256
9292+ | ES384
9393+ | ES512
9494+ | EdDSA
9595+9696+ let to_string = function
9797+ | None -> "none"
9898+ | HS256 -> "HS256"
9999+ | HS384 -> "HS384"
100100+ | HS512 -> "HS512"
101101+ | RS256 -> "RS256"
102102+ | RS384 -> "RS384"
103103+ | RS512 -> "RS512"
104104+ | ES256 -> "ES256"
105105+ | ES384 -> "ES384"
106106+ | ES512 -> "ES512"
107107+ | EdDSA -> "EdDSA"
108108+109109+ let of_string = function
110110+ | "none" -> Ok None
111111+ | "HS256" -> Ok HS256
112112+ | "HS384" -> Ok HS384
113113+ | "HS512" -> Ok HS512
114114+ | "RS256" -> Ok RS256
115115+ | "RS384" -> Ok RS384
116116+ | "RS512" -> Ok RS512
117117+ | "ES256" -> Ok ES256
118118+ | "ES384" -> Ok ES384
119119+ | "ES512" -> Ok ES512
120120+ | "EdDSA" -> Ok EdDSA
121121+ | s -> Error (Unsupported_algorithm s)
122122+123123+ let all = [ HS256; HS384; HS512; RS256; RS384; RS512; ES256; ES384; ES512; EdDSA ]
124124+ let all_with_none = None :: all
125125+end
126126+127127+(* JWK module *)
128128+module Jwk = struct
129129+ type kty = Oct | Rsa | Ec | Okp
130130+131131+ type crv = P256 | P384 | P521 | Ed25519
132132+133133+ type key_data =
134134+ | Symmetric of { k : string }
135135+ | Ed25519_pub of { x : string }
136136+ | Ed25519_priv of { x : string; d : string }
137137+ | P256_pub of { x : string; y : string }
138138+ | P256_priv of { x : string; y : string; d : string }
139139+ | P384_pub of { x : string; y : string }
140140+ | P384_priv of { x : string; y : string; d : string }
141141+ | P521_pub of { x : string; y : string }
142142+ | P521_priv of { x : string; y : string; d : string }
143143+ | Rsa_pub of { n : string; e : string }
144144+ | Rsa_priv of {
145145+ n : string;
146146+ e : string;
147147+ d : string;
148148+ p : string;
149149+ q : string;
150150+ dp : string;
151151+ dq : string;
152152+ qi : string;
153153+ }
154154+155155+ type t = {
156156+ key_data : key_data;
157157+ kid : string option;
158158+ alg : Algorithm.t option;
159159+ }
160160+161161+ let symmetric k = { key_data = Symmetric { k }; kid = None; alg = None }
162162+163163+ let ed25519_pub x =
164164+ { key_data = Ed25519_pub { x }; kid = None; alg = Some Algorithm.EdDSA }
165165+166166+ let ed25519_priv ~pub ~priv =
167167+ { key_data = Ed25519_priv { x = pub; d = priv }; kid = None; alg = Some Algorithm.EdDSA }
168168+169169+ let p256_pub ~x ~y =
170170+ { key_data = P256_pub { x; y }; kid = None; alg = Some Algorithm.ES256 }
171171+172172+ let p256_priv ~x ~y ~d =
173173+ { key_data = P256_priv { x; y; d }; kid = None; alg = Some Algorithm.ES256 }
174174+175175+ let p384_pub ~x ~y =
176176+ { key_data = P384_pub { x; y }; kid = None; alg = Some Algorithm.ES384 }
177177+178178+ let p384_priv ~x ~y ~d =
179179+ { key_data = P384_priv { x; y; d }; kid = None; alg = Some Algorithm.ES384 }
180180+181181+ let p521_pub ~x ~y =
182182+ { key_data = P521_pub { x; y }; kid = None; alg = Some Algorithm.ES512 }
183183+184184+ let p521_priv ~x ~y ~d =
185185+ { key_data = P521_priv { x; y; d }; kid = None; alg = Some Algorithm.ES512 }
186186+187187+ let rsa_pub ~n ~e =
188188+ { key_data = Rsa_pub { n; e }; kid = None; alg = Some Algorithm.RS256 }
189189+190190+ let rsa_priv ~n ~e ~d ~p ~q ~dp ~dq ~qi =
191191+ { key_data = Rsa_priv { n; e; d; p; q; dp; dq; qi }; kid = None; alg = Some Algorithm.RS256 }
192192+193193+ let kty t =
194194+ match t.key_data with
195195+ | Symmetric _ -> Oct
196196+ | Ed25519_pub _ | Ed25519_priv _ -> Okp
197197+ | P256_pub _ | P256_priv _ | P384_pub _ | P384_priv _ | P521_pub _ | P521_priv _ -> Ec
198198+ | Rsa_pub _ | Rsa_priv _ -> Rsa
199199+200200+ let kid t = t.kid
201201+ let alg t = t.alg
202202+203203+ let with_kid id t = { t with kid = Some id }
204204+ let with_alg a t = { t with alg = Some a }
205205+206206+ (* Helper to extract string from Jsont.json object members *)
207207+ let get_json_string members name =
208208+ List.find_map (fun ((n, _), v) ->
209209+ if n = name then
210210+ match v with
211211+ | Jsont.String (s, _) -> Some s
212212+ | _ -> None
213213+ else None
214214+ ) members
215215+216216+ let get_json_string_req members name =
217217+ match get_json_string members name with
218218+ | Some s -> Ok s
219219+ | None -> Error (Invalid_json (Printf.sprintf "missing required field: %s" name))
220220+221221+ let of_json s =
222222+ (* Parse the JSON to determine key type first *)
223223+ match Jsont_bytesrw.decode_string Jsont.json s with
224224+ | Error e -> Error (Invalid_json e)
225225+ | Ok (Jsont.Null _) -> Error (Invalid_json "null is not a valid JWK")
226226+ | Ok (Jsont.Object (members, _)) ->
227227+ let ( let* ) = Result.bind in
228228+ let* kty_s = get_json_string_req members "kty" in
229229+ let kid = get_json_string members "kid" in
230230+ let alg_opt =
231231+ match get_json_string members "alg" with
232232+ | None -> Ok None
233233+ | Some s ->
234234+ match Algorithm.of_string s with
235235+ | Ok a -> Ok (Some a)
236236+ | Error _ -> Ok None (* ignore unknown alg in JWK *)
237237+ in
238238+ let* alg = alg_opt in
239239+ (match kty_s with
240240+ | "oct" ->
241241+ let* k_b64 = get_json_string_req members "k" in
242242+ let* k = base64url_decode k_b64 in
243243+ Ok { key_data = Symmetric { k }; kid; alg }
244244+ | "OKP" ->
245245+ let* crv = get_json_string_req members "crv" in
246246+ if crv <> "Ed25519" then
247247+ Error (Invalid_json (Printf.sprintf "unsupported curve: %s" crv))
248248+ else
249249+ let* x_b64 = get_json_string_req members "x" in
250250+ let* x = base64url_decode x_b64 in
251251+ (match get_json_string members "d" with
252252+ | None -> Ok { key_data = Ed25519_pub { x }; kid; alg }
253253+ | Some d_b64 ->
254254+ let* d = base64url_decode d_b64 in
255255+ Ok { key_data = Ed25519_priv { x; d }; kid; alg })
256256+ | "EC" ->
257257+ let* crv = get_json_string_req members "crv" in
258258+ let* x_b64 = get_json_string_req members "x" in
259259+ let* y_b64 = get_json_string_req members "y" in
260260+ let* x = base64url_decode x_b64 in
261261+ let* y = base64url_decode y_b64 in
262262+ let has_d = Option.is_some (get_json_string members "d") in
263263+ let get_d () =
264264+ let* d_b64 = get_json_string_req members "d" in
265265+ base64url_decode d_b64
266266+ in
267267+ (match crv with
268268+ | "P-256" ->
269269+ if has_d then
270270+ let* d = get_d () in
271271+ Ok { key_data = P256_priv { x; y; d }; kid; alg }
272272+ else
273273+ Ok { key_data = P256_pub { x; y }; kid; alg }
274274+ | "P-384" ->
275275+ if has_d then
276276+ let* d = get_d () in
277277+ Ok { key_data = P384_priv { x; y; d }; kid; alg }
278278+ else
279279+ Ok { key_data = P384_pub { x; y }; kid; alg }
280280+ | "P-521" ->
281281+ if has_d then
282282+ let* d = get_d () in
283283+ Ok { key_data = P521_priv { x; y; d }; kid; alg }
284284+ else
285285+ Ok { key_data = P521_pub { x; y }; kid; alg }
286286+ | _ -> Error (Invalid_json (Printf.sprintf "unsupported curve: %s" crv)))
287287+ | "RSA" ->
288288+ let* n_b64 = get_json_string_req members "n" in
289289+ let* e_b64 = get_json_string_req members "e" in
290290+ let* n = base64url_decode n_b64 in
291291+ let* e = base64url_decode e_b64 in
292292+ (match get_json_string members "d" with
293293+ | None -> Ok { key_data = Rsa_pub { n; e }; kid; alg }
294294+ | Some d_b64 ->
295295+ let* d = base64url_decode d_b64 in
296296+ let* p_b64 = get_json_string_req members "p" in
297297+ let* q_b64 = get_json_string_req members "q" in
298298+ let* dp_b64 = get_json_string_req members "dp" in
299299+ let* dq_b64 = get_json_string_req members "dq" in
300300+ let* qi_b64 = get_json_string_req members "qi" in
301301+ let* p = base64url_decode p_b64 in
302302+ let* q = base64url_decode q_b64 in
303303+ let* dp = base64url_decode dp_b64 in
304304+ let* dq = base64url_decode dq_b64 in
305305+ let* qi = base64url_decode qi_b64 in
306306+ Ok { key_data = Rsa_priv { n; e; d; p; q; dp; dq; qi }; kid; alg })
307307+ | _ -> Error (Invalid_json (Printf.sprintf "unsupported kty: %s" kty_s)))
308308+ | Ok _ -> Error (Invalid_json "JWK must be a JSON object")
309309+310310+ (* Helper to create JSON members *)
311311+ let meta = Jsont.Meta.none
312312+ let json_string s = Jsont.String (s, meta)
313313+ let json_mem name value = ((name, meta), value)
314314+315315+ let to_json t =
316316+ let add_opt name v_opt acc =
317317+ match v_opt with
318318+ | None -> acc
319319+ | Some v -> json_mem name (json_string v) :: acc
320320+ in
321321+ let members = [] in
322322+ let members = add_opt "kid" t.kid members in
323323+ let members = add_opt "alg" (Option.map Algorithm.to_string t.alg) members in
324324+ let members =
325325+ match t.key_data with
326326+ | Symmetric { k } ->
327327+ json_mem "kty" (json_string "oct") ::
328328+ json_mem "k" (json_string (base64url_encode k)) :: members
329329+ | Ed25519_pub { x } ->
330330+ json_mem "kty" (json_string "OKP") ::
331331+ json_mem "crv" (json_string "Ed25519") ::
332332+ json_mem "x" (json_string (base64url_encode x)) :: members
333333+ | Ed25519_priv { x; d } ->
334334+ json_mem "kty" (json_string "OKP") ::
335335+ json_mem "crv" (json_string "Ed25519") ::
336336+ json_mem "x" (json_string (base64url_encode x)) ::
337337+ json_mem "d" (json_string (base64url_encode d)) :: members
338338+ | P256_pub { x; y } ->
339339+ json_mem "kty" (json_string "EC") ::
340340+ json_mem "crv" (json_string "P-256") ::
341341+ json_mem "x" (json_string (base64url_encode x)) ::
342342+ json_mem "y" (json_string (base64url_encode y)) :: members
343343+ | P256_priv { x; y; d } ->
344344+ json_mem "kty" (json_string "EC") ::
345345+ json_mem "crv" (json_string "P-256") ::
346346+ json_mem "x" (json_string (base64url_encode x)) ::
347347+ json_mem "y" (json_string (base64url_encode y)) ::
348348+ json_mem "d" (json_string (base64url_encode d)) :: members
349349+ | P384_pub { x; y } ->
350350+ json_mem "kty" (json_string "EC") ::
351351+ json_mem "crv" (json_string "P-384") ::
352352+ json_mem "x" (json_string (base64url_encode x)) ::
353353+ json_mem "y" (json_string (base64url_encode y)) :: members
354354+ | P384_priv { x; y; d } ->
355355+ json_mem "kty" (json_string "EC") ::
356356+ json_mem "crv" (json_string "P-384") ::
357357+ json_mem "x" (json_string (base64url_encode x)) ::
358358+ json_mem "y" (json_string (base64url_encode y)) ::
359359+ json_mem "d" (json_string (base64url_encode d)) :: members
360360+ | P521_pub { x; y } ->
361361+ json_mem "kty" (json_string "EC") ::
362362+ json_mem "crv" (json_string "P-521") ::
363363+ json_mem "x" (json_string (base64url_encode x)) ::
364364+ json_mem "y" (json_string (base64url_encode y)) :: members
365365+ | P521_priv { x; y; d } ->
366366+ json_mem "kty" (json_string "EC") ::
367367+ json_mem "crv" (json_string "P-521") ::
368368+ json_mem "x" (json_string (base64url_encode x)) ::
369369+ json_mem "y" (json_string (base64url_encode y)) ::
370370+ json_mem "d" (json_string (base64url_encode d)) :: members
371371+ | Rsa_pub { n; e } ->
372372+ json_mem "kty" (json_string "RSA") ::
373373+ json_mem "n" (json_string (base64url_encode n)) ::
374374+ json_mem "e" (json_string (base64url_encode e)) :: members
375375+ | Rsa_priv { n; e; d; p; q; dp; dq; qi } ->
376376+ json_mem "kty" (json_string "RSA") ::
377377+ json_mem "n" (json_string (base64url_encode n)) ::
378378+ json_mem "e" (json_string (base64url_encode e)) ::
379379+ json_mem "d" (json_string (base64url_encode d)) ::
380380+ json_mem "p" (json_string (base64url_encode p)) ::
381381+ json_mem "q" (json_string (base64url_encode q)) ::
382382+ json_mem "dp" (json_string (base64url_encode dp)) ::
383383+ json_mem "dq" (json_string (base64url_encode dq)) ::
384384+ json_mem "qi" (json_string (base64url_encode qi)) :: members
385385+ in
386386+ match Jsont_bytesrw.encode_string Jsont.json (Jsont.Object (members, meta)) with
387387+ | Ok s -> s
388388+ | Error _ -> "{}" (* Should not happen *)
389389+end
390390+391391+(* Header module *)
392392+module Header = struct
393393+ type t = {
394394+ alg : Algorithm.t;
395395+ typ : string option;
396396+ kid : string option;
397397+ cty : string option;
398398+ }
399399+400400+ let make ?typ ?kid ?cty alg = { alg; typ; kid; cty }
401401+402402+ let is_nested t =
403403+ match t.cty with
404404+ | Some s -> String.uppercase_ascii s = "JWT"
405405+ | None -> false
406406+407407+ (* Helper to extract string from Jsont.json object members *)
408408+ let get_json_string members name =
409409+ List.find_map (fun ((n, _), v) ->
410410+ if n = name then
411411+ match v with
412412+ | Jsont.String (s, _) -> Some s
413413+ | _ -> None
414414+ else None
415415+ ) members
416416+417417+ let of_json s =
418418+ match Jsont_bytesrw.decode_string Jsont.json s with
419419+ | Error e -> Error (Invalid_json e)
420420+ | Ok (Jsont.Null _) -> Error (Invalid_header "null is not a valid header")
421421+ | Ok (Jsont.Object (members, _)) ->
422422+ let ( let* ) = Result.bind in
423423+ let alg_s = get_json_string members "alg" in
424424+ (match alg_s with
425425+ | None -> Error (Invalid_header "missing required 'alg' field")
426426+ | Some alg_str ->
427427+ let* alg = Algorithm.of_string alg_str in
428428+ let typ = get_json_string members "typ" in
429429+ let kid = get_json_string members "kid" in
430430+ let cty = get_json_string members "cty" in
431431+ Ok { alg; typ; kid; cty })
432432+ | Ok _ -> Error (Invalid_header "header must be a JSON object")
433433+434434+ let meta = Jsont.Meta.none
435435+ let json_string s = Jsont.String (s, meta)
436436+ let json_mem name value = ((name, meta), value)
437437+438438+ let to_json h =
439439+ let members = [ json_mem "alg" (json_string (Algorithm.to_string h.alg)) ] in
440440+ let add_opt name v_opt acc =
441441+ match v_opt with
442442+ | None -> acc
443443+ | Some v -> json_mem name (json_string v) :: acc
444444+ in
445445+ let members = add_opt "typ" h.typ members in
446446+ let members = add_opt "kid" h.kid members in
447447+ let members = add_opt "cty" h.cty members in
448448+ match Jsont_bytesrw.encode_string Jsont.json (Jsont.Object (List.rev members, meta)) with
449449+ | Ok s -> s
450450+ | Error _ -> "{}"
451451+end
452452+453453+(* Claims module *)
454454+module Claims = struct
455455+ type t = {
456456+ iss : string option;
457457+ sub : string option;
458458+ aud : string list;
459459+ exp : Ptime.t option;
460460+ nbf : Ptime.t option;
461461+ iat : Ptime.t option;
462462+ jti : string option;
463463+ custom : (string * Jsont.json) list;
464464+ }
465465+466466+ let iss t = t.iss
467467+ let sub t = t.sub
468468+ let aud t = t.aud
469469+ let exp t = t.exp
470470+ let nbf t = t.nbf
471471+ let iat t = t.iat
472472+ let jti t = t.jti
473473+474474+ let get name t = List.assoc_opt name t.custom
475475+476476+ let get_string name t =
477477+ match get name t with
478478+ | Some (Jsont.String (s, _)) -> Some s
479479+ | _ -> None
480480+481481+ let get_int name t =
482482+ match get name t with
483483+ | Some (Jsont.Number (n, _)) -> (try Some (int_of_float n) with _ -> None)
484484+ | _ -> None
485485+486486+ let get_bool name t =
487487+ match get name t with
488488+ | Some (Jsont.Bool (b, _)) -> Some b
489489+ | _ -> None
490490+491491+ let meta = Jsont.Meta.none
492492+ let json_string s = Jsont.String (s, meta)
493493+ let json_number n = Jsont.Number (n, meta)
494494+ let json_bool b = Jsont.Bool (b, meta)
495495+ let json_mem name value = ((name, meta), value)
496496+497497+ type builder = t
498498+499499+ let empty = {
500500+ iss = None;
501501+ sub = None;
502502+ aud = [];
503503+ exp = None;
504504+ nbf = None;
505505+ iat = None;
506506+ jti = None;
507507+ custom = [];
508508+ }
509509+510510+ let set_iss v t = { t with iss = Some v }
511511+ let set_sub v t = { t with sub = Some v }
512512+ let set_aud v t = { t with aud = v }
513513+ let set_exp v t = { t with exp = Some v }
514514+ let set_nbf v t = { t with nbf = Some v }
515515+ let set_iat v t = { t with iat = Some v }
516516+ let set_jti v t = { t with jti = Some v }
517517+ let set name value t = { t with custom = (name, value) :: t.custom }
518518+ let set_string name value t = set name (json_string value) t
519519+ let set_int name value t = set name (json_number (float_of_int value)) t
520520+ let set_bool name value t = set name (json_bool value) t
521521+ let build t = t
522522+523523+ let ptime_of_numeric_date n =
524524+ let span = Ptime.Span.of_float_s n in
525525+ Option.bind span (fun s -> Ptime.of_span s)
526526+527527+ let numeric_date_of_ptime t =
528528+ Ptime.to_span t |> Ptime.Span.to_float_s
529529+530530+ (* Helper to extract values from Jsont.json object members *)
531531+ let get_json_string members name =
532532+ List.find_map (fun ((n, _), v) ->
533533+ if n = name then
534534+ match v with
535535+ | Jsont.String (s, _) -> Some s
536536+ | _ -> None
537537+ else None
538538+ ) members
539539+540540+ let get_json_number members name =
541541+ List.find_map (fun ((n, _), v) ->
542542+ if n = name then
543543+ match v with
544544+ | Jsont.Number (n, _) -> Some n
545545+ | _ -> None
546546+ else None
547547+ ) members
548548+549549+ let get_json_aud members =
550550+ List.find_map (fun ((n, _), v) ->
551551+ if n = "aud" then
552552+ match v with
553553+ | Jsont.String (s, _) -> Some [ s ]
554554+ | Jsont.Array (arr, _) ->
555555+ Some (List.filter_map (function
556556+ | Jsont.String (s, _) -> Some s
557557+ | _ -> None
558558+ ) arr)
559559+ | _ -> None
560560+ else None
561561+ ) members |> Option.value ~default:[]
562562+563563+ let of_json ?(strict = true) s =
564564+ match Jsont_bytesrw.decode_string Jsont.json s with
565565+ | Error e -> Error (Invalid_json e)
566566+ | Ok (Jsont.Null _) -> Error (Invalid_claims "null is not a valid claims set")
567567+ | Ok (Jsont.Object (members, _)) ->
568568+ let ( let* ) = Result.bind in
569569+ (* Check for duplicates in strict mode *)
570570+ let* () =
571571+ if strict then
572572+ let names = List.map (fun ((n, _), _) -> n) members in
573573+ let rec check_dups = function
574574+ | [] -> Ok ()
575575+ | n :: rest ->
576576+ if List.mem n rest then Error (Duplicate_claim n)
577577+ else check_dups rest
578578+ in
579579+ check_dups names
580580+ else Ok ()
581581+ in
582582+ (* Validate StringOrURI for iss and sub *)
583583+ let* iss =
584584+ match get_json_string members "iss" with
585585+ | None -> Ok None
586586+ | Some s ->
587587+ let* _ = validate_string_or_uri s in
588588+ Ok (Some s)
589589+ in
590590+ let* sub =
591591+ match get_json_string members "sub" with
592592+ | None -> Ok None
593593+ | Some s ->
594594+ let* _ = validate_string_or_uri s in
595595+ Ok (Some s)
596596+ in
597597+ let exp = Option.bind (get_json_number members "exp") ptime_of_numeric_date in
598598+ let nbf = Option.bind (get_json_number members "nbf") ptime_of_numeric_date in
599599+ let iat = Option.bind (get_json_number members "iat") ptime_of_numeric_date in
600600+ let jti = get_json_string members "jti" in
601601+ let aud = get_json_aud members in
602602+ (* Collect custom claims (everything not registered) *)
603603+ let registered = [ "iss"; "sub"; "aud"; "exp"; "nbf"; "iat"; "jti" ] in
604604+ let custom =
605605+ List.filter_map (fun ((n, _), v) ->
606606+ if List.mem n registered then None
607607+ else Some (n, v)
608608+ ) members
609609+ in
610610+ Ok { iss; sub; aud; exp; nbf; iat; jti; custom }
611611+ | Ok _ -> Error (Invalid_claims "claims must be a JSON object")
612612+613613+ let to_json t =
614614+ let members = [] in
615615+ let add_string name v_opt acc =
616616+ match v_opt with
617617+ | None -> acc
618618+ | Some v -> json_mem name (json_string v) :: acc
619619+ in
620620+ let add_time name v_opt acc =
621621+ match v_opt with
622622+ | None -> acc
623623+ | Some v -> json_mem name (json_number (numeric_date_of_ptime v)) :: acc
624624+ in
625625+ let members = add_string "iss" t.iss members in
626626+ let members = add_string "sub" t.sub members in
627627+ let members =
628628+ match t.aud with
629629+ | [] -> members
630630+ | [ single ] -> json_mem "aud" (json_string single) :: members
631631+ | many ->
632632+ let arr = List.map json_string many in
633633+ json_mem "aud" (Jsont.Array (arr, meta)) :: members
634634+ in
635635+ let members = add_time "exp" t.exp members in
636636+ let members = add_time "nbf" t.nbf members in
637637+ let members = add_time "iat" t.iat members in
638638+ let members = add_string "jti" t.jti members in
639639+ let members =
640640+ List.fold_left (fun acc (name, value) ->
641641+ json_mem name value :: acc
642642+ ) members t.custom
643643+ in
644644+ match Jsont_bytesrw.encode_string Jsont.json (Jsont.Object (List.rev members, meta)) with
645645+ | Ok s -> s
646646+ | Error _ -> "{}"
647647+end
648648+649649+(* JWT type *)
650650+type t = {
651651+ header : Header.t;
652652+ claims : Claims.t;
653653+ signature : string;
654654+ raw : string;
655655+}
656656+657657+let header t = t.header
658658+let claims t = t.claims
659659+let signature t = t.signature
660660+let raw t = t.raw
661661+662662+let is_nested t = Header.is_nested t.header
663663+664664+(* Parsing *)
665665+let parse ?(strict = true) token =
666666+ let ( let* ) = Result.bind in
667667+ (* RFC 7519 Section 7.2 step 1: verify at least one period *)
668668+ if not (String.contains token '.') then
669669+ Error (Invalid_structure "JWT must contain at least one period character")
670670+ else
671671+ match String.split_on_char '.' token with
672672+ | [ header_b64; payload_b64; sig_b64 ] ->
673673+ (* JWS compact serialization: 3 parts *)
674674+ let* header_json = base64url_decode header_b64 in
675675+ let* payload_json = base64url_decode payload_b64 in
676676+ let* signature = base64url_decode sig_b64 in
677677+ let* header = Header.of_json header_json in
678678+ let* claims = Claims.of_json ~strict payload_json in
679679+ Ok { header; claims; signature; raw = token }
680680+ | parts when List.length parts = 5 ->
681681+ (* JWE compact serialization - not yet supported *)
682682+ Error (Invalid_structure "JWE (encrypted JWT) not yet supported")
683683+ | _ ->
684684+ Error (Invalid_structure "JWT must have 3 parts (JWS) or 5 parts (JWE)")
685685+686686+let parse_unsafe = parse ~strict:false
687687+688688+let parse_nested ?(strict = true) ?(max_depth = 5) token =
689689+ let ( let* ) = Result.bind in
690690+ let rec loop depth acc tok =
691691+ if depth > max_depth then
692692+ Error Nesting_too_deep
693693+ else
694694+ let* jwt = parse ~strict tok in
695695+ let acc = jwt :: acc in
696696+ if is_nested jwt then
697697+ (* The payload is another JWT - decode and parse it *)
698698+ match String.split_on_char '.' tok with
699699+ | [ _; payload_b64; _ ] ->
700700+ let* inner_token = base64url_decode payload_b64 in
701701+ loop (depth + 1) acc inner_token
702702+ | _ -> Ok (List.rev acc)
703703+ else
704704+ Ok (List.rev acc)
705705+ in
706706+ loop 1 [] token
707707+708708+(* Signature operations *)
709709+module Sign = struct
710710+ let hmac_sha256 ~key data =
711711+ let key = Cstruct.of_string key in
712712+ let data = Cstruct.of_string data in
713713+ Digestif.SHA256.hmac_string ~key:(Cstruct.to_string key) (Cstruct.to_string data)
714714+ |> Digestif.SHA256.to_raw_string
715715+716716+ let hmac_sha384 ~key data =
717717+ let key = Cstruct.of_string key in
718718+ let data = Cstruct.of_string data in
719719+ Digestif.SHA384.hmac_string ~key:(Cstruct.to_string key) (Cstruct.to_string data)
720720+ |> Digestif.SHA384.to_raw_string
721721+722722+ let hmac_sha512 ~key data =
723723+ let key = Cstruct.of_string key in
724724+ let data = Cstruct.of_string data in
725725+ Digestif.SHA512.hmac_string ~key:(Cstruct.to_string key) (Cstruct.to_string data)
726726+ |> Digestif.SHA512.to_raw_string
727727+728728+ (* EdDSA signing using mirage-crypto-ec *)
729729+ let ed25519_sign ~priv data =
730730+ match Mirage_crypto_ec.Ed25519.priv_of_octets priv with
731731+ | Error _ -> Error (Key_type_mismatch "Invalid Ed25519 private key")
732732+ | Ok priv ->
733733+ let sig_ = Mirage_crypto_ec.Ed25519.sign ~key:priv data in
734734+ Ok sig_
735735+736736+ let ed25519_verify ~pub ~signature data =
737737+ match Mirage_crypto_ec.Ed25519.pub_of_octets pub with
738738+ | Error _ -> Error (Key_type_mismatch "Invalid Ed25519 public key")
739739+ | Ok pub ->
740740+ let valid = Mirage_crypto_ec.Ed25519.verify ~key:pub signature ~msg:data in
741741+ if valid then Ok () else Error Signature_mismatch
742742+743743+ (* P-256 ECDSA *)
744744+ let p256_sign ~priv data =
745745+ match Mirage_crypto_ec.P256.Dsa.priv_of_octets priv with
746746+ | Error _ -> Error (Key_type_mismatch "Invalid P-256 private key")
747747+ | Ok priv ->
748748+ let hash = Digestif.SHA256.digest_string data |> Digestif.SHA256.to_raw_string in
749749+ let (r, s) = Mirage_crypto_ec.P256.Dsa.sign ~key:priv hash in
750750+ (* JWS uses raw R||S format, each 32 bytes for P-256 *)
751751+ (* Pad to 32 bytes each *)
752752+ let pad32 s =
753753+ let len = String.length s in
754754+ if len >= 32 then String.sub s (len - 32) 32
755755+ else String.make (32 - len) '\x00' ^ s
756756+ in
757757+ Ok (pad32 r ^ pad32 s)
758758+759759+ let p256_verify ~pub ~signature data =
760760+ if String.length signature <> 64 then
761761+ Error Signature_mismatch
762762+ else
763763+ let r = String.sub signature 0 32 in
764764+ let s = String.sub signature 32 32 in
765765+ match Mirage_crypto_ec.P256.Dsa.pub_of_octets pub with
766766+ | Error _ -> Error (Key_type_mismatch "Invalid P-256 public key")
767767+ | Ok pub ->
768768+ let hash = Digestif.SHA256.digest_string data |> Digestif.SHA256.to_raw_string in
769769+ let valid = Mirage_crypto_ec.P256.Dsa.verify ~key:pub (r, s) hash in
770770+ if valid then Ok () else Error Signature_mismatch
771771+772772+ (* P-384 ECDSA *)
773773+ let p384_sign ~priv data =
774774+ match Mirage_crypto_ec.P384.Dsa.priv_of_octets priv with
775775+ | Error _ -> Error (Key_type_mismatch "Invalid P-384 private key")
776776+ | Ok priv ->
777777+ let hash = Digestif.SHA384.digest_string data |> Digestif.SHA384.to_raw_string in
778778+ let (r, s) = Mirage_crypto_ec.P384.Dsa.sign ~key:priv hash in
779779+ let pad48 s =
780780+ let len = String.length s in
781781+ if len >= 48 then String.sub s (len - 48) 48
782782+ else String.make (48 - len) '\x00' ^ s
783783+ in
784784+ Ok (pad48 r ^ pad48 s)
785785+786786+ let p384_verify ~pub ~signature data =
787787+ if String.length signature <> 96 then
788788+ Error Signature_mismatch
789789+ else
790790+ let r = String.sub signature 0 48 in
791791+ let s = String.sub signature 48 48 in
792792+ match Mirage_crypto_ec.P384.Dsa.pub_of_octets pub with
793793+ | Error _ -> Error (Key_type_mismatch "Invalid P-384 public key")
794794+ | Ok pub ->
795795+ let hash = Digestif.SHA384.digest_string data |> Digestif.SHA384.to_raw_string in
796796+ let valid = Mirage_crypto_ec.P384.Dsa.verify ~key:pub (r, s) hash in
797797+ if valid then Ok () else Error Signature_mismatch
798798+799799+ (* P-521 ECDSA *)
800800+ let p521_sign ~priv data =
801801+ match Mirage_crypto_ec.P521.Dsa.priv_of_octets priv with
802802+ | Error _ -> Error (Key_type_mismatch "Invalid P-521 private key")
803803+ | Ok priv ->
804804+ let hash = Digestif.SHA512.digest_string data |> Digestif.SHA512.to_raw_string in
805805+ let (r, s) = Mirage_crypto_ec.P521.Dsa.sign ~key:priv hash in
806806+ let pad66 s =
807807+ let len = String.length s in
808808+ if len >= 66 then String.sub s (len - 66) 66
809809+ else String.make (66 - len) '\x00' ^ s
810810+ in
811811+ Ok (pad66 r ^ pad66 s)
812812+813813+ let p521_verify ~pub ~signature data =
814814+ if String.length signature <> 132 then
815815+ Error Signature_mismatch
816816+ else
817817+ let r = String.sub signature 0 66 in
818818+ let s = String.sub signature 66 66 in
819819+ match Mirage_crypto_ec.P521.Dsa.pub_of_octets pub with
820820+ | Error _ -> Error (Key_type_mismatch "Invalid P-521 public key")
821821+ | Ok pub ->
822822+ let hash = Digestif.SHA512.digest_string data |> Digestif.SHA512.to_raw_string in
823823+ let valid = Mirage_crypto_ec.P521.Dsa.verify ~key:pub (r, s) hash in
824824+ if valid then Ok () else Error Signature_mismatch
825825+826826+ (* RSA PKCS#1 v1.5 - stub implementations *)
827827+ (* TODO: Implement proper RSA signing/verification with JWK key parsing *)
828828+ let _rsa_sign _hash_type ~priv:_ _data =
829829+ Error (Key_type_mismatch "RSA signing not yet implemented")
830830+831831+ let _rsa_verify _hash_type ~pub:_ ~signature:_ _data =
832832+ Error (Key_type_mismatch "RSA verification not yet implemented")
833833+end
834834+835835+(* Get signing input from token *)
836836+let signing_input token =
837837+ match String.rindex_opt token '.' with
838838+ | None -> token
839839+ | Some i -> String.sub token 0 i
840840+841841+(* Verification *)
842842+let verify ~key ?(allow_none = false) ?(allowed_algs = Algorithm.all) t =
843843+ let ( let* ) = Result.bind in
844844+ let alg = t.header.alg in
845845+ let alg_str = Algorithm.to_string alg in
846846+ (* Check if algorithm is allowed *)
847847+ let* () =
848848+ if alg = Algorithm.None then
849849+ (* For alg:none, only allow_none flag matters *)
850850+ if allow_none then Ok ()
851851+ else Error Unsecured_not_allowed
852852+ else if List.mem alg allowed_algs then Ok ()
853853+ else Error (Algorithm_not_allowed alg_str)
854854+ in
855855+ let input = signing_input t.raw in
856856+ match alg, key.Jwk.key_data with
857857+ | Algorithm.None, _ ->
858858+ (* Unsecured JWT - signature must be empty *)
859859+ if t.signature = "" then Ok ()
860860+ else Error Signature_mismatch
861861+ | Algorithm.HS256, Jwk.Symmetric { k } ->
862862+ let expected = Sign.hmac_sha256 ~key:k input in
863863+ if Eqaf.equal expected t.signature then Ok ()
864864+ else Error Signature_mismatch
865865+ | Algorithm.HS384, Jwk.Symmetric { k } ->
866866+ let expected = Sign.hmac_sha384 ~key:k input in
867867+ if Eqaf.equal expected t.signature then Ok ()
868868+ else Error Signature_mismatch
869869+ | Algorithm.HS512, Jwk.Symmetric { k } ->
870870+ let expected = Sign.hmac_sha512 ~key:k input in
871871+ if Eqaf.equal expected t.signature then Ok ()
872872+ else Error Signature_mismatch
873873+ | Algorithm.EdDSA, Jwk.Ed25519_pub { x } ->
874874+ Sign.ed25519_verify ~pub:x ~signature:t.signature input
875875+ | Algorithm.EdDSA, Jwk.Ed25519_priv { x; d = _ } ->
876876+ Sign.ed25519_verify ~pub:x ~signature:t.signature input
877877+ | Algorithm.ES256, Jwk.P256_pub { x; y } ->
878878+ let pub = x ^ y in (* Uncompressed point *)
879879+ Sign.p256_verify ~pub ~signature:t.signature input
880880+ | Algorithm.ES256, Jwk.P256_priv { x; y; d = _ } ->
881881+ let pub = x ^ y in
882882+ Sign.p256_verify ~pub ~signature:t.signature input
883883+ | Algorithm.ES384, Jwk.P384_pub { x; y } ->
884884+ let pub = x ^ y in
885885+ Sign.p384_verify ~pub ~signature:t.signature input
886886+ | Algorithm.ES384, Jwk.P384_priv { x; y; d = _ } ->
887887+ let pub = x ^ y in
888888+ Sign.p384_verify ~pub ~signature:t.signature input
889889+ | Algorithm.ES512, Jwk.P521_pub { x; y } ->
890890+ let pub = x ^ y in
891891+ Sign.p521_verify ~pub ~signature:t.signature input
892892+ | Algorithm.ES512, Jwk.P521_priv { x; y; d = _ } ->
893893+ let pub = x ^ y in
894894+ Sign.p521_verify ~pub ~signature:t.signature input
895895+ | Algorithm.RS256, Jwk.Rsa_pub _ ->
896896+ Error (Key_type_mismatch "RSA verification not yet implemented")
897897+ | Algorithm.RS384, Jwk.Rsa_pub _ ->
898898+ Error (Key_type_mismatch "RSA verification not yet implemented")
899899+ | Algorithm.RS512, Jwk.Rsa_pub _ ->
900900+ Error (Key_type_mismatch "RSA verification not yet implemented")
901901+ | alg, _ ->
902902+ Error (Key_type_mismatch
903903+ (Printf.sprintf "Key type doesn't match algorithm %s" (Algorithm.to_string alg)))
904904+905905+(* Claims validation *)
906906+let validate ~now ?iss ?aud ?(leeway = Ptime.Span.zero) t =
907907+ let ( let* ) = Result.bind in
908908+ let claims = t.claims in
909909+ (* Check exp claim *)
910910+ let* () =
911911+ match Claims.exp claims with
912912+ | None -> Ok ()
913913+ | Some exp_time ->
914914+ let exp_with_leeway = Ptime.add_span exp_time leeway |> Option.value ~default:exp_time in
915915+ if Ptime.is_later now ~than:exp_with_leeway then
916916+ Error Token_expired
917917+ else Ok ()
918918+ in
919919+ (* Check nbf claim *)
920920+ let* () =
921921+ match Claims.nbf claims with
922922+ | None -> Ok ()
923923+ | Some nbf_time ->
924924+ let nbf_with_leeway = Ptime.sub_span nbf_time leeway |> Option.value ~default:nbf_time in
925925+ if Ptime.is_earlier now ~than:nbf_with_leeway then
926926+ Error Token_not_yet_valid
927927+ else Ok ()
928928+ in
929929+ (* Check iss claim *)
930930+ let* () =
931931+ match iss with
932932+ | None -> Ok ()
933933+ | Some expected_iss ->
934934+ match Claims.iss claims with
935935+ | None -> Error Invalid_issuer
936936+ | Some actual_iss ->
937937+ if String.equal expected_iss actual_iss then Ok ()
938938+ else Error Invalid_issuer
939939+ in
940940+ (* Check aud claim *)
941941+ let* () =
942942+ match aud with
943943+ | None -> Ok ()
944944+ | Some expected_aud ->
945945+ let actual_aud = Claims.aud claims in
946946+ if List.mem expected_aud actual_aud then Ok ()
947947+ else Error Invalid_audience
948948+ in
949949+ Ok ()
950950+951951+let verify_and_validate ~key ~now ?allow_none ?allowed_algs ?iss ?aud ?leeway t =
952952+ let ( let* ) = Result.bind in
953953+ let* () = verify ~key ?allow_none ?allowed_algs t in
954954+ validate ~now ?iss ?aud ?leeway t
955955+956956+(* Creation *)
957957+let create ~header ~claims ~key =
958958+ let ( let* ) = Result.bind in
959959+ let header_json = Header.to_json header in
960960+ let claims_json = Claims.to_json claims in
961961+ let header_b64 = base64url_encode header_json in
962962+ let payload_b64 = base64url_encode claims_json in
963963+ let signing_input = header_b64 ^ "." ^ payload_b64 in
964964+ let* signature =
965965+ match header.Header.alg, key.Jwk.key_data with
966966+ | Algorithm.None, _ -> Ok ""
967967+ | Algorithm.HS256, Jwk.Symmetric { k } ->
968968+ Ok (Sign.hmac_sha256 ~key:k signing_input)
969969+ | Algorithm.HS384, Jwk.Symmetric { k } ->
970970+ Ok (Sign.hmac_sha384 ~key:k signing_input)
971971+ | Algorithm.HS512, Jwk.Symmetric { k } ->
972972+ Ok (Sign.hmac_sha512 ~key:k signing_input)
973973+ | Algorithm.EdDSA, Jwk.Ed25519_priv { x = _; d } ->
974974+ Sign.ed25519_sign ~priv:d signing_input
975975+ | Algorithm.ES256, Jwk.P256_priv { x = _; y = _; d } ->
976976+ Sign.p256_sign ~priv:d signing_input
977977+ | Algorithm.ES384, Jwk.P384_priv { x = _; y = _; d } ->
978978+ Sign.p384_sign ~priv:d signing_input
979979+ | Algorithm.ES512, Jwk.P521_priv { x = _; y = _; d } ->
980980+ Sign.p521_sign ~priv:d signing_input
981981+ | alg, _ ->
982982+ Error (Key_type_mismatch
983983+ (Printf.sprintf "Cannot sign with algorithm %s and given key"
984984+ (Algorithm.to_string alg)))
985985+ in
986986+ let sig_b64 = base64url_encode signature in
987987+ let raw = signing_input ^ "." ^ sig_b64 in
988988+ Ok { header; claims; signature; raw }
989989+990990+let encode t = t.raw
991991+992992+(* Utilities *)
993993+let is_expired ~now ?(leeway = Ptime.Span.zero) t =
994994+ match Claims.exp t.claims with
995995+ | None -> false
996996+ | Some exp_time ->
997997+ let exp_with_leeway = Ptime.add_span exp_time leeway |> Option.value ~default:exp_time in
998998+ Ptime.is_later now ~than:exp_with_leeway
999999+10001000+let time_to_expiry ~now t =
10011001+ match Claims.exp t.claims with
10021002+ | None -> None
10031003+ | Some exp_time ->
10041004+ let diff = Ptime.diff exp_time now in
10051005+ if Ptime.Span.compare diff Ptime.Span.zero <= 0 then None
10061006+ else Some diff
+472
ocaml-jsonwt/lib/jsonwt.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** JSON Web Token (JWT) - RFC 7519
77+88+ This module implements JSON Web Tokens as specified in
99+ {{:https://datatracker.ietf.org/doc/html/rfc7519}RFC 7519}.
1010+1111+ JWTs are compact, URL-safe means of representing claims to be transferred
1212+ between two parties. The claims are encoded as a JSON object that is used
1313+ as the payload of a JSON Web Signature (JWS) structure, enabling the claims
1414+ to be digitally signed or integrity protected with a Message Authentication
1515+ Code (MAC).
1616+1717+ {2 References}
1818+ {ul
1919+ {- {{:https://datatracker.ietf.org/doc/html/rfc7519}RFC 7519} - JSON Web Token (JWT)}
2020+ {- {{:https://datatracker.ietf.org/doc/html/rfc7515}RFC 7515} - JSON Web Signature (JWS)}
2121+ {- {{:https://datatracker.ietf.org/doc/html/rfc7517}RFC 7517} - JSON Web Key (JWK)}
2222+ {- {{:https://datatracker.ietf.org/doc/html/rfc7518}RFC 7518} - JSON Web Algorithms (JWA)}} *)
2323+2424+(** {1 Error Handling} *)
2525+2626+type error =
2727+ | Invalid_json of string
2828+ (** JSON parsing failed *)
2929+ | Invalid_base64url of string
3030+ (** Base64url decoding failed *)
3131+ | Invalid_structure of string
3232+ (** Wrong number of parts or malformed structure *)
3333+ | Invalid_header of string
3434+ (** Header validation failed *)
3535+ | Invalid_claims of string
3636+ (** Claims validation failed *)
3737+ | Invalid_uri of string
3838+ (** StringOrURI validation failed per
3939+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-2}RFC 7519 Section 2} *)
4040+ | Duplicate_claim of string
4141+ (** Duplicate claim name found in strict mode per
4242+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4}RFC 7519 Section 4} *)
4343+ | Unsupported_algorithm of string
4444+ (** Unknown algorithm identifier *)
4545+ | Algorithm_not_allowed of string
4646+ (** Algorithm rejected by allowed_algs policy *)
4747+ | Signature_mismatch
4848+ (** Signature verification failed *)
4949+ | Token_expired
5050+ (** exp claim validation failed per
5151+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4}RFC 7519 Section 4.1.4} *)
5252+ | Token_not_yet_valid
5353+ (** nbf claim validation failed per
5454+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5}RFC 7519 Section 4.1.5} *)
5555+ | Invalid_issuer
5656+ (** iss claim mismatch per
5757+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1}RFC 7519 Section 4.1.1} *)
5858+ | Invalid_audience
5959+ (** aud claim mismatch per
6060+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3}RFC 7519 Section 4.1.3} *)
6161+ | Key_type_mismatch of string
6262+ (** Key doesn't match algorithm *)
6363+ | Unsecured_not_allowed
6464+ (** alg:none used without explicit opt-in per
6565+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-6}RFC 7519 Section 6} *)
6666+ | Nesting_too_deep
6767+ (** Nested JWT exceeds max_depth *)
6868+6969+val pp_error : Format.formatter -> error -> unit
7070+(** Pretty-print an error. *)
7171+7272+val error_to_string : error -> string
7373+(** Convert error to human-readable string. *)
7474+7575+(** {1 Algorithms}
7676+7777+ Signature and MAC algorithms for JWT.
7878+ See {{:https://datatracker.ietf.org/doc/html/rfc7518#section-3}RFC 7518 Section 3}. *)
7979+8080+module Algorithm : sig
8181+ type t =
8282+ | None (** No digital signature or MAC per
8383+ {{:https://datatracker.ietf.org/doc/html/rfc7518#section-3.6}RFC 7518 Section 3.6} *)
8484+ | HS256 (** HMAC using SHA-256 per
8585+ {{:https://datatracker.ietf.org/doc/html/rfc7518#section-3.2}RFC 7518 Section 3.2} *)
8686+ | HS384 (** HMAC using SHA-384 *)
8787+ | HS512 (** HMAC using SHA-512 *)
8888+ | RS256 (** RSASSA-PKCS1-v1_5 using SHA-256 per
8989+ {{:https://datatracker.ietf.org/doc/html/rfc7518#section-3.3}RFC 7518 Section 3.3} *)
9090+ | RS384 (** RSASSA-PKCS1-v1_5 using SHA-384 *)
9191+ | RS512 (** RSASSA-PKCS1-v1_5 using SHA-512 *)
9292+ | ES256 (** ECDSA using P-256 and SHA-256 per
9393+ {{:https://datatracker.ietf.org/doc/html/rfc7518#section-3.4}RFC 7518 Section 3.4} *)
9494+ | ES384 (** ECDSA using P-384 and SHA-384 *)
9595+ | ES512 (** ECDSA using P-521 and SHA-512 *)
9696+ | EdDSA (** EdDSA using Ed25519 per
9797+ {{:https://datatracker.ietf.org/doc/html/rfc8037}RFC 8037} *)
9898+9999+ val to_string : t -> string
100100+ (** Convert algorithm to JWA identifier string. *)
101101+102102+ val of_string : string -> (t, error) result
103103+ (** Parse algorithm from JWA identifier string. *)
104104+105105+ val all : t list
106106+ (** All supported algorithms (excluding None). *)
107107+108108+ val all_with_none : t list
109109+ (** All supported algorithms (including None). *)
110110+end
111111+112112+(** {1 JSON Web Key}
113113+114114+ Key representation for JWT signature verification.
115115+ See {{:https://datatracker.ietf.org/doc/html/rfc7517}RFC 7517}. *)
116116+117117+module Jwk : sig
118118+119119+ (** Key type per {{:https://datatracker.ietf.org/doc/html/rfc7517#section-4.1}RFC 7517 Section 4.1}. *)
120120+ type kty =
121121+ | Oct (** Octet sequence (symmetric key) *)
122122+ | Rsa (** RSA key *)
123123+ | Ec (** Elliptic Curve key *)
124124+ | Okp (** Octet Key Pair (Ed25519, X25519) *)
125125+126126+ (** Elliptic curve identifiers per {{:https://datatracker.ietf.org/doc/html/rfc7518#section-6.2.1.1}RFC 7518 Section 6.2.1.1}. *)
127127+ type crv =
128128+ | P256 (** NIST P-256 curve *)
129129+ | P384 (** NIST P-384 curve *)
130130+ | P521 (** NIST P-521 curve *)
131131+ | Ed25519 (** Ed25519 curve per {{:https://datatracker.ietf.org/doc/html/rfc8037}RFC 8037} *)
132132+133133+ (** A JSON Web Key. *)
134134+ type t
135135+136136+ (** {2 Constructors} *)
137137+138138+ val symmetric : string -> t
139139+ (** [symmetric k] creates a symmetric key from raw bytes.
140140+ Used for HMAC algorithms (HS256, HS384, HS512). *)
141141+142142+ val ed25519_pub : string -> t
143143+ (** [ed25519_pub pub] creates an Ed25519 public key from 32-byte public key. *)
144144+145145+ val ed25519_priv : pub:string -> priv:string -> t
146146+ (** [ed25519_priv ~pub ~priv] creates an Ed25519 private key. *)
147147+148148+ val p256_pub : x:string -> y:string -> t
149149+ (** [p256_pub ~x ~y] creates a P-256 public key from coordinates. *)
150150+151151+ val p256_priv : x:string -> y:string -> d:string -> t
152152+ (** [p256_priv ~x ~y ~d] creates a P-256 private key. *)
153153+154154+ val p384_pub : x:string -> y:string -> t
155155+ (** [p384_pub ~x ~y] creates a P-384 public key from coordinates. *)
156156+157157+ val p384_priv : x:string -> y:string -> d:string -> t
158158+ (** [p384_priv ~x ~y ~d] creates a P-384 private key. *)
159159+160160+ val p521_pub : x:string -> y:string -> t
161161+ (** [p521_pub ~x ~y] creates a P-521 public key from coordinates. *)
162162+163163+ val p521_priv : x:string -> y:string -> d:string -> t
164164+ (** [p521_priv ~x ~y ~d] creates a P-521 private key. *)
165165+166166+ val rsa_pub : n:string -> e:string -> t
167167+ (** [rsa_pub ~n ~e] creates an RSA public key from modulus and exponent. *)
168168+169169+ val rsa_priv :
170170+ n:string -> e:string -> d:string -> p:string -> q:string ->
171171+ dp:string -> dq:string -> qi:string -> t
172172+ (** [rsa_priv ~n ~e ~d ~p ~q ~dp ~dq ~qi] creates an RSA private key. *)
173173+174174+ (** {2 Accessors} *)
175175+176176+ val kty : t -> kty
177177+ (** Get the key type. *)
178178+179179+ val kid : t -> string option
180180+ (** Get the key ID if set. *)
181181+182182+ val alg : t -> Algorithm.t option
183183+ (** Get the intended algorithm if set. *)
184184+185185+ val with_kid : string -> t -> t
186186+ (** [with_kid id key] returns key with kid set to [id]. *)
187187+188188+ val with_alg : Algorithm.t -> t -> t
189189+ (** [with_alg alg key] returns key with alg set to [alg]. *)
190190+191191+ (** {2 Serialization} *)
192192+193193+ val of_json : string -> (t, error) result
194194+ (** Parse a JWK from JSON string. *)
195195+196196+ val to_json : t -> string
197197+ (** Serialize a JWK to JSON string. *)
198198+end
199199+200200+(** {1 JOSE Header}
201201+202202+ The JOSE (JSON Object Signing and Encryption) Header.
203203+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-5}RFC 7519 Section 5}. *)
204204+205205+module Header : sig
206206+ type t = {
207207+ alg : Algorithm.t; (** Algorithm used (REQUIRED) *)
208208+ typ : string option; (** Type - RECOMMENDED to be "JWT" per
209209+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-5.1}RFC 7519 Section 5.1} *)
210210+ kid : string option; (** Key ID for key lookup *)
211211+ cty : string option; (** Content type - MUST be "JWT" for nested JWTs per
212212+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-5.2}RFC 7519 Section 5.2} *)
213213+ }
214214+215215+ val make : ?typ:string -> ?kid:string -> ?cty:string -> Algorithm.t -> t
216216+ (** [make ?typ ?kid ?cty alg] creates a JOSE header. *)
217217+218218+ val of_json : string -> (t, error) result
219219+ (** Parse header from JSON string. *)
220220+221221+ val to_json : t -> string
222222+ (** Serialize header to JSON string. *)
223223+224224+ val is_nested : t -> bool
225225+ (** [is_nested h] returns true if [cty] is "JWT" (case-insensitive),
226226+ indicating a nested JWT. *)
227227+end
228228+229229+(** {1 Claims}
230230+231231+ JWT Claims Set.
232232+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4}RFC 7519 Section 4}. *)
233233+234234+module Claims : sig
235235+ type t
236236+237237+ (** {2 Registered Claim Names}
238238+239239+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1}RFC 7519 Section 4.1}. *)
240240+241241+ val iss : t -> string option
242242+ (** Issuer claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1}Section 4.1.1}. *)
243243+244244+ val sub : t -> string option
245245+ (** Subject claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2}Section 4.1.2}. *)
246246+247247+ val aud : t -> string list
248248+ (** Audience claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3}Section 4.1.3}.
249249+ Returns empty list if not present. May be single string or array in JWT. *)
250250+251251+ val exp : t -> Ptime.t option
252252+ (** Expiration time claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4}Section 4.1.4}. *)
253253+254254+ val nbf : t -> Ptime.t option
255255+ (** Not Before claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5}Section 4.1.5}. *)
256256+257257+ val iat : t -> Ptime.t option
258258+ (** Issued At claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6}Section 4.1.6}. *)
259259+260260+ val jti : t -> string option
261261+ (** JWT ID claim per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7}Section 4.1.7}. *)
262262+263263+ (** {2 Custom Claims}
264264+265265+ For Public and Private claims per
266266+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.2}Sections 4.2} and
267267+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4.3}4.3}. *)
268268+269269+ val get : string -> t -> Jsont.json option
270270+ (** [get name claims] returns the value of custom claim [name]. *)
271271+272272+ val get_string : string -> t -> string option
273273+ (** [get_string name claims] returns the string value of custom claim [name]. *)
274274+275275+ val get_int : string -> t -> int option
276276+ (** [get_int name claims] returns the integer value of custom claim [name]. *)
277277+278278+ val get_bool : string -> t -> bool option
279279+ (** [get_bool name claims] returns the boolean value of custom claim [name]. *)
280280+281281+ (** {2 Construction} *)
282282+283283+ type builder
284284+ (** Builder for constructing claims. *)
285285+286286+ val empty : builder
287287+ (** Empty claims builder. *)
288288+289289+ val set_iss : string -> builder -> builder
290290+ (** Set issuer claim. Value is validated as StringOrURI. *)
291291+292292+ val set_sub : string -> builder -> builder
293293+ (** Set subject claim. Value is validated as StringOrURI. *)
294294+295295+ val set_aud : string list -> builder -> builder
296296+ (** Set audience claim. *)
297297+298298+ val set_exp : Ptime.t -> builder -> builder
299299+ (** Set expiration time claim. *)
300300+301301+ val set_nbf : Ptime.t -> builder -> builder
302302+ (** Set not-before claim. *)
303303+304304+ val set_iat : Ptime.t -> builder -> builder
305305+ (** Set issued-at claim. *)
306306+307307+ val set_jti : string -> builder -> builder
308308+ (** Set JWT ID claim. *)
309309+310310+ val set : string -> Jsont.json -> builder -> builder
311311+ (** [set name value builder] sets a custom claim. *)
312312+313313+ val set_string : string -> string -> builder -> builder
314314+ (** Set a custom string claim. *)
315315+316316+ val set_int : string -> int -> builder -> builder
317317+ (** Set a custom integer claim. *)
318318+319319+ val set_bool : string -> bool -> builder -> builder
320320+ (** Set a custom boolean claim. *)
321321+322322+ val build : builder -> t
323323+ (** Build the claims set. *)
324324+325325+ (** {2 Serialization} *)
326326+327327+ val of_json : ?strict:bool -> string -> (t, error) result
328328+ (** [of_json ?strict json] parses claims from JSON string.
329329+ @param strict If true (default), reject duplicate claim names per
330330+ {{:https://datatracker.ietf.org/doc/html/rfc7519#section-4}RFC 7519 Section 4}.
331331+ If false, use lexically last duplicate. *)
332332+333333+ val to_json : t -> string
334334+ (** Serialize claims to JSON string. *)
335335+end
336336+337337+(** {1 JWT Token} *)
338338+339339+type t = {
340340+ header : Header.t; (** JOSE header *)
341341+ claims : Claims.t; (** Claims set *)
342342+ signature : string; (** Raw signature bytes *)
343343+ raw : string; (** Original compact serialization *)
344344+}
345345+(** A parsed JWT token. *)
346346+347347+(** {2 Parsing}
348348+349349+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-7.2}RFC 7519 Section 7.2}. *)
350350+351351+val parse : ?strict:bool -> string -> (t, error) result
352352+(** [parse ?strict token_string] parses a JWT from its compact serialization.
353353+354354+ This parses the token structure but does NOT verify the signature.
355355+ Use {!verify} to validate the signature after parsing.
356356+357357+ @param strict If true (default), reject duplicate claim names. *)
358358+359359+val parse_unsafe : string -> (t, error) result
360360+(** [parse_unsafe token_string] parses a JWT without strict validation.
361361+ Equivalent to [parse ~strict:false]. *)
362362+363363+(** {2 Nested JWTs}
364364+365365+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-7.2}RFC 7519 Section 7.2 step 8}
366366+ and {{:https://datatracker.ietf.org/doc/html/rfc7519#appendix-A.2}Appendix A.2}. *)
367367+368368+val parse_nested :
369369+ ?strict:bool ->
370370+ ?max_depth:int ->
371371+ string ->
372372+ (t list, error) result
373373+(** [parse_nested ?strict ?max_depth token] parses a potentially nested JWT.
374374+ Returns a list of JWTs from outermost to innermost.
375375+ @param max_depth Maximum nesting depth (default 5). *)
376376+377377+val is_nested : t -> bool
378378+(** [is_nested t] returns true if the JWT has [cty: "JWT"] header,
379379+ indicating it contains a nested JWT. *)
380380+381381+(** {2 Accessors} *)
382382+383383+val header : t -> Header.t
384384+(** [header t] returns the JOSE header. *)
385385+386386+val claims : t -> Claims.t
387387+(** [claims t] returns the claims set. *)
388388+389389+val signature : t -> string
390390+(** [signature t] returns the raw signature bytes. *)
391391+392392+val raw : t -> string
393393+(** [raw t] returns the original token string. *)
394394+395395+(** {2 Verification}
396396+397397+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-7.2}RFC 7519 Section 7.2}. *)
398398+399399+val verify :
400400+ key:Jwk.t ->
401401+ ?allow_none:bool ->
402402+ ?allowed_algs:Algorithm.t list ->
403403+ t ->
404404+ (unit, error) result
405405+(** [verify ~key ?allow_none ?allowed_algs t] verifies the JWT signature.
406406+407407+ @param key The key to verify with (must match algorithm)
408408+ @param allow_none If true, accept [alg:"none"]. Default: false.
409409+ Per {{:https://datatracker.ietf.org/doc/html/rfc7519#section-6}RFC 7519 Section 6},
410410+ unsecured JWTs should only be used when security is provided by other means.
411411+ @param allowed_algs List of acceptable algorithms. Default: all except none.
412412+ Note: "none" is only allowed if BOTH in this list AND [allow_none=true]. *)
413413+414414+val validate :
415415+ now:Ptime.t ->
416416+ ?iss:string ->
417417+ ?aud:string ->
418418+ ?leeway:Ptime.Span.t ->
419419+ t ->
420420+ (unit, error) result
421421+(** [validate ~now ?iss ?aud ?leeway t] validates JWT claims.
422422+423423+ @param now Current time (required, no implicit clock)
424424+ @param iss Expected issuer (if provided, must match exactly)
425425+ @param aud Expected audience (if provided, must be in audience list)
426426+ @param leeway Clock skew tolerance for exp/nbf checks (default 0s) *)
427427+428428+val verify_and_validate :
429429+ key:Jwk.t ->
430430+ now:Ptime.t ->
431431+ ?allow_none:bool ->
432432+ ?allowed_algs:Algorithm.t list ->
433433+ ?iss:string ->
434434+ ?aud:string ->
435435+ ?leeway:Ptime.Span.t ->
436436+ t ->
437437+ (unit, error) result
438438+(** [verify_and_validate ~key ~now ...] verifies signature and validates claims. *)
439439+440440+(** {2 Creation}
441441+442442+ See {{:https://datatracker.ietf.org/doc/html/rfc7519#section-7.1}RFC 7519 Section 7.1}. *)
443443+444444+val create : header:Header.t -> claims:Claims.t -> key:Jwk.t -> (t, error) result
445445+(** [create ~header ~claims ~key] creates and signs a new JWT.
446446+447447+ The [key] must be appropriate for the algorithm specified in [header].
448448+ For [alg:none], pass any key (it will be ignored). *)
449449+450450+val encode : t -> string
451451+(** [encode t] returns the compact serialization of the JWT. *)
452452+453453+(** {1 Utilities} *)
454454+455455+val is_expired : now:Ptime.t -> ?leeway:Ptime.Span.t -> t -> bool
456456+(** [is_expired ~now ?leeway t] checks if the token has expired.
457457+ Returns false if no exp claim present. *)
458458+459459+val time_to_expiry : now:Ptime.t -> t -> Ptime.Span.t option
460460+(** [time_to_expiry ~now t] returns time until expiration, or [None] if
461461+ no expiration claim or already expired. *)
462462+463463+(** {1 Base64url Utilities}
464464+465465+ Exposed for testing with RFC test vectors. *)
466466+467467+val base64url_encode : string -> string
468468+(** Base64url encode without padding per
469469+ {{:https://datatracker.ietf.org/doc/html/rfc7515#appendix-C}RFC 7515 Appendix C}. *)
470470+471471+val base64url_decode : string -> (string, error) result
472472+(** Base64url decode, handling missing padding. *)
+1683
ocaml-jsonwt/spec/rfc7519.txt
···11+22+33+44+55+66+77+Internet Engineering Task Force (IETF) M. Jones
88+Request for Comments: 7519 Microsoft
99+Category: Standards Track J. Bradley
1010+ISSN: 2070-1721 Ping Identity
1111+ N. Sakimura
1212+ NRI
1313+ May 2015
1414+1515+1616+ JSON Web Token (JWT)
1717+1818+Abstract
1919+2020+ JSON Web Token (JWT) is a compact, URL-safe means of representing
2121+ claims to be transferred between two parties. The claims in a JWT
2222+ are encoded as a JSON object that is used as the payload of a JSON
2323+ Web Signature (JWS) structure or as the plaintext of a JSON Web
2424+ Encryption (JWE) structure, enabling the claims to be digitally
2525+ signed or integrity protected with a Message Authentication Code
2626+ (MAC) and/or encrypted.
2727+2828+Status of This Memo
2929+3030+ This is an Internet Standards Track document.
3131+3232+ This document is a product of the Internet Engineering Task Force
3333+ (IETF). It represents the consensus of the IETF community. It has
3434+ received public review and has been approved for publication by the
3535+ Internet Engineering Steering Group (IESG). Further information on
3636+ Internet Standards is available in Section 2 of RFC 5741.
3737+3838+ Information about the current status of this document, any errata,
3939+ and how to provide feedback on it may be obtained at
4040+ http://www.rfc-editor.org/info/rfc7519.
4141+4242+4343+4444+4545+4646+4747+4848+4949+5050+5151+5252+5353+5454+5555+5656+5757+5858+Jones, et al. Standards Track [Page 1]
5959+6060+RFC 7519 JSON Web Token (JWT) May 2015
6161+6262+6363+Copyright Notice
6464+6565+ Copyright (c) 2015 IETF Trust and the persons identified as the
6666+ document authors. All rights reserved.
6767+6868+ This document is subject to BCP 78 and the IETF Trust's Legal
6969+ Provisions Relating to IETF Documents
7070+ (http://trustee.ietf.org/license-info) in effect on the date of
7171+ publication of this document. Please review these documents
7272+ carefully, as they describe your rights and restrictions with respect
7373+ to this document. Code Components extracted from this document must
7474+ include Simplified BSD License text as described in Section 4.e of
7575+ the Trust Legal Provisions and are provided without warranty as
7676+ described in the Simplified BSD License.
7777+7878+7979+8080+8181+8282+8383+8484+8585+8686+8787+8888+8989+9090+9191+9292+9393+9494+9595+9696+9797+9898+9999+100100+101101+102102+103103+104104+105105+106106+107107+108108+109109+110110+111111+112112+113113+114114+Jones, et al. Standards Track [Page 2]
115115+116116+RFC 7519 JSON Web Token (JWT) May 2015
117117+118118+119119+Table of Contents
120120+121121+ 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4
122122+ 1.1. Notational Conventions . . . . . . . . . . . . . . . . . 4
123123+ 2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 4
124124+ 3. JSON Web Token (JWT) Overview . . . . . . . . . . . . . . . . 6
125125+ 3.1. Example JWT . . . . . . . . . . . . . . . . . . . . . . . 7
126126+ 4. JWT Claims . . . . . . . . . . . . . . . . . . . . . . . . . 8
127127+ 4.1. Registered Claim Names . . . . . . . . . . . . . . . . . 9
128128+ 4.1.1. "iss" (Issuer) Claim . . . . . . . . . . . . . . . . 9
129129+ 4.1.2. "sub" (Subject) Claim . . . . . . . . . . . . . . . . 9
130130+ 4.1.3. "aud" (Audience) Claim . . . . . . . . . . . . . . . 9
131131+ 4.1.4. "exp" (Expiration Time) Claim . . . . . . . . . . . . 9
132132+ 4.1.5. "nbf" (Not Before) Claim . . . . . . . . . . . . . . 10
133133+ 4.1.6. "iat" (Issued At) Claim . . . . . . . . . . . . . . . 10
134134+ 4.1.7. "jti" (JWT ID) Claim . . . . . . . . . . . . . . . . 10
135135+ 4.2. Public Claim Names . . . . . . . . . . . . . . . . . . . 10
136136+ 4.3. Private Claim Names . . . . . . . . . . . . . . . . . . . 10
137137+ 5. JOSE Header . . . . . . . . . . . . . . . . . . . . . . . . . 11
138138+ 5.1. "typ" (Type) Header Parameter . . . . . . . . . . . . . . 11
139139+ 5.2. "cty" (Content Type) Header Parameter . . . . . . . . . . 11
140140+ 5.3. Replicating Claims as Header Parameters . . . . . . . . . 12
141141+ 6. Unsecured JWTs . . . . . . . . . . . . . . . . . . . . . . . 12
142142+ 6.1. Example Unsecured JWT . . . . . . . . . . . . . . . . . . 12
143143+ 7. Creating and Validating JWTs . . . . . . . . . . . . . . . . 13
144144+ 7.1. Creating a JWT . . . . . . . . . . . . . . . . . . . . . 13
145145+ 7.2. Validating a JWT . . . . . . . . . . . . . . . . . . . . 14
146146+ 7.3. String Comparison Rules . . . . . . . . . . . . . . . . . 15
147147+ 8. Implementation Requirements . . . . . . . . . . . . . . . . . 16
148148+ 9. URI for Declaring that Content is a JWT . . . . . . . . . . . 17
149149+ 10. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 17
150150+ 10.1. JSON Web Token Claims Registry . . . . . . . . . . . . . 17
151151+ 10.1.1. Registration Template . . . . . . . . . . . . . . . 18
152152+ 10.1.2. Initial Registry Contents . . . . . . . . . . . . . 18
153153+ 10.2. Sub-Namespace Registration of
154154+ urn:ietf:params:oauth:token-type:jwt . . . . . . . . . . 19
155155+ 10.2.1. Registry Contents . . . . . . . . . . . . . . . . . 19
156156+ 10.3. Media Type Registration . . . . . . . . . . . . . . . . 20
157157+ 10.3.1. Registry Contents . . . . . . . . . . . . . . . . . 20
158158+ 10.4. Header Parameter Names Registration . . . . . . . . . . 20
159159+ 10.4.1. Registry Contents . . . . . . . . . . . . . . . . . 21
160160+ 11. Security Considerations . . . . . . . . . . . . . . . . . . . 21
161161+ 11.1. Trust Decisions . . . . . . . . . . . . . . . . . . . . 21
162162+ 11.2. Signing and Encryption Order . . . . . . . . . . . . . . 21
163163+ 12. Privacy Considerations . . . . . . . . . . . . . . . . . . . 22
164164+ 13. References . . . . . . . . . . . . . . . . . . . . . . . . . 22
165165+ 13.1. Normative References . . . . . . . . . . . . . . . . . . 22
166166+ 13.2. Informative References . . . . . . . . . . . . . . . . . 23
167167+168168+169169+170170+Jones, et al. Standards Track [Page 3]
171171+172172+RFC 7519 JSON Web Token (JWT) May 2015
173173+174174+175175+ Appendix A. JWT Examples . . . . . . . . . . . . . . . . . . . . 26
176176+ A.1. Example Encrypted JWT . . . . . . . . . . . . . . . . . . 26
177177+ A.2. Example Nested JWT . . . . . . . . . . . . . . . . . . . 26
178178+ Appendix B. Relationship of JWTs to SAML Assertions . . . . . . 28
179179+ Appendix C. Relationship of JWTs to Simple Web Tokens (SWTs) . . 28
180180+ Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . 28
181181+ Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 29
182182+183183+1. Introduction
184184+185185+ JSON Web Token (JWT) is a compact claims representation format
186186+ intended for space constrained environments such as HTTP
187187+ Authorization headers and URI query parameters. JWTs encode claims
188188+ to be transmitted as a JSON [RFC7159] object that is used as the
189189+ payload of a JSON Web Signature (JWS) [JWS] structure or as the
190190+ plaintext of a JSON Web Encryption (JWE) [JWE] structure, enabling
191191+ the claims to be digitally signed or integrity protected with a
192192+ Message Authentication Code (MAC) and/or encrypted. JWTs are always
193193+ represented using the JWS Compact Serialization or the JWE Compact
194194+ Serialization.
195195+196196+ The suggested pronunciation of JWT is the same as the English word
197197+ "jot".
198198+199199+1.1. Notational Conventions
200200+201201+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
202202+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
203203+ "OPTIONAL" in this document are to be interpreted as described in
204204+ "Key words for use in RFCs to Indicate Requirement Levels" [RFC2119].
205205+ The interpretation should only be applied when the terms appear in
206206+ all capital letters.
207207+208208+2. Terminology
209209+210210+ The terms "JSON Web Signature (JWS)", "Base64url Encoding", "Header
211211+ Parameter", "JOSE Header", "JWS Compact Serialization", "JWS
212212+ Payload", "JWS Signature", and "Unsecured JWS" are defined by the JWS
213213+ specification [JWS].
214214+215215+ The terms "JSON Web Encryption (JWE)", "Content Encryption Key
216216+ (CEK)", "JWE Compact Serialization", "JWE Encrypted Key", and "JWE
217217+ Initialization Vector" are defined by the JWE specification [JWE].
218218+219219+ The terms "Ciphertext", "Digital Signature", "Message Authentication
220220+ Code (MAC)", and "Plaintext" are defined by the "Internet Security
221221+ Glossary, Version 2" [RFC4949].
222222+223223+224224+225225+226226+Jones, et al. Standards Track [Page 4]
227227+228228+RFC 7519 JSON Web Token (JWT) May 2015
229229+230230+231231+ These terms are defined by this specification:
232232+233233+ JSON Web Token (JWT)
234234+ A string representing a set of claims as a JSON object that is
235235+ encoded in a JWS or JWE, enabling the claims to be digitally
236236+ signed or MACed and/or encrypted.
237237+238238+ JWT Claims Set
239239+ A JSON object that contains the claims conveyed by the JWT.
240240+241241+ Claim
242242+ A piece of information asserted about a subject. A claim is
243243+ represented as a name/value pair consisting of a Claim Name and a
244244+ Claim Value.
245245+246246+ Claim Name
247247+ The name portion of a claim representation. A Claim Name is
248248+ always a string.
249249+250250+ Claim Value
251251+ The value portion of a claim representation. A Claim Value can be
252252+ any JSON value.
253253+254254+ Nested JWT
255255+ A JWT in which nested signing and/or encryption are employed. In
256256+ Nested JWTs, a JWT is used as the payload or plaintext value of an
257257+ enclosing JWS or JWE structure, respectively.
258258+259259+ Unsecured JWT
260260+ A JWT whose claims are not integrity protected or encrypted.
261261+262262+ Collision-Resistant Name
263263+ A name in a namespace that enables names to be allocated in a
264264+ manner such that they are highly unlikely to collide with other
265265+ names. Examples of collision-resistant namespaces include: Domain
266266+ Names, Object Identifiers (OIDs) as defined in the ITU-T X.660 and
267267+ X.670 Recommendation series, and Universally Unique IDentifiers
268268+ (UUIDs) [RFC4122]. When using an administratively delegated
269269+ namespace, the definer of a name needs to take reasonable
270270+ precautions to ensure they are in control of the portion of the
271271+ namespace they use to define the name.
272272+273273+ StringOrURI
274274+ A JSON string value, with the additional requirement that while
275275+ arbitrary string values MAY be used, any value containing a ":"
276276+ character MUST be a URI [RFC3986]. StringOrURI values are
277277+ compared as case-sensitive strings with no transformations or
278278+ canonicalizations applied.
279279+280280+281281+282282+Jones, et al. Standards Track [Page 5]
283283+284284+RFC 7519 JSON Web Token (JWT) May 2015
285285+286286+287287+ NumericDate
288288+ A JSON numeric value representing the number of seconds from
289289+ 1970-01-01T00:00:00Z UTC until the specified UTC date/time,
290290+ ignoring leap seconds. This is equivalent to the IEEE Std 1003.1,
291291+ 2013 Edition [POSIX.1] definition "Seconds Since the Epoch", in
292292+ which each day is accounted for by exactly 86400 seconds, other
293293+ than that non-integer values can be represented. See RFC 3339
294294+ [RFC3339] for details regarding date/times in general and UTC in
295295+ particular.
296296+297297+3. JSON Web Token (JWT) Overview
298298+299299+ JWTs represent a set of claims as a JSON object that is encoded in a
300300+ JWS and/or JWE structure. This JSON object is the JWT Claims Set.
301301+ As per Section 4 of RFC 7159 [RFC7159], the JSON object consists of
302302+ zero or more name/value pairs (or members), where the names are
303303+ strings and the values are arbitrary JSON values. These members are
304304+ the claims represented by the JWT. This JSON object MAY contain
305305+ whitespace and/or line breaks before or after any JSON values or
306306+ structural characters, in accordance with Section 2 of RFC 7159
307307+ [RFC7159].
308308+309309+ The member names within the JWT Claims Set are referred to as Claim
310310+ Names. The corresponding values are referred to as Claim Values.
311311+312312+ The contents of the JOSE Header describe the cryptographic operations
313313+ applied to the JWT Claims Set. If the JOSE Header is for a JWS, the
314314+ JWT is represented as a JWS and the claims are digitally signed or
315315+ MACed, with the JWT Claims Set being the JWS Payload. If the JOSE
316316+ Header is for a JWE, the JWT is represented as a JWE and the claims
317317+ are encrypted, with the JWT Claims Set being the plaintext encrypted
318318+ by the JWE. A JWT may be enclosed in another JWE or JWS structure to
319319+ create a Nested JWT, enabling nested signing and encryption to be
320320+ performed.
321321+322322+ A JWT is represented as a sequence of URL-safe parts separated by
323323+ period ('.') characters. Each part contains a base64url-encoded
324324+ value. The number of parts in the JWT is dependent upon the
325325+ representation of the resulting JWS using the JWS Compact
326326+ Serialization or JWE using the JWE Compact Serialization.
327327+328328+329329+330330+331331+332332+333333+334334+335335+336336+337337+338338+Jones, et al. Standards Track [Page 6]
339339+340340+RFC 7519 JSON Web Token (JWT) May 2015
341341+342342+343343+3.1. Example JWT
344344+345345+ The following example JOSE Header declares that the encoded object is
346346+ a JWT, and the JWT is a JWS that is MACed using the HMAC SHA-256
347347+ algorithm:
348348+349349+ {"typ":"JWT",
350350+ "alg":"HS256"}
351351+352352+ To remove potential ambiguities in the representation of the JSON
353353+ object above, the octet sequence for the actual UTF-8 representation
354354+ used in this example for the JOSE Header above is also included
355355+ below. (Note that ambiguities can arise due to differing platform
356356+ representations of line breaks (CRLF versus LF), differing spacing at
357357+ the beginning and ends of lines, whether the last line has a
358358+ terminating line break or not, and other causes. In the
359359+ representation used in this example, the first line has no leading or
360360+ trailing spaces, a CRLF line break (13, 10) occurs between the first
361361+ and second lines, the second line has one leading space (32) and no
362362+ trailing spaces, and the last line does not have a terminating line
363363+ break.) The octets representing the UTF-8 representation of the JOSE
364364+ Header in this example (using JSON array notation) are:
365365+366366+ [123, 34, 116, 121, 112, 34, 58, 34, 74, 87, 84, 34, 44, 13, 10, 32,
367367+ 34, 97, 108, 103, 34, 58, 34, 72, 83, 50, 53, 54, 34, 125]
368368+369369+ Base64url encoding the octets of the UTF-8 representation of the JOSE
370370+ Header yields this encoded JOSE Header value:
371371+372372+ eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
373373+374374+ The following is an example of a JWT Claims Set:
375375+376376+ {"iss":"joe",
377377+ "exp":1300819380,
378378+ "http://example.com/is_root":true}
379379+380380+ The following octet sequence, which is the UTF-8 representation used
381381+ in this example for the JWT Claims Set above, is the JWS Payload:
382382+383383+ [123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10,
384384+ 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56,
385385+ 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97,
386386+ 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111,
387387+ 111, 116, 34, 58, 116, 114, 117, 101, 125]
388388+389389+390390+391391+392392+393393+394394+Jones, et al. Standards Track [Page 7]
395395+396396+RFC 7519 JSON Web Token (JWT) May 2015
397397+398398+399399+ Base64url encoding the JWS Payload yields this encoded JWS Payload
400400+ (with line breaks for display purposes only):
401401+402402+ eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly
403403+ 9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
404404+405405+ Computing the MAC of the encoded JOSE Header and encoded JWS Payload
406406+ with the HMAC SHA-256 algorithm and base64url encoding the HMAC value
407407+ in the manner specified in [JWS] yields this encoded JWS Signature:
408408+409409+ dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
410410+411411+ Concatenating these encoded parts in this order with period ('.')
412412+ characters between the parts yields this complete JWT (with line
413413+ breaks for display purposes only):
414414+415415+ eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
416416+ .
417417+ eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
418418+ cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
419419+ .
420420+ dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
421421+422422+ This computation is illustrated in more detail in Appendix A.1 of
423423+ [JWS]. See Appendix A.1 for an example of an encrypted JWT.
424424+425425+4. JWT Claims
426426+427427+ The JWT Claims Set represents a JSON object whose members are the
428428+ claims conveyed by the JWT. The Claim Names within a JWT Claims Set
429429+ MUST be unique; JWT parsers MUST either reject JWTs with duplicate
430430+ Claim Names or use a JSON parser that returns only the lexically last
431431+ duplicate member name, as specified in Section 15.12 ("The JSON
432432+ Object") of ECMAScript 5.1 [ECMAScript].
433433+434434+ The set of claims that a JWT must contain to be considered valid is
435435+ context dependent and is outside the scope of this specification.
436436+ Specific applications of JWTs will require implementations to
437437+ understand and process some claims in particular ways. However, in
438438+ the absence of such requirements, all claims that are not understood
439439+ by implementations MUST be ignored.
440440+441441+ There are three classes of JWT Claim Names: Registered Claim Names,
442442+ Public Claim Names, and Private Claim Names.
443443+444444+445445+446446+447447+448448+449449+450450+Jones, et al. Standards Track [Page 8]
451451+452452+RFC 7519 JSON Web Token (JWT) May 2015
453453+454454+455455+4.1. Registered Claim Names
456456+457457+ The following Claim Names are registered in the IANA "JSON Web Token
458458+ Claims" registry established by Section 10.1. None of the claims
459459+ defined below are intended to be mandatory to use or implement in all
460460+ cases, but rather they provide a starting point for a set of useful,
461461+ interoperable claims. Applications using JWTs should define which
462462+ specific claims they use and when they are required or optional. All
463463+ the names are short because a core goal of JWTs is for the
464464+ representation to be compact.
465465+466466+4.1.1. "iss" (Issuer) Claim
467467+468468+ The "iss" (issuer) claim identifies the principal that issued the
469469+ JWT. The processing of this claim is generally application specific.
470470+ The "iss" value is a case-sensitive string containing a StringOrURI
471471+ value. Use of this claim is OPTIONAL.
472472+473473+4.1.2. "sub" (Subject) Claim
474474+475475+ The "sub" (subject) claim identifies the principal that is the
476476+ subject of the JWT. The claims in a JWT are normally statements
477477+ about the subject. The subject value MUST either be scoped to be
478478+ locally unique in the context of the issuer or be globally unique.
479479+ The processing of this claim is generally application specific. The
480480+ "sub" value is a case-sensitive string containing a StringOrURI
481481+ value. Use of this claim is OPTIONAL.
482482+483483+4.1.3. "aud" (Audience) Claim
484484+485485+ The "aud" (audience) claim identifies the recipients that the JWT is
486486+ intended for. Each principal intended to process the JWT MUST
487487+ identify itself with a value in the audience claim. If the principal
488488+ processing the claim does not identify itself with a value in the
489489+ "aud" claim when this claim is present, then the JWT MUST be
490490+ rejected. In the general case, the "aud" value is an array of case-
491491+ sensitive strings, each containing a StringOrURI value. In the
492492+ special case when the JWT has one audience, the "aud" value MAY be a
493493+ single case-sensitive string containing a StringOrURI value. The
494494+ interpretation of audience values is generally application specific.
495495+ Use of this claim is OPTIONAL.
496496+497497+4.1.4. "exp" (Expiration Time) Claim
498498+499499+ The "exp" (expiration time) claim identifies the expiration time on
500500+ or after which the JWT MUST NOT be accepted for processing. The
501501+ processing of the "exp" claim requires that the current date/time
502502+ MUST be before the expiration date/time listed in the "exp" claim.
503503+504504+505505+506506+Jones, et al. Standards Track [Page 9]
507507+508508+RFC 7519 JSON Web Token (JWT) May 2015
509509+510510+511511+ Implementers MAY provide for some small leeway, usually no more than
512512+ a few minutes, to account for clock skew. Its value MUST be a number
513513+ containing a NumericDate value. Use of this claim is OPTIONAL.
514514+515515+4.1.5. "nbf" (Not Before) Claim
516516+517517+ The "nbf" (not before) claim identifies the time before which the JWT
518518+ MUST NOT be accepted for processing. The processing of the "nbf"
519519+ claim requires that the current date/time MUST be after or equal to
520520+ the not-before date/time listed in the "nbf" claim. Implementers MAY
521521+ provide for some small leeway, usually no more than a few minutes, to
522522+ account for clock skew. Its value MUST be a number containing a
523523+ NumericDate value. Use of this claim is OPTIONAL.
524524+525525+4.1.6. "iat" (Issued At) Claim
526526+527527+ The "iat" (issued at) claim identifies the time at which the JWT was
528528+ issued. This claim can be used to determine the age of the JWT. Its
529529+ value MUST be a number containing a NumericDate value. Use of this
530530+ claim is OPTIONAL.
531531+532532+4.1.7. "jti" (JWT ID) Claim
533533+534534+ The "jti" (JWT ID) claim provides a unique identifier for the JWT.
535535+ The identifier value MUST be assigned in a manner that ensures that
536536+ there is a negligible probability that the same value will be
537537+ accidentally assigned to a different data object; if the application
538538+ uses multiple issuers, collisions MUST be prevented among values
539539+ produced by different issuers as well. The "jti" claim can be used
540540+ to prevent the JWT from being replayed. The "jti" value is a case-
541541+ sensitive string. Use of this claim is OPTIONAL.
542542+543543+4.2. Public Claim Names
544544+545545+ Claim Names can be defined at will by those using JWTs. However, in
546546+ order to prevent collisions, any new Claim Name should either be
547547+ registered in the IANA "JSON Web Token Claims" registry established
548548+ by Section 10.1 or be a Public Name: a value that contains a
549549+ Collision-Resistant Name. In each case, the definer of the name or
550550+ value needs to take reasonable precautions to make sure they are in
551551+ control of the part of the namespace they use to define the Claim
552552+ Name.
553553+554554+4.3. Private Claim Names
555555+556556+ A producer and consumer of a JWT MAY agree to use Claim Names that
557557+ are Private Names: names that are not Registered Claim Names
558558+ (Section 4.1) or Public Claim Names (Section 4.2). Unlike Public
559559+560560+561561+562562+Jones, et al. Standards Track [Page 10]
563563+564564+RFC 7519 JSON Web Token (JWT) May 2015
565565+566566+567567+ Claim Names, Private Claim Names are subject to collision and should
568568+ be used with caution.
569569+570570+5. JOSE Header
571571+572572+ For a JWT object, the members of the JSON object represented by the
573573+ JOSE Header describe the cryptographic operations applied to the JWT
574574+ and optionally, additional properties of the JWT. Depending upon
575575+ whether the JWT is a JWS or JWE, the corresponding rules for the JOSE
576576+ Header values apply.
577577+578578+ This specification further specifies the use of the following Header
579579+ Parameters in both the cases where the JWT is a JWS and where it is a
580580+ JWE.
581581+582582+5.1. "typ" (Type) Header Parameter
583583+584584+ The "typ" (type) Header Parameter defined by [JWS] and [JWE] is used
585585+ by JWT applications to declare the media type [IANA.MediaTypes] of
586586+ this complete JWT. This is intended for use by the JWT application
587587+ when values that are not JWTs could also be present in an application
588588+ data structure that can contain a JWT object; the application can use
589589+ this value to disambiguate among the different kinds of objects that
590590+ might be present. It will typically not be used by applications when
591591+ it is already known that the object is a JWT. This parameter is
592592+ ignored by JWT implementations; any processing of this parameter is
593593+ performed by the JWT application. If present, it is RECOMMENDED that
594594+ its value be "JWT" to indicate that this object is a JWT. While
595595+ media type names are not case sensitive, it is RECOMMENDED that "JWT"
596596+ always be spelled using uppercase characters for compatibility with
597597+ legacy implementations. Use of this Header Parameter is OPTIONAL.
598598+599599+5.2. "cty" (Content Type) Header Parameter
600600+601601+ The "cty" (content type) Header Parameter defined by [JWS] and [JWE]
602602+ is used by this specification to convey structural information about
603603+ the JWT.
604604+605605+ In the normal case in which nested signing or encryption operations
606606+ are not employed, the use of this Header Parameter is NOT
607607+ RECOMMENDED. In the case that nested signing or encryption is
608608+ employed, this Header Parameter MUST be present; in this case, the
609609+ value MUST be "JWT", to indicate that a Nested JWT is carried in this
610610+ JWT. While media type names are not case sensitive, it is
611611+ RECOMMENDED that "JWT" always be spelled using uppercase characters
612612+ for compatibility with legacy implementations. See Appendix A.2 for
613613+ an example of a Nested JWT.
614614+615615+616616+617617+618618+Jones, et al. Standards Track [Page 11]
619619+620620+RFC 7519 JSON Web Token (JWT) May 2015
621621+622622+623623+5.3. Replicating Claims as Header Parameters
624624+625625+ In some applications using encrypted JWTs, it is useful to have an
626626+ unencrypted representation of some claims. This might be used, for
627627+ instance, in application processing rules to determine whether and
628628+ how to process the JWT before it is decrypted.
629629+630630+ This specification allows claims present in the JWT Claims Set to be
631631+ replicated as Header Parameters in a JWT that is a JWE, as needed by
632632+ the application. If such replicated claims are present, the
633633+ application receiving them SHOULD verify that their values are
634634+ identical, unless the application defines other specific processing
635635+ rules for these claims. It is the responsibility of the application
636636+ to ensure that only claims that are safe to be transmitted in an
637637+ unencrypted manner are replicated as Header Parameter values in the
638638+ JWT.
639639+640640+ Section 10.4.1 of this specification registers the "iss" (issuer),
641641+ "sub" (subject), and "aud" (audience) Header Parameter names for the
642642+ purpose of providing unencrypted replicas of these claims in
643643+ encrypted JWTs for applications that need them. Other specifications
644644+ MAY similarly register other names that are registered Claim Names as
645645+ Header Parameter names, as needed.
646646+647647+6. Unsecured JWTs
648648+649649+ To support use cases in which the JWT content is secured by a means
650650+ other than a signature and/or encryption contained within the JWT
651651+ (such as a signature on a data structure containing the JWT), JWTs
652652+ MAY also be created without a signature or encryption. An Unsecured
653653+ JWT is a JWS using the "alg" Header Parameter value "none" and with
654654+ the empty string for its JWS Signature value, as defined in the JWA
655655+ specification [JWA]; it is an Unsecured JWS with the JWT Claims Set
656656+ as its JWS Payload.
657657+658658+6.1. Example Unsecured JWT
659659+660660+ The following example JOSE Header declares that the encoded object is
661661+ an Unsecured JWT:
662662+663663+ {"alg":"none"}
664664+665665+ Base64url encoding the octets of the UTF-8 representation of the JOSE
666666+ Header yields this encoded JOSE Header value:
667667+668668+ eyJhbGciOiJub25lIn0
669669+670670+671671+672672+673673+674674+Jones, et al. Standards Track [Page 12]
675675+676676+RFC 7519 JSON Web Token (JWT) May 2015
677677+678678+679679+ The following is an example of a JWT Claims Set:
680680+681681+ {"iss":"joe",
682682+ "exp":1300819380,
683683+ "http://example.com/is_root":true}
684684+685685+ Base64url encoding the octets of the UTF-8 representation of the JWT
686686+ Claims Set yields this encoded JWS Payload (with line breaks for
687687+ display purposes only):
688688+689689+ eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
690690+ cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
691691+692692+ The encoded JWS Signature is the empty string.
693693+694694+ Concatenating these encoded parts in this order with period ('.')
695695+ characters between the parts yields this complete JWT (with line
696696+ breaks for display purposes only):
697697+698698+ eyJhbGciOiJub25lIn0
699699+ .
700700+ eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
701701+ cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
702702+ .
703703+704704+7. Creating and Validating JWTs
705705+706706+7.1. Creating a JWT
707707+708708+ To create a JWT, the following steps are performed. The order of the
709709+ steps is not significant in cases where there are no dependencies
710710+ between the inputs and outputs of the steps.
711711+712712+ 1. Create a JWT Claims Set containing the desired claims. Note that
713713+ whitespace is explicitly allowed in the representation and no
714714+ canonicalization need be performed before encoding.
715715+716716+ 2. Let the Message be the octets of the UTF-8 representation of the
717717+ JWT Claims Set.
718718+719719+ 3. Create a JOSE Header containing the desired set of Header
720720+ Parameters. The JWT MUST conform to either the [JWS] or [JWE]
721721+ specification. Note that whitespace is explicitly allowed in the
722722+ representation and no canonicalization need be performed before
723723+ encoding.
724724+725725+726726+727727+728728+729729+730730+Jones, et al. Standards Track [Page 13]
731731+732732+RFC 7519 JSON Web Token (JWT) May 2015
733733+734734+735735+ 4. Depending upon whether the JWT is a JWS or JWE, there are two
736736+ cases:
737737+738738+ * If the JWT is a JWS, create a JWS using the Message as the JWS
739739+ Payload; all steps specified in [JWS] for creating a JWS MUST
740740+ be followed.
741741+742742+ * Else, if the JWT is a JWE, create a JWE using the Message as
743743+ the plaintext for the JWE; all steps specified in [JWE] for
744744+ creating a JWE MUST be followed.
745745+746746+ 5. If a nested signing or encryption operation will be performed,
747747+ let the Message be the JWS or JWE, and return to Step 3, using a
748748+ "cty" (content type) value of "JWT" in the new JOSE Header
749749+ created in that step.
750750+751751+ 6. Otherwise, let the resulting JWT be the JWS or JWE.
752752+753753+7.2. Validating a JWT
754754+755755+ When validating a JWT, the following steps are performed. The order
756756+ of the steps is not significant in cases where there are no
757757+ dependencies between the inputs and outputs of the steps. If any of
758758+ the listed steps fail, then the JWT MUST be rejected -- that is,
759759+ treated by the application as an invalid input.
760760+761761+ 1. Verify that the JWT contains at least one period ('.')
762762+ character.
763763+764764+ 2. Let the Encoded JOSE Header be the portion of the JWT before the
765765+ first period ('.') character.
766766+767767+ 3. Base64url decode the Encoded JOSE Header following the
768768+ restriction that no line breaks, whitespace, or other additional
769769+ characters have been used.
770770+771771+ 4. Verify that the resulting octet sequence is a UTF-8-encoded
772772+ representation of a completely valid JSON object conforming to
773773+ RFC 7159 [RFC7159]; let the JOSE Header be this JSON object.
774774+775775+ 5. Verify that the resulting JOSE Header includes only parameters
776776+ and values whose syntax and semantics are both understood and
777777+ supported or that are specified as being ignored when not
778778+ understood.
779779+780780+ 6. Determine whether the JWT is a JWS or a JWE using any of the
781781+ methods described in Section 9 of [JWE].
782782+783783+784784+785785+786786+Jones, et al. Standards Track [Page 14]
787787+788788+RFC 7519 JSON Web Token (JWT) May 2015
789789+790790+791791+ 7. Depending upon whether the JWT is a JWS or JWE, there are two
792792+ cases:
793793+794794+ * If the JWT is a JWS, follow the steps specified in [JWS] for
795795+ validating a JWS. Let the Message be the result of base64url
796796+ decoding the JWS Payload.
797797+798798+ * Else, if the JWT is a JWE, follow the steps specified in
799799+ [JWE] for validating a JWE. Let the Message be the resulting
800800+ plaintext.
801801+802802+ 8. If the JOSE Header contains a "cty" (content type) value of
803803+ "JWT", then the Message is a JWT that was the subject of nested
804804+ signing or encryption operations. In this case, return to Step
805805+ 1, using the Message as the JWT.
806806+807807+ 9. Otherwise, base64url decode the Message following the
808808+ restriction that no line breaks, whitespace, or other additional
809809+ characters have been used.
810810+811811+ 10. Verify that the resulting octet sequence is a UTF-8-encoded
812812+ representation of a completely valid JSON object conforming to
813813+ RFC 7159 [RFC7159]; let the JWT Claims Set be this JSON object.
814814+815815+ Finally, note that it is an application decision which algorithms may
816816+ be used in a given context. Even if a JWT can be successfully
817817+ validated, unless the algorithms used in the JWT are acceptable to
818818+ the application, it SHOULD reject the JWT.
819819+820820+7.3. String Comparison Rules
821821+822822+ Processing a JWT inevitably requires comparing known strings to
823823+ members and values in JSON objects. For example, in checking what
824824+ the algorithm is, the Unicode [UNICODE] string encoding "alg" will be
825825+ checked against the member names in the JOSE Header to see if there
826826+ is a matching Header Parameter name.
827827+828828+ The JSON rules for doing member name comparison are described in
829829+ Section 8.3 of RFC 7159 [RFC7159]. Since the only string comparison
830830+ operations that are performed are equality and inequality, the same
831831+ rules can be used for comparing both member names and member values
832832+ against known strings.
833833+834834+ These comparison rules MUST be used for all JSON string comparisons
835835+ except in cases where the definition of the member explicitly calls
836836+ out that a different comparison rule is to be used for that member
837837+ value. In this specification, only the "typ" and "cty" member values
838838+ do not use these comparison rules.
839839+840840+841841+842842+Jones, et al. Standards Track [Page 15]
843843+844844+RFC 7519 JSON Web Token (JWT) May 2015
845845+846846+847847+ Some applications may include case-insensitive information in a case-
848848+ sensitive value, such as including a DNS name as part of the "iss"
849849+ (issuer) claim value. In those cases, the application may need to
850850+ define a convention for the canonical case to use for representing
851851+ the case-insensitive portions, such as lowercasing them, if more than
852852+ one party might need to produce the same value so that they can be
853853+ compared. (However, if all other parties consume whatever value the
854854+ producing party emitted verbatim without attempting to compare it to
855855+ an independently produced value, then the case used by the producer
856856+ will not matter.)
857857+858858+8. Implementation Requirements
859859+860860+ This section defines which algorithms and features of this
861861+ specification are mandatory to implement. Applications using this
862862+ specification can impose additional requirements upon implementations
863863+ that they use. For instance, one application might require support
864864+ for encrypted JWTs and Nested JWTs, while another might require
865865+ support for signing JWTs with the Elliptic Curve Digital Signature
866866+ Algorithm (ECDSA) using the P-256 curve and the SHA-256 hash
867867+ algorithm ("ES256").
868868+869869+ Of the signature and MAC algorithms specified in JSON Web Algorithms
870870+ [JWA], only HMAC SHA-256 ("HS256") and "none" MUST be implemented by
871871+ conforming JWT implementations. It is RECOMMENDED that
872872+ implementations also support RSASSA-PKCS1-v1_5 with the SHA-256 hash
873873+ algorithm ("RS256") and ECDSA using the P-256 curve and the SHA-256
874874+ hash algorithm ("ES256"). Support for other algorithms and key sizes
875875+ is OPTIONAL.
876876+877877+ Support for encrypted JWTs is OPTIONAL. If an implementation
878878+ provides encryption capabilities, of the encryption algorithms
879879+ specified in [JWA], only RSAES-PKCS1-v1_5 with 2048-bit keys
880880+ ("RSA1_5"), AES Key Wrap with 128- and 256-bit keys ("A128KW" and
881881+ "A256KW"), and the composite authenticated encryption algorithm using
882882+ AES-CBC and HMAC SHA-2 ("A128CBC-HS256" and "A256CBC-HS512") MUST be
883883+ implemented by conforming implementations. It is RECOMMENDED that
884884+ implementations also support using Elliptic Curve Diffie-Hellman
885885+ Ephemeral Static (ECDH-ES) to agree upon a key used to wrap the
886886+ Content Encryption Key ("ECDH-ES+A128KW" and "ECDH-ES+A256KW") and
887887+ AES in Galois/Counter Mode (GCM) with 128- and 256-bit keys
888888+ ("A128GCM" and "A256GCM"). Support for other algorithms and key
889889+ sizes is OPTIONAL.
890890+891891+ Support for Nested JWTs is OPTIONAL.
892892+893893+894894+895895+896896+897897+898898+Jones, et al. Standards Track [Page 16]
899899+900900+RFC 7519 JSON Web Token (JWT) May 2015
901901+902902+903903+9. URI for Declaring that Content is a JWT
904904+905905+ This specification registers the URN
906906+ "urn:ietf:params:oauth:token-type:jwt" for use by applications that
907907+ declare content types using URIs (rather than, for instance, media
908908+ types) to indicate that the content referred to is a JWT.
909909+910910+10. IANA Considerations
911911+912912+10.1. JSON Web Token Claims Registry
913913+914914+ This section establishes the IANA "JSON Web Token Claims" registry
915915+ for JWT Claim Names. The registry records the Claim Name and a
916916+ reference to the specification that defines it. This section
917917+ registers the Claim Names defined in Section 4.1.
918918+919919+ Values are registered on a Specification Required [RFC5226] basis
920920+ after a three-week review period on the jwt-reg-review@ietf.org
921921+ mailing list, on the advice of one or more Designated Experts.
922922+ However, to allow for the allocation of values prior to publication,
923923+ the Designated Experts may approve registration once they are
924924+ satisfied that such a specification will be published.
925925+926926+ Registration requests sent to the mailing list for review should use
927927+ an appropriate subject (e.g., "Request to register claim: example").
928928+929929+ Within the review period, the Designated Experts will either approve
930930+ or deny the registration request, communicating this decision to the
931931+ review list and IANA. Denials should include an explanation and, if
932932+ applicable, suggestions as to how to make the request successful.
933933+ Registration requests that are undetermined for a period longer than
934934+ 21 days can be brought to the IESG's attention (using the
935935+ iesg@ietf.org mailing list) for resolution.
936936+937937+ Criteria that should be applied by the Designated Experts includes
938938+ determining whether the proposed registration duplicates existing
939939+ functionality, whether it is likely to be of general applicability or
940940+ whether it is useful only for a single application, and whether the
941941+ registration description is clear.
942942+943943+ IANA must only accept registry updates from the Designated Experts
944944+ and should direct all requests for registration to the review mailing
945945+ list.
946946+947947+ It is suggested that multiple Designated Experts be appointed who are
948948+ able to represent the perspectives of different applications using
949949+ this specification, in order to enable broadly informed review of
950950+ registration decisions. In cases where a registration decision could
951951+952952+953953+954954+Jones, et al. Standards Track [Page 17]
955955+956956+RFC 7519 JSON Web Token (JWT) May 2015
957957+958958+959959+ be perceived as creating a conflict of interest for a particular
960960+ Expert, that Expert should defer to the judgment of the other
961961+ Experts.
962962+963963+10.1.1. Registration Template
964964+965965+ Claim Name:
966966+ The name requested (e.g., "iss"). Because a core goal of this
967967+ specification is for the resulting representations to be compact,
968968+ it is RECOMMENDED that the name be short -- that is, not to exceed
969969+ 8 characters without a compelling reason to do so. This name is
970970+ case sensitive. Names may not match other registered names in a
971971+ case-insensitive manner unless the Designated Experts state that
972972+ there is a compelling reason to allow an exception.
973973+974974+ Claim Description:
975975+ Brief description of the claim (e.g., "Issuer").
976976+977977+ Change Controller:
978978+ For Standards Track RFCs, list the "IESG". For others, give the
979979+ name of the responsible party. Other details (e.g., postal
980980+ address, email address, home page URI) may also be included.
981981+982982+ Specification Document(s):
983983+ Reference to the document or documents that specify the parameter,
984984+ preferably including URIs that can be used to retrieve copies of
985985+ the documents. An indication of the relevant sections may also be
986986+ included but is not required.
987987+988988+10.1.2. Initial Registry Contents
989989+990990+ o Claim Name: "iss"
991991+ o Claim Description: Issuer
992992+ o Change Controller: IESG
993993+ o Specification Document(s): Section 4.1.1 of RFC 7519
994994+995995+ o Claim Name: "sub"
996996+ o Claim Description: Subject
997997+ o Change Controller: IESG
998998+ o Specification Document(s): Section 4.1.2 of RFC 7519
999999+10001000+ o Claim Name: "aud"
10011001+ o Claim Description: Audience
10021002+ o Change Controller: IESG
10031003+ o Specification Document(s): Section 4.1.3 of RFC 7519
10041004+10051005+10061006+10071007+10081008+10091009+10101010+Jones, et al. Standards Track [Page 18]
10111011+10121012+RFC 7519 JSON Web Token (JWT) May 2015
10131013+10141014+10151015+ o Claim Name: "exp"
10161016+ o Claim Description: Expiration Time
10171017+ o Change Controller: IESG
10181018+ o Specification Document(s): Section 4.1.4 of RFC 7519
10191019+10201020+ o Claim Name: "nbf"
10211021+ o Claim Description: Not Before
10221022+ o Change Controller: IESG
10231023+ o Specification Document(s): Section 4.1.5 of RFC 7519
10241024+10251025+ o Claim Name: "iat"
10261026+ o Claim Description: Issued At
10271027+ o Change Controller: IESG
10281028+ o Specification Document(s): Section 4.1.6 of RFC 7519
10291029+10301030+ o Claim Name: "jti"
10311031+ o Claim Description: JWT ID
10321032+ o Change Controller: IESG
10331033+ o Specification Document(s): Section 4.1.7 of RFC 7519
10341034+10351035+10.2. Sub-Namespace Registration of
10361036+ urn:ietf:params:oauth:token-type:jwt
10371037+10381038+10.2.1. Registry Contents
10391039+10401040+ This section registers the value "token-type:jwt" in the IANA "OAuth
10411041+ URI" registry established by "An IETF URN Sub-Namespace for OAuth"
10421042+ [RFC6755], which can be used to indicate that the content is a JWT.
10431043+10441044+ o URN: urn:ietf:params:oauth:token-type:jwt
10451045+ o Common Name: JSON Web Token (JWT) Token Type
10461046+ o Change Controller: IESG
10471047+ o Specification Document(s): RFC 7519
10481048+10491049+10501050+10511051+10521052+10531053+10541054+10551055+10561056+10571057+10581058+10591059+10601060+10611061+10621062+10631063+10641064+10651065+10661066+Jones, et al. Standards Track [Page 19]
10671067+10681068+RFC 7519 JSON Web Token (JWT) May 2015
10691069+10701070+10711071+10.3. Media Type Registration
10721072+10731073+10.3.1. Registry Contents
10741074+10751075+ This section registers the "application/jwt" media type [RFC2046] in
10761076+ the "Media Types" registry [IANA.MediaTypes] in the manner described
10771077+ in RFC 6838 [RFC6838], which can be used to indicate that the content
10781078+ is a JWT.
10791079+10801080+ o Type name: application
10811081+ o Subtype name: jwt
10821082+ o Required parameters: n/a
10831083+ o Optional parameters: n/a
10841084+ o Encoding considerations: 8bit; JWT values are encoded as a series
10851085+ of base64url-encoded values (some of which may be the empty
10861086+ string) separated by period ('.') characters.
10871087+ o Security considerations: See the Security Considerations section
10881088+ of RFC 7519
10891089+ o Interoperability considerations: n/a
10901090+ o Published specification: RFC 7519
10911091+ o Applications that use this media type: OpenID Connect, Mozilla
10921092+ Persona, Salesforce, Google, Android, Windows Azure, Amazon Web
10931093+ Services, and numerous others
10941094+ o Fragment identifier considerations: n/a
10951095+ o Additional information:
10961096+10971097+ Magic number(s): n/a
10981098+ File extension(s): n/a
10991099+ Macintosh file type code(s): n/a
11001100+11011101+ o Person & email address to contact for further information:
11021102+ Michael B. Jones, mbj@microsoft.com
11031103+ o Intended usage: COMMON
11041104+ o Restrictions on usage: none
11051105+ o Author: Michael B. Jones, mbj@microsoft.com
11061106+ o Change controller: IESG
11071107+ o Provisional registration? No
11081108+11091109+10.4. Header Parameter Names Registration
11101110+11111111+ This section registers specific Claim Names defined in Section 4.1 in
11121112+ the IANA "JSON Web Signature and Encryption Header Parameters"
11131113+ registry established by [JWS] for use by claims replicated as Header
11141114+ Parameters in JWEs, per Section 5.3.
11151115+11161116+11171117+11181118+11191119+11201120+11211121+11221122+Jones, et al. Standards Track [Page 20]
11231123+11241124+RFC 7519 JSON Web Token (JWT) May 2015
11251125+11261126+11271127+10.4.1. Registry Contents
11281128+11291129+ o Header Parameter Name: "iss"
11301130+ o Header Parameter Description: Issuer
11311131+ o Header Parameter Usage Location(s): JWE
11321132+ o Change Controller: IESG
11331133+ o Specification Document(s): Section 4.1.1 of RFC 7519
11341134+11351135+ o Header Parameter Name: "sub"
11361136+ o Header Parameter Description: Subject
11371137+ o Header Parameter Usage Location(s): JWE
11381138+ o Change Controller: IESG
11391139+ o Specification Document(s): Section 4.1.2 of RFC 7519
11401140+11411141+ o Header Parameter Name: "aud"
11421142+ o Header Parameter Description: Audience
11431143+ o Header Parameter Usage Location(s): JWE
11441144+ o Change Controller: IESG
11451145+ o Specification Document(s): Section 4.1.3 of RFC 7519
11461146+11471147+11. Security Considerations
11481148+11491149+ All of the security issues that are pertinent to any cryptographic
11501150+ application must be addressed by JWT/JWS/JWE/JWK agents. Among these
11511151+ issues are protecting the user's asymmetric private and symmetric
11521152+ secret keys and employing countermeasures to various attacks.
11531153+11541154+ All the security considerations in the JWS specification also apply
11551155+ to JWT, as do the JWE security considerations when encryption is
11561156+ employed. In particular, Sections 10.12 ("JSON Security
11571157+ Considerations") and 10.13 ("Unicode Comparison Security
11581158+ Considerations") of [JWS] apply equally to the JWT Claims Set in the
11591159+ same manner that they do to the JOSE Header.
11601160+11611161+11.1. Trust Decisions
11621162+11631163+ The contents of a JWT cannot be relied upon in a trust decision
11641164+ unless its contents have been cryptographically secured and bound to
11651165+ the context necessary for the trust decision. In particular, the
11661166+ key(s) used to sign and/or encrypt the JWT will typically need to
11671167+ verifiably be under the control of the party identified as the issuer
11681168+ of the JWT.
11691169+11701170+11.2. Signing and Encryption Order
11711171+11721172+ While syntactically the signing and encryption operations for Nested
11731173+ JWTs may be applied in any order, if both signing and encryption are
11741174+ necessary, normally producers should sign the message and then
11751175+11761176+11771177+11781178+Jones, et al. Standards Track [Page 21]
11791179+11801180+RFC 7519 JSON Web Token (JWT) May 2015
11811181+11821182+11831183+ encrypt the result (thus encrypting the signature). This prevents
11841184+ attacks in which the signature is stripped, leaving just an encrypted
11851185+ message, as well as providing privacy for the signer. Furthermore,
11861186+ signatures over encrypted text are not considered valid in many
11871187+ jurisdictions.
11881188+11891189+ Note that potential concerns about security issues related to the
11901190+ order of signing and encryption operations are already addressed by
11911191+ the underlying JWS and JWE specifications; in particular, because JWE
11921192+ only supports the use of authenticated encryption algorithms,
11931193+ cryptographic concerns about the potential need to sign after
11941194+ encryption that apply in many contexts do not apply to this
11951195+ specification.
11961196+11971197+12. Privacy Considerations
11981198+11991199+ A JWT may contain privacy-sensitive information. When this is the
12001200+ case, measures MUST be taken to prevent disclosure of this
12011201+ information to unintended parties. One way to achieve this is to use
12021202+ an encrypted JWT and authenticate the recipient. Another way is to
12031203+ ensure that JWTs containing unencrypted privacy-sensitive information
12041204+ are only transmitted using protocols utilizing encryption that
12051205+ support endpoint authentication, such as Transport Layer Security
12061206+ (TLS). Omitting privacy-sensitive information from a JWT is the
12071207+ simplest way of minimizing privacy issues.
12081208+12091209+13. References
12101210+12111211+13.1. Normative References
12121212+12131213+ [ECMAScript]
12141214+ Ecma International, "ECMAScript Language Specification,
12151215+ 5.1 Edition", ECMA Standard 262, June 2011,
12161216+ <http://www.ecma-international.org/ecma-262/5.1/
12171217+ ECMA-262.pdf>.
12181218+12191219+ [IANA.MediaTypes]
12201220+ IANA, "Media Types",
12211221+ <http://www.iana.org/assignments/media-types>.
12221222+12231223+ [JWA] Jones, M., "JSON Web Algorithms (JWA)", RFC 7518,
12241224+ DOI 10.17487/RFC7518, May 2015,
12251225+ <http://www.rfc-editor.org/info/rfc7518>.
12261226+12271227+ [JWE] Jones, M. and J. Hildebrand, "JSON Web Encryption (JWE)",
12281228+ RFC 7516, DOI 10.17487/RFC7516, May 2015,
12291229+ <http://www.rfc-editor.org/info/rfc7516>.
12301230+12311231+12321232+12331233+12341234+Jones, et al. Standards Track [Page 22]
12351235+12361236+RFC 7519 JSON Web Token (JWT) May 2015
12371237+12381238+12391239+ [JWS] Jones, M., Bradley, J., and N. Sakimura, "JSON Web
12401240+ Signature (JWS)", RFC 7515, DOI 10.17487/RFC, May 2015,
12411241+ <http://www.rfc-editor.org/info/rfc7515>.
12421242+12431243+ [RFC20] Cerf, V., "ASCII format for Network Interchange", STD 80,
12441244+ RFC 20, DOI 10.17487/RFC0020, October 1969,
12451245+ <http://www.rfc-editor.org/info/rfc20>.
12461246+12471247+ [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail
12481248+ Extensions (MIME) Part Two: Media Types", RFC 2046,
12491249+ DOI 10.17487/RFC2046, November 1996,
12501250+ <http://www.rfc-editor.org/info/rfc2046>.
12511251+12521252+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
12531253+ Requirement Levels", BCP 14, RFC 2119,
12541254+ DOI 10.17487/RFC2119, March 1997,
12551255+ <http://www.rfc-editor.org/info/rfc2119>.
12561256+12571257+ [RFC3986] Berners-Lee, T., Fielding, R., and L. Masinter, "Uniform
12581258+ Resource Identifier (URI): Generic Syntax", STD 66,
12591259+ RFC 3986, DOI 10.17487/RFC3986, January 2005,
12601260+ <http://www.rfc-editor.org/info/rfc3986>.
12611261+12621262+ [RFC4949] Shirey, R., "Internet Security Glossary, Version 2",
12631263+ FYI 36, RFC 4949, DOI 10.17487/RFC4949, August 2007,
12641264+ <http://www.rfc-editor.org/info/rfc4949>.
12651265+12661266+ [RFC7159] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
12671267+ Interchange Format", RFC 7159, DOI 10.17487/RFC7159, March
12681268+ 2014, <http://www.rfc-editor.org/info/rfc7159>.
12691269+12701270+ [UNICODE] The Unicode Consortium, "The Unicode Standard",
12711271+ <http://www.unicode.org/versions/latest/>.
12721272+12731273+13.2. Informative References
12741274+12751275+ [CanvasApp]
12761276+ Facebook, "Canvas Applications", 2010,
12771277+ <http://developers.facebook.com/docs/authentication/
12781278+ canvas>.
12791279+12801280+ [JSS] Bradley, J. and N. Sakimura (editor), "JSON Simple Sign",
12811281+ September 2010, <http://jsonenc.info/jss/1.0/>.
12821282+12831283+12841284+12851285+12861286+12871287+12881288+12891289+12901290+Jones, et al. Standards Track [Page 23]
12911291+12921292+RFC 7519 JSON Web Token (JWT) May 2015
12931293+12941294+12951295+ [MagicSignatures]
12961296+ Panzer, J., Ed., Laurie, B., and D. Balfanz, "Magic
12971297+ Signatures", January 2011,
12981298+ <http://salmon-protocol.googlecode.com/svn/
12991299+ trunk/draft-panzer-magicsig-01.html>.
13001300+13011301+ [OASIS.saml-core-2.0-os]
13021302+ Cantor, S., Kemp, J., Philpott, R., and E. Maler,
13031303+ "Assertions and Protocols for the OASIS Security Assertion
13041304+ Markup Language (SAML) V2.0", OASIS Standard
13051305+ saml-core-2.0-os, March 2005,
13061306+ <http://docs.oasis-open.org/security/saml/v2.0/
13071307+ saml-core-2.0-os.pdf>.
13081308+13091309+ [POSIX.1] IEEE, "The Open Group Base Specifications Issue 7", IEEE
13101310+ Std 1003.1, 2013 Edition, 2013,
13111311+ <http://pubs.opengroup.org/onlinepubs/9699919799/
13121312+ basedefs/V1_chap04.html#tag_04_15>.
13131313+13141314+ [RFC3275] Eastlake 3rd, D., Reagle, J., and D. Solo, "(Extensible
13151315+ Markup Language) XML-Signature Syntax and Processing",
13161316+ RFC 3275, DOI 10.17487/RFC3275, March 2002,
13171317+ <http://www.rfc-editor.org/info/rfc3275>.
13181318+13191319+ [RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
13201320+ Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
13211321+ <http://www.rfc-editor.org/info/rfc3339>.
13221322+13231323+ [RFC4122] Leach, P., Mealling, M., and R. Salz, "A Universally
13241324+ Unique IDentifier (UUID) URN Namespace", RFC 4122,
13251325+ DOI 10.17487/RFC4122, July 2005,
13261326+ <http://www.rfc-editor.org/info/rfc4122>.
13271327+13281328+ [RFC5226] Narten, T. and H. Alvestrand, "Guidelines for Writing an
13291329+ IANA Considerations Section in RFCs", BCP 26, RFC 5226,
13301330+ DOI 10.17487/RFC5226, May 2008,
13311331+ <http://www.rfc-editor.org/info/rfc5226>.
13321332+13331333+ [RFC6755] Campbell, B. and H. Tschofenig, "An IETF URN Sub-Namespace
13341334+ for OAuth", RFC 6755, DOI 10.17487/RFC6755, October 2012,
13351335+ <http://www.rfc-editor.org/info/rfc6755>.
13361336+13371337+ [RFC6838] Freed, N., Klensin, J., and T. Hansen, "Media Type
13381338+ Specifications and Registration Procedures", BCP 13,
13391339+ RFC 6838, DOI 10.17487/RFC6838, January 2013,
13401340+ <http://www.rfc-editor.org/info/rfc6838>.
13411341+13421342+13431343+13441344+13451345+13461346+Jones, et al. Standards Track [Page 24]
13471347+13481348+RFC 7519 JSON Web Token (JWT) May 2015
13491349+13501350+13511351+ [SWT] Hardt, D. and Y. Goland, "Simple Web Token (SWT)", Version
13521352+ 0.9.5.1, November 2009, <http://msdn.microsoft.com/en-us/
13531353+ library/windowsazure/hh781551.aspx>.
13541354+13551355+ [W3C.CR-xml11-20060816]
13561356+ Cowan, J., "Extensible Markup Language (XML) 1.1 (Second
13571357+ Edition)", World Wide Web Consortium Recommendation
13581358+ REC-xml11-20060816, August 2006,
13591359+ <http://www.w3.org/TR/2006/REC-xml11-20060816>.
13601360+13611361+ [W3C.REC-xml-c14n-20010315]
13621362+ Boyer, J., "Canonical XML Version 1.0", World Wide Web
13631363+ Consortium Recommendation REC-xml-c14n-20010315, March
13641364+ 2001, <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>.
13651365+13661366+13671367+13681368+13691369+13701370+13711371+13721372+13731373+13741374+13751375+13761376+13771377+13781378+13791379+13801380+13811381+13821382+13831383+13841384+13851385+13861386+13871387+13881388+13891389+13901390+13911391+13921392+13931393+13941394+13951395+13961396+13971397+13981398+13991399+14001400+14011401+14021402+Jones, et al. Standards Track [Page 25]
14031403+14041404+RFC 7519 JSON Web Token (JWT) May 2015
14051405+14061406+14071407+Appendix A. JWT Examples
14081408+14091409+ This section contains examples of JWTs. For other example JWTs, see
14101410+ Section 6.1 of this document and Appendices A.1 - A.3 of [JWS].
14111411+14121412+A.1. Example Encrypted JWT
14131413+14141414+ This example encrypts the same claims as used in Section 3.1 to the
14151415+ recipient using RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256.
14161416+14171417+ The following example JOSE Header declares that:
14181418+14191419+ o The Content Encryption Key is encrypted to the recipient using the
14201420+ RSAES-PKCS1-v1_5 algorithm to produce the JWE Encrypted Key.
14211421+ o Authenticated encryption is performed on the plaintext using the
14221422+ AES_128_CBC_HMAC_SHA_256 algorithm to produce the JWE Ciphertext
14231423+ and the JWE Authentication Tag.
14241424+14251425+ {"alg":"RSA1_5","enc":"A128CBC-HS256"}
14261426+14271427+ Other than using the octets of the UTF-8 representation of the JWT
14281428+ Claims Set from Section 3.1 as the plaintext value, the computation
14291429+ of this JWT is identical to the computation of the JWE in
14301430+ Appendix A.2 of [JWE], including the keys used.
14311431+14321432+ The final result in this example (with line breaks for display
14331433+ purposes only) is:
14341434+14351435+ eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.
14361436+ QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtM
14371437+ oNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLG
14381438+ TkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26ima
14391439+ sOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYMmpUDkR-Cx9obNGwJQ3nM52
14401440+ YCitxoQVPzjbl7WBuB7AohdBoZOdZ24WlN1lVIeh8v1K4krB8xgKvRU8kgFrEn_a
14411441+ 1rZgN5TiysnmzTROF869lQ.
14421442+ AxY8DCtDaGlsbGljb3RoZQ.
14431443+ MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaM
14441444+ HDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8.
14451445+ fiK51VwhsxJ-siBMR-YFiA
14461446+14471447+A.2. Example Nested JWT
14481448+14491449+ This example shows how a JWT can be used as the payload of a JWE or
14501450+ JWS to create a Nested JWT. In this case, the JWT Claims Set is
14511451+ first signed, and then encrypted.
14521452+14531453+14541454+14551455+14561456+14571457+14581458+Jones, et al. Standards Track [Page 26]
14591459+14601460+RFC 7519 JSON Web Token (JWT) May 2015
14611461+14621462+14631463+ The inner signed JWT is identical to the example in Appendix A.2 of
14641464+ [JWS]. Therefore, its computation is not repeated here. This
14651465+ example then encrypts this inner JWT to the recipient using
14661466+ RSAES-PKCS1-v1_5 and AES_128_CBC_HMAC_SHA_256.
14671467+14681468+ The following example JOSE Header declares that:
14691469+14701470+ o The Content Encryption Key is encrypted to the recipient using the
14711471+ RSAES-PKCS1-v1_5 algorithm to produce the JWE Encrypted Key.
14721472+ o Authenticated encryption is performed on the plaintext using the
14731473+ AES_128_CBC_HMAC_SHA_256 algorithm to produce the JWE Ciphertext
14741474+ and the JWE Authentication Tag.
14751475+ o The plaintext is itself a JWT.
14761476+14771477+ {"alg":"RSA1_5","enc":"A128CBC-HS256","cty":"JWT"}
14781478+14791479+ Base64url encoding the octets of the UTF-8 representation of the JOSE
14801480+ Header yields this encoded JOSE Header value:
14811481+14821482+ eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldUIn0
14831483+14841484+ The computation of this JWT is identical to the computation of the
14851485+ JWE in Appendix A.2 of [JWE], other than that different JOSE Header,
14861486+ plaintext, JWE Initialization Vector, and Content Encryption Key
14871487+ values are used. (The RSA key used is the same.)
14881488+14891489+ The plaintext used is the octets of the ASCII [RFC20] representation
14901490+ of the JWT at the end of Appendix A.2.1 of [JWS] (with all whitespace
14911491+ and line breaks removed), which is a sequence of 458 octets.
14921492+14931493+ The JWE Initialization Vector value used (using JSON array notation)
14941494+ is:
14951495+14961496+ [82, 101, 100, 109, 111, 110, 100, 32, 87, 65, 32, 57, 56, 48, 53,
14971497+ 50]
14981498+14991499+ This example uses the Content Encryption Key represented by the
15001500+ base64url-encoded value below:
15011501+15021502+ GawgguFyGrWKav7AX4VKUg
15031503+15041504+15051505+15061506+15071507+15081508+15091509+15101510+15111511+15121512+15131513+15141514+Jones, et al. Standards Track [Page 27]
15151515+15161516+RFC 7519 JSON Web Token (JWT) May 2015
15171517+15181518+15191519+ The final result for this Nested JWT (with line breaks for display
15201520+ purposes only) is:
15211521+15221522+ eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldU
15231523+ In0.
15241524+ g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_M
15251525+ qewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYE
15261526+ b9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvh
15271527+ DuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4VZGGlxRGPLRHvolVLEHx6D
15281528+ YyLpw30Ay9R6d68YCLi9FYTq3hIXPK_-dmPlOUlKvPr1GgJzRoeC9G5qCvdcHWsq
15291529+ JGTO_z3Wfo5zsqwkxruxwA.
15301530+ UmVkbW9uZCBXQSA5ODA1Mg.
15311531+ VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTB
15321532+ BLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT
15331533+ -FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10
15341534+ l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6ULmkV-uLC4FUiyKECK4e3WZY
15351535+ Kw1bpgIqGYsw2v_grHjszJZ-_I5uM-9RA8ycX9KqPRp9gc6pXmoU_-27ATs9XCvr
15361536+ ZXUtK2902AUzqpeEUJYjWWxSNsS-r1TJ1I-FMJ4XyAiGrfmo9hQPcNBYxPz3GQb2
15371537+ 8Y5CLSQfNgKSGt0A4isp1hBUXBHAndgtcslt7ZoQJaKe_nNJgNliWtWpJ_ebuOpE
15381538+ l8jdhehdccnRMIwAmU1n7SPkmhIl1HlSOpvcvDfhUN5wuqU955vOBvfkBOh5A11U
15391539+ zBuo2WlgZ6hYi9-e3w29bR0C2-pp3jbqxEDw3iWaf2dc5b-LnR0FEYXvI_tYk5rd
15401540+ _J9N0mg0tQ6RbpxNEMNoA9QWk5lgdPvbh9BaO195abQ.
15411541+ AVO9iT5AV4CzvDJCdhSFlQ
15421542+15431543+Appendix B. Relationship of JWTs to SAML Assertions
15441544+15451545+ Security Assertion Markup Language (SAML) 2.0
15461546+ [OASIS.saml-core-2.0-os] provides a standard for creating security
15471547+ tokens with greater expressivity and more security options than
15481548+ supported by JWTs. However, the cost of this flexibility and
15491549+ expressiveness is both size and complexity. SAML's use of XML
15501550+ [W3C.CR-xml11-20060816] and XML Digital Signature (DSIG) [RFC3275]
15511551+ contributes to the size of SAML Assertions; its use of XML and
15521552+ especially XML Canonicalization [W3C.REC-xml-c14n-20010315]
15531553+ contributes to their complexity.
15541554+15551555+ JWTs are intended to provide a simple security token format that is
15561556+ small enough to fit into HTTP headers and query arguments in URIs.
15571557+ It does this by supporting a much simpler token model than SAML and
15581558+ using the JSON [RFC7159] object encoding syntax. It also supports
15591559+ securing tokens using Message Authentication Codes (MACs) and digital
15601560+ signatures using a smaller (and less flexible) format than XML DSIG.
15611561+15621562+ Therefore, while JWTs can do some of the things SAML Assertions do,
15631563+ JWTs are not intended as a full replacement for SAML Assertions, but
15641564+ rather as a token format to be used when ease of implementation or
15651565+ compactness are considerations.
15661566+15671567+15681568+15691569+15701570+Jones, et al. Standards Track [Page 28]
15711571+15721572+RFC 7519 JSON Web Token (JWT) May 2015
15731573+15741574+15751575+ SAML Assertions are always statements made by an entity about a
15761576+ subject. JWTs are often used in the same manner, with the entity
15771577+ making the statements being represented by the "iss" (issuer) claim,
15781578+ and the subject being represented by the "sub" (subject) claim.
15791579+ However, with these claims being optional, other uses of the JWT
15801580+ format are also permitted.
15811581+15821582+Appendix C. Relationship of JWTs to Simple Web Tokens (SWTs)
15831583+15841584+ Both JWTs and SWTs [SWT], at their core, enable sets of claims to be
15851585+ communicated between applications. For SWTs, both the claim names
15861586+ and claim values are strings. For JWTs, while claim names are
15871587+ strings, claim values can be any JSON type. Both token types offer
15881588+ cryptographic protection of their content: SWTs with HMAC SHA-256 and
15891589+ JWTs with a choice of algorithms, including signature, MAC, and
15901590+ encryption algorithms.
15911591+15921592+Acknowledgements
15931593+15941594+ The authors acknowledge that the design of JWTs was intentionally
15951595+ influenced by the design and simplicity of SWTs [SWT] and ideas for
15961596+ JSON tokens that Dick Hardt discussed within the OpenID community.
15971597+15981598+ Solutions for signing JSON content were previously explored by Magic
15991599+ Signatures [MagicSignatures], JSON Simple Sign [JSS], and Canvas
16001600+ Applications [CanvasApp], all of which influenced this document.
16011601+16021602+ This specification is the work of the OAuth working group, which
16031603+ includes dozens of active and dedicated participants. In particular,
16041604+ the following individuals contributed ideas, feedback, and wording
16051605+ that influenced this specification:
16061606+16071607+ Dirk Balfanz, Richard Barnes, Brian Campbell, Alissa Cooper, Breno de
16081608+ Medeiros, Stephen Farrell, Yaron Y. Goland, Dick Hardt, Joe
16091609+ Hildebrand, Jeff Hodges, Edmund Jay, Warren Kumari, Ben Laurie, Barry
16101610+ Leiba, Ted Lemon, James Manger, Prateek Mishra, Kathleen Moriarty,
16111611+ Tony Nadalin, Axel Nennker, John Panzer, Emmanuel Raviart, David
16121612+ Recordon, Eric Rescorla, Jim Schaad, Paul Tarjan, Hannes Tschofenig,
16131613+ Sean Turner, and Tom Yu.
16141614+16151615+ Hannes Tschofenig and Derek Atkins chaired the OAuth working group
16161616+ and Sean Turner, Stephen Farrell, and Kathleen Moriarty served as
16171617+ Security Area Directors during the creation of this specification.
16181618+16191619+16201620+16211621+16221622+16231623+16241624+16251625+16261626+Jones, et al. Standards Track [Page 29]
16271627+16281628+RFC 7519 JSON Web Token (JWT) May 2015
16291629+16301630+16311631+Authors' Addresses
16321632+16331633+ Michael B. Jones
16341634+ Microsoft
16351635+16361636+ EMail: mbj@microsoft.com
16371637+ URI: http://self-issued.info/
16381638+16391639+16401640+ John Bradley
16411641+ Ping Identity
16421642+16431643+ EMail: ve7jtb@ve7jtb.com
16441644+ URI: http://www.thread-safe.com/
16451645+16461646+16471647+ Nat Sakimura
16481648+ Nomura Research Institute
16491649+16501650+ EMail: n-sakimura@nri.co.jp
16511651+ URI: http://nat.sakimura.org/
16521652+16531653+16541654+16551655+16561656+16571657+16581658+16591659+16601660+16611661+16621662+16631663+16641664+16651665+16661666+16671667+16681668+16691669+16701670+16711671+16721672+16731673+16741674+16751675+16761676+16771677+16781678+16791679+16801680+16811681+16821682+Jones, et al. Standards Track [Page 30]
16831683+