Skip to main content

Python SDK API Reference

OAuth Exchange

exchange_code_for_claims()

Exchanges a Hawcx authorization code for verified JWT claims. Parameters:
def exchange_code_for_claims(
    code: str,                       # Authorization code from Hawcx
    oauth_token_url: str,            # Token endpoint URL
    client_id: str,                  # OAuth client ID
    public_key: str | Path,          # Public key (PEM string or file path)
    code_verifier: str | None = None,  # PKCE code verifier (optional)
    audience: str | None = None,     # Expected 'aud' claim (optional)
    issuer: str | None = None,       # Expected 'iss' claim (optional)
    leeway: int = 0                  # Clock skew tolerance in seconds
) -> dict[str, Any]
Returns:
{
    'sub': str,                      # Subject (user ID)
    'email': str,                    # User email
    'email_verified': bool,          # Whether email is verified
    'aud': str,                      # Audience
    'iss': str,                      # Issuer
    'iat': int,                      # Issued at (Unix timestamp)
    'exp': int,                      # Expiration (Unix timestamp)
    # ... additional claims
}
Raises:
  • OAuthExchangeError - Failed to exchange code for token
  • JWTVerificationError - JWT signature or claims validation failed
  • InvalidPublicKeyError - Public key is malformed
Example:
from hawcx_oauth_client import exchange_code_for_claims
import os

try:
    claims = exchange_code_for_claims(
        code='auth_code_from_client',
        oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
        client_id=os.getenv('OAUTH_CLIENT_ID'),
        public_key=os.getenv('OAUTH_PUBLIC_KEY'),
        audience=os.getenv('OAUTH_CLIENT_ID')
    )
    
    print(f'User ID: {claims["sub"]}')
    print(f'Email: {claims["email"]}')
except Exception as error:
    print(f'Exchange failed: {error}')

JWT Verification

verify_jwt()

Manually verify a JWT token using the provided public key. Parameters:
def verify_jwt(
    token: str,                      # JWT token to verify
    public_key: str | Path,          # Public key (PEM string or file path)
    algorithms: list[str] | None = None,  # Allowed algorithms (default: ['RS256'])
    audience: str | None = None,     # Expected 'aud' claim
    issuer: str | None = None,       # Expected 'iss' claim
    leeway: int = 0                  # Clock skew tolerance in seconds
) -> dict[str, Any]
Returns:
dict[str, Any]  # JWT payload
Example:
from hawcx_oauth_client import verify_jwt

payload = verify_jwt(
    token=token,
    public_key=public_key,
    audience='my-app',
    issuer='https://oauth.hawcx.com'
)

Delegation Client

The HawcxDelegationClient provides high-level APIs for managing user MFA settings and credentials.

HawcxDelegationClient.from_keys()

Initialize the delegation client with explicit cryptographic keys. Parameters:
@classmethod
def from_keys(
    cls,
    sp_signing_key: str,             # Service provider ED25519 private key (PEM)
    sp_encryption_key: str,          # Service provider X25519 private key (PEM)
    idp_verify_key: str,             # Identity provider ED25519 public key (PEM)
    idp_encryption_key: str,         # Identity provider X25519 public key (PEM)
    base_url: str | None = None,     # Hawcx base URL
    sp_id: str | None = None         # Service provider ID
) -> HawcxDelegationClient
Example:
from hawcx_oauth_client.delegation import HawcxDelegationClient
import os

client = HawcxDelegationClient.from_keys(
    sp_signing_key=os.getenv('SP_ED25519_PRIVATE_KEY_PEM'),
    sp_encryption_key=os.getenv('SP_X25519_PRIVATE_KEY_PEM'),
    idp_verify_key=os.getenv('IDP_ED25519_PUBLIC_KEY_PEM'),
    idp_encryption_key=os.getenv('IDP_X25519_PUBLIC_KEY_PEM'),
    sp_id=os.getenv('OAUTH_CLIENT_ID')
)

initiate_mfa_change()

Initiate MFA setup or change for a user. Parameters:
def initiate_mfa_change(
    self,
    userid: str,                     # User email or ID
    mfa_method: MfaMethod,           # MFA method (EMAIL, SMS, TOTP)
    phone_number: str | None = None  # Phone number (required for SMS)
) -> dict[str, Any]
MfaMethod Enum:
from hawcx_oauth_client.delegation import MfaMethod

MfaMethod.EMAIL  # Email-based MFA
MfaMethod.SMS    # SMS-based MFA
MfaMethod.TOTP   # Time-based One-Time Password
Returns:
{
    'session_id': str,               # Session ID for verification
    'status': str,
    'message': str | None
}
Example:
from hawcx_oauth_client.delegation import HawcxDelegationClient, MfaMethod

client = HawcxDelegationClient.from_env()

result = client.initiate_mfa_change(
    userid='[email protected]',
    mfa_method=MfaMethod.SMS,
    phone_number='+15551234567'
)

print(f'Session ID: {result["session_id"]}')

verify_mfa_change()

Verify OTP and complete MFA setup or change. Parameters:
def verify_mfa_change(
    self,
    userid: str,                     # User email or ID
    session_id: str,                 # Session ID from initiate_mfa_change
    otp: str                         # One-time password (6 digits)
) -> dict[str, Any]
Returns:
{
    'status': str,                   # 'success' or 'failed'
    'message': str | None
}
Example:
result = client.verify_mfa_change(
    userid='[email protected]',
    session_id=session_from_initiate,
    otp='123456'
)

if result['status'] == 'success':
    print('MFA setup complete!')

get_user_credentials()

Retrieve the current MFA method and credentials for a user. Parameters:
def get_user_credentials(self, userid: str) -> dict[str, Any]
Returns:
{
    'mfa_method': str,               # Current MFA method
    'status': str,
    'message': str | None
}
Example:
creds = client.get_user_credentials('[email protected]')
print(f'Current MFA: {creds["mfa_method"]}')

Exception Types

OAuthExchangeError

Thrown when OAuth code exchange fails.
from hawcx_oauth_client import OAuthExchangeError, exchange_code_for_claims

try:
    claims = exchange_code_for_claims(...)
except OAuthExchangeError as error:
    print(f'OAuth error: {error}')

JWTVerificationError

Thrown when JWT signature or claims validation fails.
from hawcx_oauth_client import JWTVerificationError

try:
    payload = verify_jwt(token, public_key)
except JWTVerificationError as error:
    print(f'JWT verification failed: {error}')

InvalidPublicKeyError

Thrown when the provided public key is malformed.
from hawcx_oauth_client import InvalidPublicKeyError, exchange_code_for_claims

try:
    claims = exchange_code_for_claims(
        code=code,
        oauth_token_url=url,
        client_id=client_id,
        public_key='invalid-key'
    )
except InvalidPublicKeyError as error:
    print(f'Key format error: {error}')

Type Hints

Full type annotations are available:
from hawcx_oauth_client import (
    exchange_code_for_claims,
    verify_jwt,
    OAuthExchangeError,
    JWTVerificationError,
    InvalidPublicKeyError
)
from hawcx_oauth_client.delegation import (
    HawcxDelegationClient,
    MfaMethod
)

Flask Integration

The SDK provides optional Flask decorators:
from flask import Flask, request, session
from hawcx_oauth_client.flask_helpers import require_hawcx_auth

app = Flask(__name__)

@app.route('/protected', methods=['GET'])
@require_hawcx_auth
def protected_endpoint():
    """Only accessible to authenticated users"""
    user_id = session.get('hawcx_user_id')
    return {'user_id': user_id}

Best Practices

1. Environment Variable Management

from hawcx_oauth_client.delegation import HawcxDelegationClient
import os

# Use environment variables for all sensitive keys
client = HawcxDelegationClient.from_env()

2. Error Handling

from hawcx_oauth_client import (
    exchange_code_for_claims,
    OAuthExchangeError,
    JWTVerificationError
)
import os

try:
    claims = exchange_code_for_claims(
        code=auth_code,
        oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
        client_id=os.getenv('OAUTH_CLIENT_ID'),
        public_key=os.getenv('OAUTH_PUBLIC_KEY')
    )
except OAuthExchangeError as e:
    # Handle OAuth errors (invalid code, expired, etc.)
    print(f'OAuth error: {e}')
except JWTVerificationError as e:
    # Handle JWT validation errors
    print(f'JWT error: {e}')

3. Clock Skew Tolerance

For distributed systems, add clock skew tolerance:
claims = exchange_code_for_claims(
    code=auth_code,
    oauth_token_url=token_endpoint,
    client_id=client_id,
    public_key=public_key,
    leeway=10  # Allow 10 seconds of clock skew
)

4. MFA Flow

from hawcx_oauth_client.delegation import MfaMethod

# Step 1: Initiate
mfa_session = client.initiate_mfa_change(
    userid='[email protected]',
    mfa_method=MfaMethod.SMS,
    phone_number='+15551234567'
)

# Step 2: User receives OTP via SMS

# Step 3: Verify
result = client.verify_mfa_change(
    userid='[email protected]',
    session_id=mfa_session['session_id'],
    otp=user_provided_otp
)

if result['status'] == 'success':
    # MFA setup complete
    pass