Python Backend SDK Quickstart
Integrate Hawcx OAuth authentication in your Python backend
Add passwordless authentication to your Python backend. Exchange authorization codes for verified user claims and manage MFA.
Installation
pip install hawcx-oauth-clientQuick Start
OAuth Code Exchange
The most common flow: exchange authCode + codeVerifier for verified user claims.
from hawcx_oauth_client import HawcxOAuth
from flask import request, jsonify
import os
oauth = HawcxOAuth.from_issuer(
os.environ['HAWCX_BASE_URL'], # issuer URL (Base URL from Admin Console)
os.environ['HAWCX_CONFIG_ID'],
os.environ['HAWCX_CLIENT_ID'], # expected `aud` on issued id_tokens
)
@app.route('/exchange', methods=['POST'])
def exchange():
try:
data = request.json
result = oauth.exchange_code(data['authCode'], data['codeVerifier'])
claims = result.claims
# Create user session or JWT
return jsonify({
'success': True,
'userId': claims['sub'],
'email': claims.get('email')
})
except Exception as error:
app.logger.error(f'Authentication failed: {error}')
return jsonify({'error': 'Authentication failed'}), 401MFA Management (Step-Up)
For advanced use cases like setting up or changing a user's MFA from your backend, use StepUpClient.
Recommended for management auth: private_key_jwt
For management / step-up calls, prefer StepUpClient.with_private_key_jwt (Path A) — it reuses your OIDC private_key_jwt signing key (one key, RFC 7523). A legacy StepUpClient.from_secret_key(...) path also exists (see the reference).
StepUpClient with private_key_jwt:
import os
from hawcx_oauth_client import StepUpClient
# Reuses the same Ed25519 key you registered for OIDC login (one key, RFC 7523)
client = StepUpClient.with_private_key_jwt(
oidc_signing_key=os.environ["HAWCX_OIDC_PRIVATE_KEY_PEM"], # Ed25519 PEM or bytes
kid=os.environ["HAWCX_PRIVATE_KEY_KID"], # key id from Admin Console
client_id=os.environ["HAWCX_CLIENT_ID"],
base_url=os.environ["HAWCX_BASE_URL"],
config_id=os.environ["HAWCX_CONFIG_ID"],
)
# Begin a step-up flow (purposes: "change_mfa_method", "change_phone_number")
result = client.start_token(
user_id="user@example.com",
purpose="change_mfa_method",
new_mfa_method="email_otp",
)
# Finalize once the user completes the MFA challenge (receipt comes from your frontend)
client.consume_receipt(receipt="receipt-from-frontend-after-mfa")See Step-Up Client (Management API) for management_request and full method details.
Configuration
Environment Variables
For OAuth Code Exchange:
# All three from Admin Console → Project Settings (environment-specific)
HAWCX_BASE_URL="<Base URL from Admin Console>" # issuer URL for discovery
HAWCX_CONFIG_ID="<Config ID from Admin Console>"
HAWCX_CLIENT_ID="<Client ID from Admin Console>"Where to find these values: Open the Hawcx Admin Console, go to Project Settings, and copy the Base URL, Config ID, and Client ID. All three are environment-specific — the base URL has no universal default.
HAWCX_CLIENT_IDis the expectedaudon issued id_tokens and is required byfrom_issuer.
For MFA management (StepUpClient with private_key_jwt):
HAWCX_OIDC_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----..." # Ed25519 PEM (your OIDC signing key)
HAWCX_PRIVATE_KEY_KID="<key id from Admin Console>"
# reuses HAWCX_BASE_URL / HAWCX_CONFIG_ID / HAWCX_CLIENT_ID from aboveIntegration Examples
Flask
from flask import Flask, request, jsonify, session
from hawcx_oauth_client import HawcxOAuth
import os
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY')
oauth = HawcxOAuth.from_issuer(
os.environ['HAWCX_BASE_URL'], # issuer URL (Base URL from Admin Console)
os.environ['HAWCX_CONFIG_ID'],
os.environ['HAWCX_CLIENT_ID'],
)
@app.route('/exchange', methods=['POST'])
def exchange():
data = request.json
auth_code = data.get('authCode')
code_verifier = data.get('codeVerifier')
if not auth_code or not code_verifier:
return jsonify({'error': 'Missing authCode or codeVerifier'}), 400
try:
result = oauth.exchange_code(auth_code, code_verifier)
claims = result.claims
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'}), 401Django
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from hawcx_oauth_client import HawcxOAuth
import os
oauth = HawcxOAuth.from_issuer(
os.environ['HAWCX_BASE_URL'], # issuer URL (Base URL from Admin Console)
os.environ['HAWCX_CONFIG_ID'],
os.environ['HAWCX_CLIENT_ID'],
)
@require_http_methods(["POST"])
def exchange(request):
auth_code = request.POST.get('authCode')
code_verifier = request.POST.get('codeVerifier')
if not auth_code or not code_verifier:
return JsonResponse({'error': 'Missing authCode or codeVerifier'}, status=400)
try:
result = oauth.exchange_code(auth_code, code_verifier)
claims = result.claims
request.session['user_id'] = claims['sub']
request.session['email'] = claims.get('email')
return JsonResponse({'success': True, 'userId': claims['sub']})
except Exception:
return JsonResponse({'error': 'Authentication failed'}, status=401)FastAPI
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from hawcx_oauth_client import HawcxOAuth
import os
app = FastAPI()
oauth = HawcxOAuth.from_issuer(
os.environ['HAWCX_BASE_URL'], # issuer URL (Base URL from Admin Console)
os.environ['HAWCX_CONFIG_ID'],
os.environ['HAWCX_CLIENT_ID'],
)
@app.post('/exchange')
async def exchange(payload: dict):
auth_code = payload.get('authCode')
code_verifier = payload.get('codeVerifier')
if not auth_code or not code_verifier:
return JSONResponse({'error': 'Missing authCode or codeVerifier'}, status_code=400)
try:
result = oauth.exchange_code(auth_code, code_verifier)
claims = result.claims
return JSONResponse({
'success': True,
'userId': claims['sub'],
'email': claims.get('email')
})
except Exception:
return JSONResponse({'error': 'Authentication failed'}, status_code=401)PKCE Support
codeVerifier is required for the exchange. Store it on the client and send it alongside authCode when your backend calls exchange_code().
Confidential clients (private_key_jwt)
Optional — most apps stay on PKCE. For a backend that should authenticate with a
key it holds, switch the project to Enhanced mode in the Admin Console, then
attach the Ed25519 private JWK with with_client_assertion (discovery mode):
from hawcx_oauth_client.oauth import HawcxOAuth, ClientAssertionSigner
oauth = HawcxOAuth.from_issuer(issuer, config_id, client_id).with_client_assertion(
ClientAssertionSigner.ed25519_from_jwk(private_key_jwk)
)
result = oauth.exchange_code(auth_code, code_verifier) # assertion attached
claims = result.claimsSee Confidential Clients (private_key_jwt)
for the Admin Console setup and a no-SDK (stock JWT library) example.
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
Last updated on