Sign in

    oauth2

    OAuth2 and OpenID Connect (OIDC) client library for MoonBit with support for Authorization Code Flow, Client Credentials, Password Grant, and PKCE

    oauth2
    oidc
    openid-connect
    authentication
    authorization
    pkce
    jwt
    id-token
    Download zip
    Author
    Version
    0.1.2
    License
    Apache-2.0
    Last updated
    7 months ago
    Downloads
    616

    Dependencies

    #ryota0624/oauth2

    Test

    ⚠️ ALPHA VERSION This library is currently in alpha stage. APIs may change without notice. Not recommended for production use yet.

    OAuth2 client library for MoonBit with support for Native and JS targets.

    #Features

    • ✅ Authorization Code Flow with PKCE support
    • ✅ Client Credentials Flow (Machine-to-Machine)
    • ✅ Password Grant Flow (Resource Owner Password Credentials)
    • ✅ Refresh Token support
    • ✅ CSRF protection with secure token generation
    • ✅ Cryptographically secure random number generation (Chacha8 CSPRNG)
    • ✅ Type-safe API with proper error handling
    • ✅ Cross-platform (Native and JS targets)

    #Installation

    Add to your moon.mod.json:

    { "deps": { "ryota0624/oauth2": "*" } }

    #Quick Start

    #Client Credentials Flow

    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()}")
    }
    }

    #Authorization Code Flow with PKCE

    // 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)

    #Testing

    #Unit Tests

    moon test

    #Integration Tests with Keycloak

    #OAuth2 Tests

    # Start Keycloak and setup test environment ./scripts/setup_keycloak.sh # Run OAuth2 integration tests ./scripts/test_keycloak_moonbit.sh

    #OIDC Verification Tests

    # Run OIDC verification tests ./scripts/verify_oidc.sh

    See OIDC Verification Guide for detailed instructions.

    #CI/CD

    This project uses GitHub Actions for continuous integration:

    • Unit Tests: Run on both Native and JS targets
    • Integration Tests: Run with Keycloak using JS target after unit tests pass
    • Code Quality: Formatting and type checking

    The integration tests use the JS target to verify cross-platform compatibility.

    #Documentation

    #Architecture

    • lib/oauth2/ - Core OAuth2 library
      • types.mbt - Type definitions (ClientId, AccessToken, etc.)
      • client_credentials.mbt - Client Credentials Flow
      • password_request.mbt - Password Grant Flow
      • authorization_request.mbt - Authorization Code Flow
      • token_request.mbt - Token exchange
      • pkce.mbt - PKCE implementation
      • http_client.mbt - HTTP client abstraction

    • lib/keycloak_test/ - Integration test suite
      • Tests all OAuth2 flows with real Keycloak server
      • Includes UserInfo endpoint verification

    #Security

    • PKCE (Proof Key for Code Exchange) for Authorization Code Flow
    • CSRF protection with cryptographically secure tokens
    • Chacha8 CSPRNG for random number generation
    • Type-safe API prevents common security mistakes

    #License

    Apache-2.0

    #Contributing

    Contributions are welcome! Please see CLAUDE.md for development guidelines.

    HttpHeaders

    type HttpHeaders = Map[String, String]

    HttpHeaders represents HTTP headers as a map

    AccessToken

    type AccessToken

    AccessToken represents the OAuth2 access token
    impl Eq for AccessToken
    impl Show for AccessToken

    AccessToken::new

    fn AccessToken::new(value : String) -> AccessToken

    Create a new AccessToken

    AccessToken::to_string

    fn AccessToken::to_string(self : AccessToken) -> String

    Get the string value of AccessToken

    AuthUrl

    type AuthUrl

    AuthUrl represents the authorization endpoint URL
    impl Eq for AuthUrl
    impl Show for AuthUrl

    AuthUrl::new

    fn AuthUrl::new(value : String) -> AuthUrl

    Create a new AuthUrl

    AuthUrl::to_string

    fn AuthUrl::to_string(self : AuthUrl) -> String

    Get the string value of AuthUrl

    AuthorizationRequest

    pub struct AuthorizationRequest {
    auth_url : AuthUrl
    client_id : ClientId
    redirect_uri : RedirectUrl
    scope : Array[Scope]
    state : CsrfToken
    response_type : String
    pkce_challenge : PkceCodeChallenge?
    nonce : Nonce?
    }

    AuthorizationRequest represents an OAuth2 authorization request

    AuthorizationRequest::build_authorization_url

    fn AuthorizationRequest::build_authorization_url(self : AuthorizationRequest) -> String

    Build the authorization URL with all parameters

    AuthorizationRequest::new

    fn AuthorizationRequest::new(auth_url : AuthUrl, client_id : ClientId, redirect_uri : RedirectUrl, scope : Array[Scope], state : CsrfToken) -> AuthorizationRequest

    Create a new AuthorizationRequest

    AuthorizationRequest::new_with_pkce

    fn AuthorizationRequest::new_with_pkce(auth_url : AuthUrl, client_id : ClientId, redirect_uri : RedirectUrl, scope : Array[Scope], state : CsrfToken, pkce_challenge : PkceCodeChallenge) -> AuthorizationRequest

    Create a new AuthorizationRequest with PKCE support

    AuthorizationRequest::with_nonce

    Add nonce to an existing AuthorizationRequest (OIDC)

    ClientCredentialsRequest

    pub struct ClientCredentialsRequest {
    token_url : TokenUrl
    client_id : ClientId
    client_secret : ClientSecret
    scope : Array[Scope]
    grant_type : String
    }

    ClientCredentialsRequest represents an OAuth2 client credentials request Used for machine-to-machine (M2M) authentication

    ClientCredentialsRequest::build_request_body

    fn ClientCredentialsRequest::build_request_body(self : ClientCredentialsRequest) -> String

    Build the request body for client credentials request Returns application/x-www-form-urlencoded format

    ClientCredentialsRequest::execute

    Execute the client credentials request using HTTP client Returns TokenResponse on success, OAuth2Error on failure

    ClientCredentialsRequest::get_auth_header

    fn ClientCredentialsRequest::get_auth_header(self : ClientCredentialsRequest) -> String

    Get authorization header for Basic authentication

    ClientCredentialsRequest::new

    fn ClientCredentialsRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, scope : Array[Scope]) -> ClientCredentialsRequest

    Create a new ClientCredentialsRequest

    ClientId

    type ClientId

    ClientId represents the OAuth2 client identifier
    impl Eq for ClientId
    impl Show for ClientId

    ClientId::new

    fn ClientId::new(value : String) -> ClientId

    Create a new ClientId

    ClientId::to_string

    fn ClientId::to_string(self : ClientId) -> String

    Get the string value of ClientId

    ClientSecret

    type ClientSecret

    ClientSecret represents the OAuth2 client secret
    impl Eq for ClientSecret

    ClientSecret::new

    fn ClientSecret::new(value : String) -> ClientSecret

    Create a new ClientSecret

    ClientSecret::to_string

    fn ClientSecret::to_string(self : ClientSecret) -> String

    Get the string value of ClientSecret

    CsrfToken

    type CsrfToken

    CsrfToken represents the state parameter for CSRF protection
    impl Eq for CsrfToken
    impl Show for CsrfToken

    CsrfToken::new

    fn CsrfToken::new(value : String) -> CsrfToken

    Create a new CsrfToken

    CsrfToken::to_string

    fn CsrfToken::to_string(self : CsrfToken) -> String

    Get the string value of CsrfToken

    HttpMethod

    pub(all) enum HttpMethod {
    GET
    POST
    PUT
    DELETE
    }

    HttpMethod represents HTTP request methods Note: GET, POST, PUT, DELETE variants are defined for future use
    impl Eq for HttpMethod
    impl Show for HttpMethod

    HttpMethod::to_string

    fn HttpMethod::to_string(self : HttpMethod) -> String

    Convert HttpMethod to string

    HttpRequest

    pub struct HttpRequest {
    url : String
    http_method : HttpMethod
    headers : Map[String, String]
    body : String
    }

    HttpRequest represents an HTTP request
    impl Show for HttpRequest

    HttpRequest::new

    fn HttpRequest::new(url : String, http_method : HttpMethod, headers : Map[String, String], body : String) -> HttpRequest

    Create a new HttpRequest

    HttpResponse

    pub struct HttpResponse {
    status_code : Int
    headers : Map[String, String]
    body : String
    }

    HttpResponse represents an HTTP response

    HttpResponse::is_error

    fn HttpResponse::is_error(self : HttpResponse) -> Bool

    Check if response is error (4xx or 5xx status code)

    HttpResponse::is_success

    fn HttpResponse::is_success(self : HttpResponse) -> Bool

    Check if response is successful (2xx status code)

    HttpResponse::new

    fn HttpResponse::new(status_code : Int, headers : Map[String, String], body : String) -> HttpResponse

    Create a new HttpResponse

    Nonce

    type Nonce

    Nonce represents the nonce parameter for OIDC replay attack protection
    impl Eq for Nonce
    impl Show for Nonce

    Nonce::new

    fn Nonce::new(value : String) -> Nonce

    Create a new Nonce

    Nonce::to_string

    fn Nonce::to_string(self : Nonce) -> String

    Get the string value of Nonce

    OAuth2Error

    pub 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)
    Other(String)
    }

    OAuth2Error represents errors that can occur during OAuth2 flow
    impl Eq for OAuth2Error
    impl Show for OAuth2Error

    OAuth2Error::from_error_code

    fn OAuth2Error::from_error_code(error_code : String, error_description : String?) -> OAuth2Error

    Create an OAuth2Error from error code and description

    OAuth2Error::message

    fn OAuth2Error::message(self : OAuth2Error) -> String

    Get error message

    OAuth2Error::new_http_error

    fn OAuth2Error::new_http_error(msg : String) -> OAuth2Error

    Create an HTTP error

    OAuth2Error::new_other

    fn OAuth2Error::new_other(msg : String) -> OAuth2Error

    Create a generic error

    OAuth2Error::new_parse_error

    fn OAuth2Error::new_parse_error(msg : String) -> OAuth2Error

    Create a parse error

    OAuth2HttpClient

    pub struct OAuth2HttpClient {
    debug : Bool
    }

    OAuth2HttpClient provides HTTP functionality for OAuth2 operations

    OAuth2HttpClient::get

    async fn OAuth2HttpClient::get(self : OAuth2HttpClient, url : String, headers : Map[String, String]) -> Result[HttpResponse, OAuth2Error]

    Send HTTP GET request Used for OIDC UserInfo Endpoint and other GET requests

    OAuth2HttpClient::new

    Create a new OAuth2HttpClient with debug output disabled (default)

    OAuth2HttpClient::new_with_debug

    fn OAuth2HttpClient::new_with_debug(debug : Bool) -> OAuth2HttpClient

    Create a new OAuth2HttpClient with configurable debug output

    OAuth2HttpClient::post

    async fn OAuth2HttpClient::post(self : OAuth2HttpClient, url : String, headers : Map[String, String], body : String) -> Result[HttpResponse, OAuth2Error]

    Send HTTP POST request using mizchi/x

    PasswordRequest

    pub struct PasswordRequest {
    token_url : TokenUrl
    client_id : ClientId
    client_secret : ClientSecret?
    username : String
    password : String
    scope : Array[Scope]
    grant_type : String
    }

    WARNING: Resource Owner Password Credentials Grant is deprecated and should only be used for legacy systems. Consider using Authorization Code Grant with PKCE instead.

    PasswordRequest represents an OAuth2 password credentials request Used when the resource owner has a trust relationship with the client

    PasswordRequest::build_request_body

    fn PasswordRequest::build_request_body(self : PasswordRequest) -> String

    Build the request body for password credentials request Returns application/x-www-form-urlencoded format

    PasswordRequest::execute

    async fn PasswordRequest::execute(self : PasswordRequest, http_client : OAuth2HttpClient) -> Result[TokenResponse, OAuth2Error]

    Execute the password credentials request using HTTP client Returns TokenResponse on success, OAuth2Error on failure WARNING: This grant type is deprecated and should only be used for legacy systems.

    PasswordRequest::get_auth_header

    fn PasswordRequest::get_auth_header(self : PasswordRequest) -> String?

    Get authorization header for Basic authentication Returns None if client_secret is not provided

    PasswordRequest::new

    fn PasswordRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret?, username : String, password : String, scope : Array[Scope]) -> PasswordRequest

    Create a new PasswordRequest WARNING: This grant type is deprecated. Use Authorization Code Flow with PKCE instead.

    PkceCodeChallenge

    pub struct PkceCodeChallenge {
    value : String
    challenge_method : PkceCodeChallengeMethod
    }

    PKCE code_challenge Derived from code_verifier using S256 (SHA256) or Plain method

    PkceCodeChallenge::from_verifier_plain

    fn PkceCodeChallenge::from_verifier_plain(verifier : PkceCodeVerifier) -> PkceCodeChallenge

    Create a PkceCodeChallenge using Plain method code_challenge = code_verifier (not recommended, use S256 instead)

    PkceCodeChallenge::from_verifier_s256

    fn PkceCodeChallenge::from_verifier_s256(verifier : PkceCodeVerifier) -> PkceCodeChallenge

    Create a PkceCodeChallenge from a code_verifier Uses S256 method (SHA256 hash + Base64URL encoding)

    PkceCodeChallenge::method_string

    fn PkceCodeChallenge::method_string(self : PkceCodeChallenge) -> String

    Get the code_challenge_method as a string

    PkceCodeChallenge::to_string

    fn PkceCodeChallenge::to_string(self : PkceCodeChallenge) -> String

    Get the string value of code_challenge

    PkceCodeChallengeMethod

    pub enum PkceCodeChallengeMethod {
    Plain
    S256
    }

    PKCE code_challenge_method

    PkceCodeVerifier

    pub struct PkceCodeVerifier {
    value : String
    }

    PKCE code_verifier A cryptographically random string between 43 and 128 characters

    PkceCodeVerifier::new

    fn PkceCodeVerifier::new(value : String) -> PkceCodeVerifier

    Create a new PkceCodeVerifier from an existing string The string must be 43-128 characters long and contain only unreserved characters

    PkceCodeVerifier::new_random

    Generate a random PKCE code_verifier Returns a 43-character string (256 bits of entropy) Uses cryptographically secure random number generator (Chacha8 CSPRNG) Characters: A-Z, a-z, 0-9, -, ., _, ~

    PkceCodeVerifier::to_string

    fn PkceCodeVerifier::to_string(self : PkceCodeVerifier) -> String

    Get the string value of code_verifier

    RedirectUrl

    type RedirectUrl

    RedirectUrl represents the redirect URL for OAuth2 callback
    impl Eq for RedirectUrl
    impl Show for RedirectUrl

    RedirectUrl::new

    fn RedirectUrl::new(value : String) -> RedirectUrl

    Create a new RedirectUrl

    RedirectUrl::to_string

    fn RedirectUrl::to_string(self : RedirectUrl) -> String

    Get the string value of RedirectUrl

    RefreshToken

    type RefreshToken

    RefreshToken represents the OAuth2 refresh token
    impl Eq for RefreshToken

    RefreshToken::new

    fn RefreshToken::new(value : String) -> RefreshToken

    Create a new RefreshToken

    RefreshToken::to_string

    fn RefreshToken::to_string(self : RefreshToken) -> String

    Get the string value of RefreshToken

    Scope

    type Scope

    Scope represents OAuth2 access scope
    impl Eq for Scope
    impl Show for Scope

    Scope::address

    fn Scope::address() -> Scope

    OIDC standard scope: address

    Scope::email

    fn Scope::email() -> Scope

    OIDC standard scope: email

    Scope::new

    fn Scope::new(value : String) -> Scope

    Create a new Scope

    Scope::openid

    fn Scope::openid() -> Scope

    OIDC standard scope: openid (required for OIDC)

    Scope::phone

    fn Scope::phone() -> Scope

    OIDC standard scope: phone

    Scope::profile

    fn Scope::profile() -> Scope

    OIDC standard scope: profile

    Scope::to_string

    fn Scope::to_string(self : Scope) -> String

    Get the string value of Scope

    TokenRequest

    pub struct TokenRequest {
    token_url : TokenUrl
    client_id : ClientId
    client_secret : ClientSecret
    code : String
    redirect_uri : RedirectUrl
    grant_type : String
    pkce_verifier : PkceCodeVerifier?
    }

    TokenRequest represents an OAuth2 token request Used to exchange authorization code for access token

    TokenRequest::build_request_body

    fn TokenRequest::build_request_body(self : TokenRequest) -> String

    Build the request body for token request Returns application/x-www-form-urlencoded format

    TokenRequest::execute

    async fn TokenRequest::execute(self : TokenRequest, http_client : OAuth2HttpClient) -> Result[TokenResponse, OAuth2Error]

    Execute the token request using HTTP client Returns TokenResponse on success, OAuth2Error on failure

    TokenRequest::get_auth_header

    fn TokenRequest::get_auth_header(self : TokenRequest) -> String

    Get authorization header for Basic authentication

    TokenRequest::new

    fn TokenRequest::new(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, code : String, redirect_uri : RedirectUrl) -> TokenRequest

    Create a new TokenRequest for authorization code grant

    TokenRequest::new_with_pkce

    fn TokenRequest::new_with_pkce(token_url : TokenUrl, client_id : ClientId, client_secret : ClientSecret, code : String, redirect_uri : RedirectUrl, pkce_verifier : PkceCodeVerifier) -> TokenRequest

    Create a new TokenRequest with PKCE support

    TokenResponse

    pub struct TokenResponse {
    access_token : AccessToken
    token_type : String
    expires_in : Int?
    refresh_token : RefreshToken?
    scope : String?
    id_token : String?
    }

    TokenResponse represents the response from the token endpoint

    TokenResponse::access_token

    fn TokenResponse::access_token(self : TokenResponse) -> AccessToken

    Get the access token

    TokenResponse::expires_in

    fn TokenResponse::expires_in(self : TokenResponse) -> Int?

    Get the expiration time in seconds

    TokenResponse::id_token

    fn TokenResponse::id_token(self : TokenResponse) -> String?

    Get the ID Token if present (OIDC)

    TokenResponse::new

    fn TokenResponse::new(access_token : AccessToken, token_type : String, expires_in : Int?, refresh_token : RefreshToken?, scope : String?, id_token : String?) -> TokenResponse

    Create a new TokenResponse

    TokenResponse::refresh_token

    fn TokenResponse::refresh_token(self : TokenResponse) -> RefreshToken?

    Get the refresh token if present

    TokenResponse::scope

    fn TokenResponse::scope(self : TokenResponse) -> String?

    Get the scope if present

    TokenResponse::token_type

    fn TokenResponse::token_type(self : TokenResponse) -> String

    Get the token type

    TokenUrl

    type TokenUrl

    TokenUrl represents the token endpoint URL
    impl Eq for TokenUrl
    impl Show for TokenUrl

    TokenUrl::new

    fn TokenUrl::new(value : String) -> TokenUrl

    Create a new TokenUrl

    TokenUrl::to_string

    fn TokenUrl::to_string(self : TokenUrl) -> String

    Get the string value of TokenUrl

    base64url_encode

    fn base64url_encode(s : String) -> String

    Base64URL encode a string (RFC 4648 Section 5) Uses URL-safe alphabet: replaces + with - and / with _ Removes padding (=) characters

    build_basic_auth_header

    fn build_basic_auth_header(client_id : ClientId, client_secret : ClientSecret) -> String

    Build Basic Authentication header value

    build_form_urlencoded_body

    fn build_form_urlencoded_body(params : Map[String, String]) -> String

    Build application/x-www-form-urlencoded body from parameters

    build_scope_string

    fn build_scope_string(scopes : Array[Scope]) -> String

    Build scope string from array of Scope Joins scope values with space separator

    generate_csrf_token

    fn generate_csrf_token() -> CsrfToken

    Generate a random CSRF token Uses cryptographically secure random number generator (Chacha8 CSPRNG)

    generate_nonce

    fn generate_nonce() -> Nonce

    Generate a random nonce for OIDC Uses cryptographically secure random number generator (Chacha8 CSPRNG)

    int_to_hex

    fn int_to_hex(n : Int) -> String

    Convert integer to 2-digit uppercase hex string

    parse_oauth2_error

    fn parse_oauth2_error(body : String) -> OAuth2Error

    Parse OAuth2 error from response body Expects JSON format: {"error": "...", "error_description": "..."}

    parse_token_response

    fn parse_token_response(json : String) -> Result[TokenResponse, OAuth2Error]

    Parse token response from JSON Expected format: {"access_token":"...","token_type":"Bearer",...}

    url_encode

    fn url_encode(s : String) -> String

    URL encoding for form data (RFC 3986) Encodes special characters as %XX where XX is the hex value