package savvy
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=a9e2b62cbf273c881d47d86db243706e41dcf52ce3107288b7edcf61cc53bb10
sha512=2903142b0f64d06c02ab91e3a3545d8e38f0bef34a57e7aa8d97d06f11f12daee43825a4c91afa39f5c6542b61e8bb3a57b1076358031ae74b5d6d0e5944cecd
Description
A client library for OAuth2 that will help make the setup of secure auth flows easy to manage.
README
OAuth2 Client for OCaml
An easy to use OAuth2 client for your next OCaml app
Example usages
Savvy is designed so that you can just open it at the top and leverage whatever you need. The below examples assume you have open Savvy
at the top of your file, but if you would rather not do that, just add Savvy.
before the functions (which you can find a list of in src/savvy.mli
).
Client Credentials
(* Each flow type has its own config type so the compiler can tell you forgot things *)
let config = Oauth2_client.ClientCredentialsConfig {
client_id = "your-client-id";
client_secret = "your-client-secret";
scope = ["foo:create" ; "foo:read" ; "foo:update"];
token_auth_method = Basic; (* Can be Basic or Body - the two places that credentials can live in transit *)
token_endpoint = Uri.of_string "https://example.com/token"; (* Client Credentials flow goes straight to the token endpoint *)
} in
Client.get_client_credentials_token ~config
>>= fun token ->
let token_info =
"<p>Auth was successful!</p>" ^
"<p>Access Token: " ^ token.access_token ^ "</p>" ^
(match token.refresh_token with
| Some refresh_token -> "<p>Refresh Token: " ^ refresh_token ^ "</p>"
| None -> "") in
Server.respond_string ~status:`OK ~body:(token_info ^ "<a href='/client-creds'>Auth Again</a>") ()
Authorization Code
First you need to construct the authorize URL, so you can pass your config in and have Savvy generate that for you.
let config = Oauth2_client.AuthorizationCodeConfig {
authorization_endpoint = Uri.of_string "https://example.com/authorize";
client_id = "your-client-id"; (* Replace with your client ID *)
client_secret = Some "your-client-secret"; (* Replace with your client secret or None if a public client *)
pkce = S256; (* Allowed values: S256, Plain, No_Pkce *)
pkce_verifier = None; (* Pass None to have it auto-generate which is more secure *)
redirect_uri = Uri.of_string "https://example.com/callback"; (* Replace with your redirect URI *)
scope = ["bar:create" ; "bar:read" ; "bar:update"]; (* Replace with your desired scopes *)
token_auth_method = Basic; (* Can be Basic or Body, depending on whether you are putting credentials in a basic header or the body *)
token_endpoint = Uri.of_string "https://example.com/token"; (* Replace with your token endpoint *)
} in
let (auth_url, _state, _code_verifier) = Client.get_authorization_url ~config in
When generating the authorize URL, you will also receive a state value and a PKCE code_verifier. Savvy will store these (as noted below) but if you need them for an external auth service or because your callback route is being served from another server, you can grab them and stash them wherever you please.
Because token exchange is a request FROM the OAuth2 server TO your app, it will happen in a separate part of your code. Savvy can store the PKCE code_verifier and flow config in-memory for you, using the InMemoryStorage module that comes bundled in, but you should plan to implement your own storage module that matches the STORAGE_UNIT interface before going to production.
let uri = Request.uri req in
let code_query = Uri.get_query_param uri "code" in
let state_query = Uri.get_query_param uri "state" in
(* Here is where you should validate the state from the query params *)
match state_query with
| Some state -> begin
match code_query with
| Some code -> begin
Client.exchange_code_for_token state code
>>= fun token ->
let token_info =
"<p>Auth was successful!</p>" ^
"<p>Access Token: " ^ token.access_token ^ "</p>" ^
(match token.refresh_token with
| Some refresh_token -> "<p>Refresh Token: " ^ refresh_token ^ "</p>"
| None -> "") in
Server.respond_string ~status:`OK ~body:(token_info ^ "<a href='/'>Back to Login</a>") ()
end
| None ->
Server.respond_string ~status:`Bad_request ~body:"No code parameter provided" ()
end
| None -> Server.respond_string ~status:`Bad_request ~body:"No code parameter provided" ()
Dependencies (9)
- cryptokit
- ppx_deriving_yojson
- ppx_deriving
- uri
- yojson
- cohttp-lwt-unix
-
base64
>= "3.5.0"
-
dune
>= "3.16"
- ocaml
Dev Dependencies (1)
-
odoc
with-doc
Used by
None
Conflicts
None