Hawcx OAuth Client SDK for Python
Add passwordless authentication to your Python backend. Exchange OAuth codes for verified user claims and manage MFA.Installation
Copy
pip install hawcx-oauth-client
Quick Start
OAuth Code Exchange
The most common flow: exchange a Hawcx authorization code for verified user claims.Copy
from hawcx_oauth_client import exchange_code_for_claims
import os
@app.route('/auth/callback', methods=['POST'])
def auth_callback():
try:
# Exchange the authorization code for verified claims
claims = exchange_code_for_claims(
code=request.form['code'],
oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=os.getenv('OAUTH_CLIENT_ID'),
public_key=os.getenv('OAUTH_PUBLIC_KEY'),
# Optional (recommended for production):
code_verifier=session.get('pkce_verifier'), # PKCE support
audience='my-app', # Validate 'aud' claim
issuer='https://oauth.example.com', # Validate 'iss' claim
leeway=10 # Clock skew tolerance
)
# Use the verified claims
user_id = claims['sub']
email = claims['email']
# Create user session or JWT
session['user_id'] = user_id
session['email'] = email
return jsonify({'success': True, 'userId': user_id})
except Exception as error:
app.logger.error(f'Authentication failed: {error}')
return jsonify({'error': 'Authentication failed'}), 401
Hawcx Delegation Client (MFA Setup)
For advanced use cases like setting up MFA for users programmatically:Copy
from hawcx_oauth_client.delegation import HawcxDelegationClient, MfaMethod
import os
# Initialize the delegation client with your signing/encryption keys
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'),
base_url='https://ceasar-api.hawcx.com',
sp_id=os.getenv('OAUTH_CLIENT_ID')
)
# Initiate MFA setup (Email, SMS, or TOTP)
result = client.initiate_mfa_change(
userid="[email protected]",
mfa_method=MfaMethod.SMS, # Type-safe enum!
phone_number="+15551234567"
)
# Verify OTP and complete MFA setup
client.verify_mfa_change(
userid="[email protected]",
session_id=result['session_id'],
otp="123456"
)
# Get user credentials
creds = client.get_user_credentials("[email protected]")
print(f"MFA method: {creds.get('mfa_method')}")
Configuration
Environment Variables
For OAuth Code Exchange:Copy
OAUTH_TOKEN_ENDPOINT="https://oauth.hawcx.com/token"
OAUTH_CLIENT_ID="your-client-id"
OAUTH_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
OAUTH_ISSUER="https://oauth.hawcx.com" # Optional but recommended
OAUTH_AUDIENCE="your-client-id" # Optional but recommended
Copy
SP_ED25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
SP_X25519_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..."
IDP_ED25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
IDP_X25519_PUBLIC_KEY_PEM="-----BEGIN PUBLIC KEY-----..."
OAUTH_CLIENT_ID="your-client-id"
Public Key Formats
The SDK accepts public keys in multiple formats: From environment variable (recommended):Copy
from os import getenv
claims = exchange_code_for_claims(
code=code,
oauth_token_url=getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=getenv('OAUTH_CLIENT_ID'),
public_key=getenv('OAUTH_PUBLIC_KEY') # PEM string
)
Copy
from pathlib import Path
claims = exchange_code_for_claims(
code=code,
oauth_token_url=token_endpoint,
client_id=client_id,
public_key=Path('keys/public.pem') # Path object or string
)
Copy
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----
Error Handling
The SDK provides specific exceptions for different failure scenarios:Copy
from hawcx_oauth_client import (
exchange_code_for_claims,
OAuthExchangeError,
JWTVerificationError,
InvalidPublicKeyError
)
try:
claims = exchange_code_for_claims(
code=auth_code,
oauth_token_url=token_endpoint,
client_id=client_id,
public_key=public_key
)
except OAuthExchangeError as e:
# The authorization code may be invalid or expired
print(f'OAuth exchange failed: {e}')
except JWTVerificationError as e:
# The JWT signature or claims validation failed
print(f'JWT verification failed: {e}')
except InvalidPublicKeyError as e:
# The provided public key is malformed
print(f'Invalid public key: {e}')
Integration Examples
Flask
Copy
from flask import Flask, request, jsonify, session
from hawcx_oauth_client import exchange_code_for_claims
import os
import logging
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY')
logging.basicConfig(level=logging.INFO)
@app.route('/auth/callback', methods=['POST'])
def auth_callback():
"""Handle Hawcx OAuth callback"""
code = request.form.get('code')
if not code:
return jsonify({'error': 'Missing authorization code'}), 400
try:
claims = exchange_code_for_claims(
code=code,
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')
)
# Create user session
session['user_id'] = claims['sub']
session['email'] = claims.get('email')
return jsonify({'success': True, 'userId': claims['sub']})
except Exception as error:
app.logger.error(f'Authentication failed: {error}')
return jsonify({'error': 'Authentication failed'}), 401
@app.route('/auth/user', methods=['GET'])
def get_user():
"""Get current user from session"""
user_id = session.get('user_id')
if not user_id:
return jsonify({'error': 'Unauthorized'}), 401
return jsonify({'userId': user_id, 'email': session.get('email')})
if __name__ == '__main__':
app.run(debug=False, port=5000)
Django
Copy
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from hawcx_oauth_client import exchange_code_for_claims
import os
@require_http_methods(["POST"])
def auth_callback(request):
"""Handle Hawcx OAuth callback"""
code = request.POST.get('code')
if not code:
return JsonResponse({'error': 'Missing authorization code'}, status=400)
try:
claims = exchange_code_for_claims(
code=code,
oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=os.getenv('OAUTH_CLIENT_ID'),
public_key=os.getenv('OAUTH_PUBLIC_KEY')
)
# Create user session
request.session['user_id'] = claims['sub']
request.session['email'] = claims.get('email')
return JsonResponse({'success': True, 'userId': claims['sub']})
except Exception as error:
return JsonResponse({'error': 'Authentication failed'}, status=401)
@require_http_methods(["GET"])
def get_user(request):
"""Get current user from session"""
user_id = request.session.get('user_id')
if not user_id:
return JsonResponse({'error': 'Unauthorized'}, status=401)
return JsonResponse({
'userId': user_id,
'email': request.session.get('email')
})
FastAPI
Copy
from fastapi import FastAPI, Form, HTTPException
from fastapi.responses import JSONResponse
from hawcx_oauth_client import exchange_code_for_claims
import os
app = FastAPI()
@app.post("/auth/callback")
async def auth_callback(code: str = Form(...)):
"""Handle Hawcx OAuth callback"""
if not code:
raise HTTPException(status_code=400, detail="Missing authorization code")
try:
claims = exchange_code_for_claims(
code=code,
oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=os.getenv('OAUTH_CLIENT_ID'),
public_key=os.getenv('OAUTH_PUBLIC_KEY')
)
return JSONResponse({
'success': True,
'userId': claims['sub'],
'email': claims.get('email')
})
except Exception as error:
raise HTTPException(status_code=401, detail="Authentication failed")
PKCE Support
For enhanced security, especially in native or SPA applications, use PKCE:Copy
import hashlib
import base64
import secrets
from hawcx_oauth_client import exchange_code_for_claims
# On client: Generate code verifier
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')
# On backend: Exchange with verifier
claims = exchange_code_for_claims(
code=auth_code,
code_verifier=code_verifier, # Include the verifier from client session
oauth_token_url=os.getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=os.getenv('OAUTH_CLIENT_ID'),
public_key=os.getenv('OAUTH_PUBLIC_KEY')
)
Next Steps
- View the complete API reference for advanced features and detailed method signatures
- Set up MFA management for your users
- Join our developer community on Slack for support and updates
