User authentication and session management for web applications
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement RFC 7636 (PKCE) in ocaml-oauth

The dune-project claimed PKCE support but no implementation existed.
Add code_verifier generation, S256/Plain code_challenge computation,
and integrate into authorization_url and exchange_form_body via
optional parameters. Verified against RFC 7636 Appendix B test vector.

New API: generate_code_verifier, code_challenge, challenge_method type.
Updated: authorization_url and exchange_form_body now accept unit arg
with optional PKCE params. All downstream callers in ocaml-auth updated.

10 unit tests + 2 fuzz tests added. Dependencies: digestif, base64.

+10 -15
+7 -11
lib/auth.ml
··· 155 155 let user_sessions = ref [] in 156 156 Sqlite.Table.iter t.sessions ~f:(fun token signed_value -> 157 157 match verify_session_value ~secret signed_value with 158 - | None -> 159 - Sqlite.Table.delete t.sessions token 158 + | None -> Sqlite.Table.delete t.sessions token 160 159 | Some value -> ( 161 160 match String.split_on_char ':' value with 162 161 | [ uid_s; exp_s ] -> ( ··· 310 309 (** GET /auth/signin — redirect to OAuth provider *) 311 310 let signin_route (cfg : config) (_req : Respond.get_request) = 312 311 let state = Oauth.generate_state () in 313 - let signed_state = 314 - Csrf.sign_state ~secret:cfg.session.cookie_secret state 315 - in 312 + let signed_state = Csrf.sign_state ~secret:cfg.session.cookie_secret state in 316 313 let slug = Oauth.provider_slug cfg.session.oauth_provider in 317 314 let redirect_uri = cfg.session.base_url ^ "/auth/" ^ slug ^ "/callback" in 318 315 let scope = Oauth.default_scope cfg.session.oauth_provider in 319 316 let url = 320 317 Oauth.authorization_url cfg.session.oauth_provider ~client_id:cfg.client_id 321 - ~redirect_uri ~state:signed_state ~scope 318 + ~redirect_uri ~state:signed_state ~scope () 322 319 in 323 320 Respond.Response.redirect url 324 321 ··· 335 332 in 336 333 let form_str = 337 334 Oauth.exchange_form_body ~client_id:cfg.client_id 338 - ~client_secret:cfg.client_secret ~code ~redirect_uri 335 + ~client_secret:cfg.client_secret ~code ~redirect_uri () 339 336 in 340 337 let body = Requests.Body.text form_str in 341 338 let resp = Requests.post cfg.http token_url ~body ~headers in ··· 378 375 Respond.Response.internal_server_error "Token exchange failed" 379 376 | Ok token_resp -> ( 380 377 match 381 - fetch_userinfo cfg.http 382 - ~provider:cfg.session.oauth_provider 378 + fetch_userinfo cfg.http ~provider:cfg.session.oauth_provider 383 379 ~access_token:token_resp.access_token 384 380 with 385 381 | Error e -> ··· 393 389 Store.delete_user_sessions store 394 390 ~secret:cfg.session.cookie_secret ~user_id:user.id; 395 391 let session = 396 - Store.create_session store 397 - ~secret:cfg.session.cookie_secret ~user_id:user.id 392 + Store.create_session store ~secret:cfg.session.cookie_secret 393 + ~user_id:user.id 398 394 in 399 395 Log.info (fun m -> m "callback: signed in %a" pp_user user); 400 396 let cookie =
+3 -4
test/test_auth.ml
··· 174 174 (* ── Cookie middleware ─────────────────────────────────────────── *) 175 175 176 176 let dummy_cfg = 177 - Auth.session_config ~oauth_provider:Oauth.Github 178 - ~base_url:"http://localhost" ~cookie_secret:test_secret ~allow_insecure:true 179 - () 177 + Auth.session_config ~oauth_provider:Oauth.Github ~base_url:"http://localhost" 178 + ~cookie_secret:test_secret ~allow_insecure:true () 180 179 181 180 let test_no_cookie () = 182 181 with_store @@ fun _env _sw store -> ··· 330 329 let test_exchange_form_includes_grant_type () = 331 330 let body = 332 331 Oauth.exchange_form_body ~client_id:"cid" ~client_secret:"sec" ~code:"abc" 333 - ~redirect_uri:"https://app.com/cb" 332 + ~redirect_uri:"https://app.com/cb" () 334 333 in 335 334 Alcotest.(check bool) 336 335 "has grant_type" true