1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
open Utils
type payload = Yojson.Safe.t
type claim = string * Yojson.Safe.t
type error =
[ `Msg of string
| `Expired
| `Json_parse_failed of string
| `Unsupported_kty
| `Invalid_signature ]
let empty_payload = `Assoc []
let payload_to_string payload =
let serialized_payload = Yojson.Safe.to_string payload in
U_Base64.url_encode_string serialized_payload
let payload_of_string payload_str =
let payload = U_Base64.url_decode payload_str in
Result.map Yojson.Safe.from_string payload
type t = {
header : Header.t;
raw_header : string;
payload : payload;
raw_payload : string;
signature : Jws.signature;
}
let add_claim (claim_name : string) (claim_value : Yojson.Safe.t)
(payload : payload) =
`Assoc ((claim_name, claim_value) :: Yojson.Safe.Util.to_assoc payload)
let get_yojson_claim (jwt : t) (claim_name : string) =
Yojson.Safe.Util.member claim_name jwt.payload |> Option.some
let get_string_claim (jwt : t) (claim_name : string) =
Option.bind
(get_yojson_claim jwt claim_name)
Yojson.Safe.Util.to_string_option
let get_int_claim (jwt : t) (claim_name : string) =
Option.bind (get_yojson_claim jwt claim_name) Yojson.Safe.Util.to_int_option
let to_jws (t : t) =
Jws.
{
header = t.header;
raw_header = t.raw_header;
signature = t.signature;
payload = t.raw_payload;
}
let of_jws (jws : Jws.t) =
let payload = jws.payload |> Yojson.Safe.from_string in
{
header = jws.header;
raw_header = jws.raw_header;
signature = jws.signature;
payload;
raw_payload = jws.payload;
}
let to_string ?serialization t =
let jws = to_jws t in
Jws.to_string ?serialization jws
let unsafe_of_string token = Jws.of_string token |> Result.map of_jws
let check_expiration ~(now : Ptime.t) t =
let module Json = Yojson.Safe.Util in
match
Json.member "exp" t.payload |> Json.to_int_option |> Option.map float_of_int
with
| Some exp ->
let pexp = Ptime.of_float_s exp in
let is_earlier =
Option.map (fun pexp -> Ptime.is_earlier now ~than:pexp) pexp
in
if is_earlier = Some true then Ok t else Error `Expired
| None -> Ok t
let validate_signature (type a) ~(jwk : a Jwk.t) (t : t) : (t, 'error) result =
Jws.validate ~jwk (to_jws t) |> Result.map of_jws
let validate (type a) ~(jwk : a Jwk.t) ~now (t : t) : (t, 'error) result =
match validate_signature ~jwk t with
| Ok t -> check_expiration t ~now
| Error e -> Error e
let of_string ~jwk ~now s =
Result.bind (unsafe_of_string s) (validate ~jwk ~now)
let sign ? ~payload (jwk : Jwk.priv Jwk.t) =
let =
match header with
| Some -> header
| None -> Header.make_header ~typ:"JWT" jwk
in
let payload =
try Ok (Yojson.Safe.to_string payload)
with _ -> Error (`Msg "Can't serialize payload")
in
match payload with
| Ok payload -> Jws.sign ~header ~payload jwk |> Result.map of_jws
| Error e -> Error e