Python Backend SDK Quickstart
Integrate Hawcx OAuth authentication in your Python backend
Hawcx OAuth Client SDK for Python
Add passwordless authentication to your Python backend. Exchange OAuth codes for verified user claims and manage MFA.
Installation
pip install hawcx-oauth-clientQuick Start
OAuth Code Exchange
The most common flow: exchange a Hawcx authorization code for verified user claims.
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'}), 401Hawcx Delegation Client (MFA Setup)
For advanced use cases like setting up MFA for users programmatically:
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://example.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:
OAUTH_TOKEN_ENDPOINT="https://example.hawcx.com/oauth2/token"
JWKS_URL="https://example.hawcx.com/.well-known/jwks.json
OAUTH_CLIENT_ID="your-client-id"
OAUTH_ISSUER="https://example.hawcx.com" # Optional but recommended
OAUTH_AUDIENCE="your-client-id" # Optional but recommendedFor Hawcx Delegation:
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 or via JWKS endpoint (recommended):
Using JWKS endpoint (recommended for production):
from os import getenv
claims = exchange_code_for_claims(
code=code,
oauth_token_url=getenv('OAUTH_TOKEN_ENDPOINT'),
client_id=getenv('OAUTH_CLIENT_ID'),
jwks_url=getenv('JWKS_URL') # Dynamic key fetching
)From environment variable (static key):
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
)From file path:
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
)Expected PEM format:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----Error Handling
The SDK provides specific exceptions for different failure scenarios:
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
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
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
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:
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