Dependencies
Targets: native + js ship the defaults. The traits below compile on every target, but the default HTTP transport and the callback-server factories exist only on native and js; a wasm-gc host injects its own impls.
| Trait | Role |
|---|---|
| OAuthProvider | the provider contract: provider_id / login / refresh / to_auth |
| AuthInteraction | host UI channel — AuthUrl / DeviceCode / Progress messages plus is_cancelled |
| AuthPromptInteraction | optional prompt seam (Secret / Select), kept separate from AuthInteraction so OAuth-only hosts stay source compatible |
| CallbackServer | local loopback callback seam for the PKCE browser flow |
| OAuthHttpTransport | HTTP seam used by every flow; DefaultOAuthHttpTransport ships on native + js only |
| CredentialStore / ProviderCredentialStore / ApiKeyStore | credential storage; ProviderCredentialStore keeps one tagged record per provider and preserves the user's explicit auth-method choice across restarts |
moon add posoco/ext-oauth// moon.pkg: "posoco/ext-oauth" @oauth
let server : &@oauth.CallbackServer = @oauth.create_callback_server()
let transport : &@oauth.OAuthHttpTransport =
@oauth.DefaultOAuthHttpTransport::DefaultOAuthHttpTransport()
let credential = @oauth.run_pkce_browser_flow(
{
client_id: "your-client-id",
authorize_url: "https://auth.example.com/authorize",
token_url: "https://auth.example.com/token",
redirect_uri: "http://localhost:1455/auth/callback",
callback_port: 1455,
scope: "openid profile email offline_access",
extra_authorize_params: [],
fallback_port: Some(1457),
},
interaction, // your &AuthInteraction impl
server,
transport,
)| Field | Meaning |
|---|---|
| client_id | public client id, sent in the authorize URL and the token exchange |
| authorize_url | provider authorization endpoint, e.g. https://auth.openai.com/oauth/authorize |
| token_url | provider token endpoint, e.g. https://auth.openai.com/oauth/token |
| redirect_uri | loopback callback URI, e.g. http://localhost:1455/auth/callback |
| callback_port | port the local callback server binds |
| scope | scope string, url-encoded into the authorize URL |
| extra_authorize_params | provider-specific authorize params (e.g. Codex's originator), appended url-encoded in array order |
| fallback_port | retry port when callback_port is occupied — see the rewrite rule below |
| Target | Impl | Mechanism |
|---|---|---|
| native | NativeCallbackServer | @http.Server on 127.0.0.1; wait_callback polls accept directly (no background accept loop), answers 200 "Login successful" on a code and 404 otherwise |
| js | JsCallbackServer | Bun.serve; its fetch handler writes code/state into the struct, returns 400 on an error param |
// moon.pkg: "posoco/ext-oauth" @oauth
let config = @oauth.DeviceFlowConfig(
client_id="your-client-id",
device_auth_endpoint="https://example.com/device/authorize",
token_endpoint="https://example.com/token",
)
let credential = @oauth.run_device_flow(config, interaction)pub(open) trait ApiKeyStore {
async fn read(Self, provider_id : String) -> ApiKeyCredential? raise OAuthError
async fn write(Self, provider_id : String, credential : ApiKeyCredential) -> Unit raise OAuthError
async fn delete(Self, provider_id : String) -> Unit raise OAuthError
}pub(open) trait AuthInteraction {
fn notify(Self, message : AuthMessage) -> Unit
fn is_cancelled(Self) -> Bool
}pub(open) trait AuthPromptInteraction {
async fn prompt(Self, request : AuthPromptRequest) -> String raise OAuthError
}pub(open) trait CallbackServer {
async fn start(self : Self, port : Int) -> Unit raise OAuthError
async fn wait_callback(self : Self) -> (String, String) raise OAuthError
fn stop(self : Self) -> Unit
}pub(open) trait CredentialStore {
async fn read(Self, provider_id : String) -> Credential? raise OAuthError
async fn write(Self, provider_id : String, credential : Credential) -> Unit raise OAuthError
async fn delete(Self, provider_id : String) -> Unit raise OAuthError
}pub(open) trait OAuthHttpTransport {
async fn post(Self, request : OAuthHttpRequest) -> OAuthHttpResponse raise OAuthError
async fn get(Self, url : String, headers : Map[String, String]) -> OAuthHttpResponse raise OAuthError = _
}pub(open) trait OAuthProvider {
fn provider_id(Self) -> String
async fn login(Self, interaction : &AuthInteraction) -> Credential raise OAuthError
async fn refresh(Self, credential : Credential) -> Credential raise OAuthError
fn to_auth(Self, credential : Credential) -> AuthHeader
}pub(open) trait ProviderCredentialStore {
async fn read(Self, provider_id : String) -> ProviderCredential? raise OAuthError
async fn write(Self, provider_id : String, credential : ProviderCredential) -> Unit raise OAuthError
async fn delete(Self, provider_id : String) -> Unit raise OAuthError
}fn ApiKeyCredential::ApiKeyCredential(secret~ : String, metadata? : Map[String, String]) -> ApiKeyCredentialpub(all) enum AuthMessage {
AuthUrl(String)
DeviceCode(String, String)
Progress(String)
}pub(all) struct AuthPromptOption {
id : String
label : String
description : String?
}fn AuthPromptOption::AuthPromptOption(id~ : String, label~ : String, description? : String?) -> AuthPromptOptionpub(all) enum AuthPromptRequest {
Secret(message~ : String)
Select(message~ : String, options~ : Array[AuthPromptOption])
}pub(all) struct Credential {
access_token : String
refresh_token : String
expires_at : Int
token_type : String
metadata : Map[String, String]
}fn Credential::Credential(access_token~ : String, refresh_token~ : String, expires_at~ : Int, token_type? : String, metadata? : Map[String, String]) -> Credentialpub(all) struct DefaultOAuthHttpTransport {
}async fn get(_self : DefaultOAuthHttpTransport, url : String, headers : Map[String, String]) -> OAuthHttpResponse raise OAuthErrorasync fn post(_self : DefaultOAuthHttpTransport, request : OAuthHttpRequest) -> OAuthHttpResponse raise OAuthErrorasync fn DefaultOAuthHttpTransport::get(_self : DefaultOAuthHttpTransport, url : String, headers : Map[String, String]) -> OAuthHttpResponse raise OAuthErrorasync fn DefaultOAuthHttpTransport::post(_self : DefaultOAuthHttpTransport, request : OAuthHttpRequest) -> OAuthHttpResponse raise OAuthErrorpub(all) struct DeviceAuthResponse {
device_code : String
user_code : String
verification_uri : String
verification_uri_complete : String?
expires_in : Int
interval : Int
}pub(all) struct DeviceFlowConfig {
client_id : String
device_auth_endpoint : String
token_endpoint : String
scope : String
default_headers : Map[String, String]
}fn DeviceFlowConfig::DeviceFlowConfig(client_id~ : String, device_auth_endpoint~ : String, token_endpoint~ : String, scope? : String) -> DeviceFlowConfigfn DeviceFlowConfig::with_default_headers(self : DeviceFlowConfig, headers : Map[String, String]) -> DeviceFlowConfigimpl ApiKeyStore for InMemoryApiKeyStoreasync fn read(self : InMemoryApiKeyStore, provider_id : String) -> ApiKeyCredential? raise OAuthErrorasync fn write(self : InMemoryApiKeyStore, provider_id : String, credential : ApiKeyCredential) -> Unit raise OAuthErrorasync fn InMemoryApiKeyStore::delete(self : InMemoryApiKeyStore, provider_id : String) -> Unit raise OAuthErrorasync fn InMemoryApiKeyStore::read(self : InMemoryApiKeyStore, provider_id : String) -> ApiKeyCredential? raise OAuthErrorasync fn InMemoryApiKeyStore::write(self : InMemoryApiKeyStore, provider_id : String, credential : ApiKeyCredential) -> Unit raise OAuthErrorimpl CredentialStore for InMemoryCredentialStoreasync fn write(self : InMemoryCredentialStore, provider_id : String, credential : Credential) -> Unit raise OAuthErrorasync fn InMemoryCredentialStore::delete(self : InMemoryCredentialStore, provider_id : String) -> Unit raise OAuthErrorasync fn InMemoryCredentialStore::read(self : InMemoryCredentialStore, provider_id : String) -> Credential? raise OAuthErrorasync fn InMemoryCredentialStore::write(self : InMemoryCredentialStore, provider_id : String, credential : Credential) -> Unit raise OAuthErrorasync fn delete(self : InMemoryProviderCredentialStore, provider_id : String) -> Unit raise OAuthErrorasync fn read(self : InMemoryProviderCredentialStore, provider_id : String) -> ProviderCredential? raise OAuthErrorasync fn write(self : InMemoryProviderCredentialStore, provider_id : String, credential : ProviderCredential) -> Unit raise OAuthErrorfn InMemoryProviderCredentialStore::InMemoryProviderCredentialStore() -> InMemoryProviderCredentialStoreasync fn InMemoryProviderCredentialStore::delete(self : InMemoryProviderCredentialStore, provider_id : String) -> Unit raise OAuthErrorasync fn InMemoryProviderCredentialStore::read(self : InMemoryProviderCredentialStore, provider_id : String) -> ProviderCredential? raise OAuthErrorasync fn InMemoryProviderCredentialStore::write(self : InMemoryProviderCredentialStore, provider_id : String, credential : ProviderCredential) -> Unit raise OAuthErrorimpl CallbackServer for NativeCallbackServerasync fn NativeCallbackServer::start(self : NativeCallbackServer, port : Int) -> Unit raise OAuthErrorasync fn NativeCallbackServer::wait_callback(self : NativeCallbackServer) -> (String, String) raise OAuthErrorpub(all) struct NoopAuthInteraction {
}impl AuthInteraction for NoopAuthInteractionpub(all) struct NoopAuthPromptInteraction {
}async fn prompt(_self : NoopAuthPromptInteraction, _request : AuthPromptRequest) -> String raise OAuthErrorasync fn NoopAuthPromptInteraction::prompt(_self : NoopAuthPromptInteraction, _request : AuthPromptRequest) -> String raise OAuthErrorfn OAuthHttpRequest::OAuthHttpRequest(url~ : String, body~ : String, headers~ : Map[String, String]) -> OAuthHttpRequestpub(all) struct OAuthHttpResponse {
status : Int
body : String
}pub(all) struct PkceFlowConfig {
client_id : String
authorize_url : String
token_url : String
redirect_uri : String
callback_port : Int
scope : String
extra_authorize_params : Array[(String, String)]
fallback_port : Int?
}pub(all) struct PkcePair {
code_verifier : String
code_challenge : String
code_challenge_method : String
}pub(all) struct ServerMetadata {
authorization_endpoint : String
token_endpoint : String
device_authorization_endpoint : String?
registration_endpoint : String?
scopes_supported : Array[String]
}fn ServerMetadata::ServerMetadata(authorization_endpoint~ : String, token_endpoint~ : String, device_authorization_endpoint? : String, registration_endpoint? : String, scopes_supported? : Array[String]) -> ServerMetadataasync fn fetch_resource_metadata(uri : String, transport : &OAuthHttpTransport) -> ResourceMetadata raise OAuthErrorasync fn fetch_server_metadata(metadata_url : String, transport : &OAuthHttpTransport) -> ServerMetadata raise OAuthErrorfn oauth_form_url_encode(value : String) -> Stringasync fn register_client(registration_endpoint : String, client_name : String, transport : &OAuthHttpTransport) -> String raise OAuthErrorasync fn run_device_flow(config : DeviceFlowConfig, interaction : &AuthInteraction) -> Credential raise OAuthErrorasync fn run_device_flow_with_transport(config : DeviceFlowConfig, interaction : &AuthInteraction, transport : &OAuthHttpTransport) -> Credential raise OAuthErrorasync fn run_pkce_browser_flow(config : PkceFlowConfig, interaction : &AuthInteraction, server : &CallbackServer, transport : &OAuthHttpTransport, parse_credential? : (Json) -> Credential raise OAuthError, prompt? : &AuthPromptInteraction) -> Credential raise OAuthErrorasync fn run_refresh_flow(token_endpoint~ : String, client_id~ : String, refresh_token~ : String, transport~ : &OAuthHttpTransport) -> Credential raise OAuthErrorInstall
Download zipDependencies