OAuth2 and OpenID Connect (OIDC) client library for MoonBit with support for Authorization Code Flow, Client Credentials, Password Grant, and PKCE
Dependencies
⚠️ ALPHA VERSION This library is currently in alpha stage. APIs may change without notice. Not recommended for production use yet.
{
"deps": {
"ryota0624/oauth2": "*"
}
}let token_url = @oauth2.TokenUrl::new("https://oauth.example.com/token")
let client_id = @oauth2.ClientId::new("your-client-id")
let client_secret = @oauth2.ClientSecret::new("your-client-secret")
let scopes = [@oauth2.Scope::new("read"), @oauth2.Scope::new("write")]
let request = @oauth2.ClientCredentialsRequest::new(
token_url,
client_id,
client_secret,
scopes,
)
let http_client = @oauth2.OAuth2HttpClient::new()
let result = request.execute(http_client)
match result {
Ok(response) => {
let access_token = response.access_token()
println("Access Token: \{access_token}")
}
Err(error) => {
println("Error: \{error.message()}")
}
}// 1. Generate authorization URL
let auth_url = @oauth2.AuthUrl::new("https://oauth.example.com/authorize")
let client_id = @oauth2.ClientId::new("your-client-id")
let redirect_uri = @oauth2.RedirectUrl::new("http://localhost:3000/callback")
let scopes = [@oauth2.Scope::new("openid"), @oauth2.Scope::new("profile")]
let state = @oauth2.generate_csrf_token()
let pkce_verifier = @oauth2.PkceCodeVerifier::new_random()
let pkce_challenge = @oauth2.PkceCodeChallenge::from_verifier_s256(pkce_verifier)
let auth_request = @oauth2.AuthorizationRequest::new_with_pkce(
auth_url,
client_id,
redirect_uri,
scopes,
state,
pkce_challenge,
)
let authorization_url = auth_request.build_authorization_url()
// Redirect user to authorization_url
// 2. Exchange authorization code for token
let token_url = @oauth2.TokenUrl::new("https://oauth.example.com/token")
let client_secret = @oauth2.ClientSecret::new("your-client-secret")
let code = "authorization-code-from-callback"
let token_request = @oauth2.TokenRequest::new_with_pkce(
token_url,
client_id,
client_secret,
code,
redirect_uri,
pkce_verifier,
)
let http_client = @oauth2.OAuth2HttpClient::new()
let result = token_request.execute(http_client)let http_client = @oauth2.OAuth2HttpClient::new()
let client_id = "your-client-id.apps.googleusercontent.com"
let client_secret = "your-client-secret"
let redirect_uri = "http://localhost:3000/callback"
// 1. Fetch Google Discovery Document
let discovery = @providers.google.fetch_discovery(http_client)?
// 2. Generate authorization URL with PKCE
let state = @oauth2.generate_csrf_token()
let pkce_verifier = @oauth2.PkceCodeVerifier::new_random()
let pkce_challenge = @oauth2.PkceCodeChallenge::from_verifier_s256(pkce_verifier)
let auth_request = @oauth2.AuthorizationRequest::new_with_pkce(
discovery.authorization_url(),
@oauth2.ClientId::new(client_id),
@oauth2.RedirectUrl::new(redirect_uri),
[
@oauth2.Scope::new("openid"),
@oauth2.Scope::new("email"),
@oauth2.Scope::new("profile"),
],
state,
pkce_challenge,
)
let auth_url = auth_request.build_authorization_url()
// Redirect user to auth_url
// 3. Exchange authorization code for tokens
let token_request = @oauth2.TokenRequest::new_with_pkce(
discovery.token_url(),
@oauth2.ClientId::new(client_id),
@oauth2.ClientSecret::new(client_secret),
authorization_code,
@oauth2.RedirectUrl::new(redirect_uri),
pkce_verifier,
)
let token_response = token_request.execute(http_client)?
// 4. Verify ID Token
let id_token = @oidc.get_id_token_from_response(token_response)?
@providers.google.verify_id_token(id_token, client_id, http_client, None)?
println("User ID: \{id_token.subject()}")
println("Email: \{id_token.email().or(\"N/A\")}")moon test# Start Keycloak and setup test environment
./scripts/setup_keycloak.sh
# Run OAuth2 integration tests
./scripts/test_keycloak_moonbit.sh# Run OIDC verification tests
./scripts/verify_oidc.shpub struct AuthorizationRequest {
auth_url : AuthUrl
client_id : ClientId
redirect_uri : RedirectUrl
scope : Array[Scope]
state : CsrfToken
response_type : String
pkce_challenge : PkceCodeChallenge?
nonce : Nonce?
} derive(Show)fn AuthorizationRequest::new(auth_url : AuthUrl, client_id : ClientId, redirect_uri : RedirectUrl, scope : Array[Scope], state : CsrfToken) -> AuthorizationRequestfn AuthorizationRequest::new_with_pkce(auth_url : AuthUrl, client_id : ClientId, redirect_uri : RedirectUrl, scope : Array[Scope], state : CsrfToken, pkce_challenge : PkceCodeChallenge) -> AuthorizationRequestfn AuthorizationRequest::with_nonce(self : AuthorizationRequest, nonce : Nonce) -> AuthorizationRequestasync fn ClientCredentialsRequest::execute(self : ClientCredentialsRequest, http_client : OAuth2HttpClient) -> Result[TokenResponse, OAuth2Error]fn ClientCredentialsRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, scope : Array[Scope]) -> ClientCredentialsRequestpub struct HttpRequest {
url : String
http_method : HttpMethod
headers : Map[String, String]
body : String
} derive(Show)fn HttpRequest::new(url : String, http_method : HttpMethod, headers : Map[String, String], body : String) -> HttpRequestfn HttpResponse::new(status_code : Int, headers : Map[String, String], body : String) -> HttpResponsepub enum OAuth2Error {
InvalidRequest(String)
InvalidClient(String)
InvalidGrant(String)
UnauthorizedClient(String)
UnsupportedGrantType(String)
InvalidScope(String)
AccessDenied(String)
UnsupportedResponseType(String)
ServerError(String)
TemporarilyUnavailable(String)
HttpError(String)
ParseError(String)
} derive(Eq, Show)async fn OAuth2HttpClient::get(self : OAuth2HttpClient, url : String, headers : Map[String, String]) -> Result[HttpResponse, OAuth2Error]async fn OAuth2HttpClient::post(self : OAuth2HttpClient, url : String, headers : Map[String, String], body : String) -> Result[HttpResponse, OAuth2Error]pub struct OAuth2HttpClientConfig {
max_retries : Int
base_delay_ms : Int
custom_headers : Map[String, String]?
user_agent : String?
debug : Bool
}fn OAuth2HttpClientConfig::with_base_delay_ms(self : OAuth2HttpClientConfig, base_delay_ms : Int) -> OAuth2HttpClientConfigfn OAuth2HttpClientConfig::with_custom_headers(self : OAuth2HttpClientConfig, custom_headers : Map[String, String]) -> OAuth2HttpClientConfigfn OAuth2HttpClientConfig::with_debug(self : OAuth2HttpClientConfig, debug : Bool) -> OAuth2HttpClientConfigfn OAuth2HttpClientConfig::with_max_retries(self : OAuth2HttpClientConfig, max_retries : Int) -> OAuth2HttpClientConfigfn OAuth2HttpClientConfig::with_user_agent(self : OAuth2HttpClientConfig, user_agent : String) -> OAuth2HttpClientConfigasync fn PasswordRequest::execute(self : PasswordRequest, http_client : OAuth2HttpClient) -> Result[TokenResponse, OAuth2Error]fn PasswordRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret?, username : String, password : String, scope : Array[Scope]) -> PasswordRequestpub struct PkceCodeChallenge {
value : String
challenge_method : PkceCodeChallengeMethod
} derive(Eq, Show)pub struct TokenRequest {
token_url : TokenUrl
client_id : ClientId
client_secret : ClientSecret
code : String
redirect_uri : RedirectUrl
grant_type : String
pkce_verifier : PkceCodeVerifier?
} derive(Show)async fn TokenRequest::execute(self : TokenRequest, http_client : OAuth2HttpClient) -> Result[TokenResponse, OAuth2Error]fn TokenRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, code : String, redirect_uri : RedirectUrl) -> TokenRequestfn TokenRequest::new_with_pkce(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, code : String, redirect_uri : RedirectUrl, pkce_verifier : PkceCodeVerifier) -> TokenRequestpub struct TokenResponse {
access_token : AccessToken
token_type : String
expires_in : Int?
refresh_token : RefreshToken?
scope : String?
id_token : String?
} derive(Show)fn TokenResponse::new(access_token : AccessToken, token_type : String, expires_in : Int?, refresh_token : RefreshToken?, scope : String?, id_token : String?) -> TokenResponsefn base64url_encode(s : String) -> Stringfn url_encode(s : String) -> StringInstall
Download zipOAuth2 and OpenID Connect (OIDC) client library for MoonBit with support for Authorization Code Flow, Client Credentials, Password Grant, and PKCE
Dependencies