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-client

Quick 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'}), 401

MFA 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_ID is the expected aud on issued id_tokens and is required by from_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 above

Integration 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'}), 401

Django

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.claims

See 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