Skip to content

sjqtentacles/sml-oauth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sml-oauth

CI

OAuth 2.0 (RFC 6749) + PKCE (RFC 7636) as a pure sans-IO state machine for Standard ML.

Part of the sjqtentacles monorepo of SML libraries. It builds on sml-codec (Base64 + SHA-256 for PKCE), sml-uri (URL building), sml-json (token response parsing), and sml-random (seeded verifier generation).

Features

  • OAuthPkce — generate {verifier, challenge, method} from a deterministic seed. Challenge = Base64.encodeUrl (Sha256.digest verifier) per RFC 7636 section 4.2. Verified against the RFC 7636 Appendix B test vector.
  • OAuthUrl — authorization-endpoint URL builder with all required params (response_type=code, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method).
  • OAuthToken — token record parsing from RFC 6749 section 5.1 JSON responses.
  • OAuthClient — pure FSM: states Idle | Authorizing | Exchanging | Authenticated | Refreshing | Failed. step advances on events (StartAuth, CodeReceived, TokenReceived, TokenExpired, RefreshReceived, Error).
  • OAuthRequest — HTTP request records for the authorization-code and refresh-token grant types (form-encoded POST bodies).

Status

Working — the PKCE test vector, authorization URL params, FSM transitions, token JSON parsing, and mismatched-state reset are all covered by the test suite.

Portability

Pure Standard ML using only the Basis library (plus the vendored sml-codec, sml-uri, sml-json, and sml-random) — no FFI, no threads, no sockets. Verified on MLton and Poly/ML, with identical, deterministic output across both.

Building and testing

make test        # build + run the suite under MLton (default)
make test-poly   # run the suite under Poly/ML
make all-tests   # run under both
make clean

Usage

(* Generate a PKCE pair from a seed. *)
val rng = Random.fromInt 42
val (pkce, rng') = OAuth.OAuthPkce.generate rng

(* Build the authorization URL. *)
val url = OAuth.OAuthUrl.authorizationUrl
  { endpoint = "https://auth.example.com/authorize",
    clientId = "client-123",
    redirectUri = "https://app.example.com/callback",
    scopes = ["read", "write"],
    state = "xyz",
    pkce = pkce }

(* Drive the FSM. *)
val session =
  { clientId = "client-123",
    redirectUri = "https://app.example.com/callback",
    tokenEndpoint = "https://auth.example.com/token",
    clientSecret = NONE }
val (state1, _) = OAuth.OAuthClient.step session
  OAuth.OAuthClient.Idle
  (OAuth.OAuthClient.StartAuth (pkce, "xyz"))
(* ... redirect the user; receive callback with code + state ... *)
val (state2, _) = OAuth.OAuthClient.step session state1
  (OAuth.OAuthClient.CodeReceived ("thecode", "xyz"))
(* ... execute the token exchange request; parse JSON ... *)
val (state3, _) = OAuth.OAuthClient.step session state2
  (OAuth.OAuthClient.TokenReceived tokenJson)

PKCE test vector

The library is verified against RFC 7636 Appendix B: verifier "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" produces challenge "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM".

Dependencies

  • sml-codecBase64.encodeUrl and Sha256.digest for PKCE.
  • sml-uriPercent.encodeForm for query string construction.
  • sml-json — token response parsing (transitively vendors sml-parsec).
  • sml-random — SplitMix64-based seeded verifier generation.

sml-crypto is intentionally NOT a dependency — PKCE only needs SHA-256 and Base64URL, both provided by sml-codec.

Installing with smlpkg

smlpkg add github.com/sjqtentacles/sml-oauth
smlpkg sync

Then reference the library basis from your own .mlb:

lib/github.com/sjqtentacles/sml-oauth/sml-oauth.mlb

License

MIT. See LICENSE.

About

OAuth 2.0 + PKCE (RFC 6749 / RFC 7636) as a pure state machine in Standard ML (MLton + Poly/ML)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors