Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
TokenResponse.ml1 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 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139type token_type = Bearer type t = { token_type : token_type ; scope : Scopes.t list ; expires_in : int option ; access_token : string option ; refresh_token : string option ; id_token : string option } let make ?(token_type = Bearer) ?(scope = []) ?expires_in ?access_token ?refresh_token ?id_token () = { token_type; scope; expires_in; access_token; refresh_token; id_token } (* Microsoft returns ints as strings... *) let string_or_int_to_int_opt = function | `String s -> (try Some (int_of_string s) with _ -> None) | `Int i -> Some i | `Null -> None | _ -> None (* TODO: Should we log or throw? *) let of_yojson json = try let module Json = Yojson.Safe.Util in let scope = match Json.member "scope" json with | `Null -> [] | `String scope -> Scopes.of_scope_parameter scope | `List json -> (* Some OIDC providers (Twitch for example) return an array of strings for scope. *) List.map Json.to_string json |> List.map Scopes.of_string | json -> raise (Json.Type_error ("scope: expected a string or an array of strings", json)) in Ok { token_type = Bearer ; (* Only Bearer is supported by OIDC, TODO = return a error if it is not Bearer *) scope ; expires_in = json |> Json.member "expires_in" |> string_or_int_to_int_opt ; access_token = json |> Json.member "access_token" |> Json.to_string_option ; refresh_token = json |> Json.member "refresh_token" |> Json.to_string_option ; id_token = json |> Json.member "id_token" |> Json.to_string_option } with | Yojson.Safe.Util.Type_error (str, _) -> Error (`Msg str) let of_query query = let scope = let qp = Uri.get_query_param query "scope" in Option.value ~default:[] (Option.map Scopes.of_scope_parameter qp) in { token_type = Bearer ; (* Only Bearer is supported by OIDC, TODO = return a error if it is not Bearer *) scope ; expires_in = Uri.get_query_param query "expires_in" |> Option.map int_of_string ; access_token = Uri.get_query_param query "access_token" ; refresh_token = Uri.get_query_param query "refresh_token" ; id_token = Uri.get_query_param query "id_token" } let of_string str = Yojson.Safe.from_string str |> of_yojson let validate ?clock_tolerance ?nonce ~jwks ~(client : Client.t) ~(discovery : Discover.t) t = match Jose.Jwt.unsafe_of_string (Option.get t.id_token) with | Ok jwt -> if jwt.header.alg = `None then IDToken.validate ?clock_tolerance ?nonce ~client ~issuer:discovery.issuer jwt |> Result.map (fun _ -> t) else ( match Jwks.find_jwk ~jwt jwks with | Some jwk -> IDToken.validate ?clock_tolerance ?nonce ~client ~issuer:discovery.issuer ~jwk jwt |> Result.map (fun _ -> t) (* When there is only 1 key in the jwks we can try with that key according to the OIDC spec *) | None when List.length jwks.keys = 1 -> let jwk = List.hd jwks.keys in IDToken.validate ?clock_tolerance ?nonce ~client ~issuer:discovery.issuer ~jwk jwt |> Result.map (fun _ -> t) | None -> Error (`Msg "Could not find JWK")) | Error e -> Error e let to_yojson { scope; expires_in; access_token; refresh_token; id_token; _ } = let or_null = Option.value ~default:`Null in let json_str = Option.map (fun x -> `String x) in `Assoc [ ( "scope" , match scope with | [] -> `Null | _ -> `String (Scopes.to_scope_parameter scope) ) ; "token_type", `String "Bearer" ; "expires_in", or_null (Option.map (fun x -> `Int x) expires_in) ; "access_token", or_null (json_str access_token) ; "refresh_token", or_null (json_str refresh_token) ; "id_token", or_null (json_str id_token) ]