Skip to main content

A Django authentication middleware package for integrating with OUA SSO server

Project description

Organization Unified Access Authentication

A plug-and-play Django authentication package for integrating with the Organisation SSO backend. Supports JWKS-based JWT verification, tenant hierarchy (Country → Company → Branch), OAuth2 authorization code flow with PKCE, and hierarchical DRF permission classes.

Installation

pip install oua-auth

Quick Start

# settings.py
INSTALLED_APPS = [..., "oua_auth"]

OUA_SSO_URL = "https://sso.org.com"
OUA_CLIENT_ID = "your-client-id"
OUA_CLIENT_SECRET = "your-client-secret"
OUA_REDIRECT_URI = "https://yourapp.com/sso/callback/"
OUA_AUDIENCE = "organisation-services"
OUA_APPLICATION_CODE = "APPLICATION_CODE"

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "oua_auth.authentication.OUAJWTAuthentication",
    ],
}

MIDDLEWARE = [
    ...,
    "oua_auth.middleware.OUAAuthMiddleware",
    "oua_auth.middleware.TenantScopeMiddleware",
    "oua_auth.security_middleware.SecurityHeadersMiddleware",
]

# urls.py
urlpatterns = [
    path("sso/", include("oua_auth.urls")),
    ...
]

Run migrations:

python manage.py migrate oua_auth

Configuration

Required Settings

Setting Description
OUA_SSO_URL Organisation SSO server base URL
OUA_CLIENT_ID OAuth client ID

Auto-Discovered (OIDC)

These are fetched automatically from /.well-known/openid-configuration but can be overridden:

Setting Default
OUA_ISSUER From OIDC discovery
OUA_JWKS_URI {OUA_SSO_URL}/api/v1/oauth/keys
OUA_AUDIENCE organisation-services

OAuth Flow Settings

Setting Description
OUA_CLIENT_SECRET OAuth client secret (for confidential clients)
OUA_REDIRECT_URI Callback URL in your Django app
OUA_SCOPES OAuth scopes (default: openid profile email)
OUA_APPLICATION_CODE App code for role filtering (e.g. APPLICATION_CODE)
OUA_LOGIN_REDIRECT_URL Where to redirect after login (default: /)

Breaking changes in 0.8.0

  • Identity and authorisation claims are no longer read from the JWT body. Starting in 0.8.0, oua_auth resolves identity (email, preferred_username, name) from GET /userinfo and role + tenant (sso_role, country_id, company_id, branch_ids) from POST /introspect. The JWT body carries only sub, jti, typ, scp, azp, exp, iat, iss, aud, email_verified (v2 slim-token contract). Deployments must configure introspection credentials (OUA_INTROSPECTION_CLIENT_ID / OUA_INTROSPECTION_CLIENT_SECRET) or authentication fails closed with HTTP 503.
  • SSO unreachable → 503, not 401. When the SSO authority is unreachable and the local cache is cold, the SDK returns 503 + Retry-After — never a forced logout or 401. Stale cached claims are served within the grace window (OUA_REMOTE_CLAIMS_GRACE, default 300 s beyond TTL).
  • Seven new OUA_* settings: OUA_INTROSPECT_ENDPOINT, OUA_INTROSPECTION_CLIENT_ID, OUA_INTROSPECTION_CLIENT_SECRET, OUA_REMOTE_CLAIMS_CACHE_TTL, OUA_REMOTE_CLAIMS_GRACE, OUA_REMOTE_CLAIMS_TIMEOUT, OUA_FETCH_USERINFO. See docs/configuration.md for defaults and details.

Breaking changes in 0.6.0

  • Admin promotion fails closed (M-9). If both OUA_TRUSTED_ADMIN_DOMAINS and OUA_TRUSTED_ADMIN_EMAILS are unset, no SSO SUPER_ADMIN is auto-granted Django is_staff/is_superuser (previous versions promoted everyone in that case). Set at least one to retain auto-promotion; otherwise provision admins manually. A startup WARNING is logged when both are unset in production-mode config.
  • Session-restore verifies the token (C-1). Browser/session requests now re-verify the session access token via JWKS; an invalid token is cleared and the request proceeds unauthenticated.
  • Static-PEM fallback is disabled by default (H-4). When OUA_ALLOW_STATIC_KEY_FALLBACK is unset or False, a JWKS resolution failure results in AuthenticationFailed (401) instead of silently retrying against OUA_PUBLIC_KEY. Clients that relied on the implicit fallback must set OUA_ALLOW_STATIC_KEY_FALLBACK = True explicitly — but the recommended fix is to ensure JWKS reachability.

Security Settings

Setting Default Description
OUA_PUBLIC_KEY None Static PEM fallback key (PEM string). Only consulted when OUA_ALLOW_STATIC_KEY_FALLBACK is True.
OUA_ALLOW_STATIC_KEY_FALLBACK False Security gate (H-4). When False (default), a JWKS resolution failure raises AuthenticationFailed immediately (fail-closed). Set to True only in environments where JWKS is genuinely unreachable and OUA_PUBLIC_KEY is a current, trusted key. A WARNING is logged every time the fallback fires. The correct long-term fix is ensuring JWKS reachability.
OUA_TOKEN_LEEWAY 0 Clock skew tolerance in seconds
OUA_JWKS_REFETCH_COOLDOWN 60 Minimum seconds between forced JWKS refetches on signature failure (H-3). A refetch is skipped — and InvalidSignatureError is raised immediately — if one already fired within this window.
OUA_TRUSTED_ADMIN_DOMAINS [] Domains auto-granted Django admin (case-insensitive). Empty = no auto-promotion (fail-closed).
OUA_TRUSTED_ADMIN_EMAILS [] Emails auto-granted Django admin. Empty = no auto-promotion (fail-closed).
OUA_MAX_AUTH_FAILURES 5 Failures before rate limiting
OUA_AUTH_FAILURE_WINDOW 300 Rate limit window in seconds
OUA_ALLOWED_DOMAINS [] Only these email domains can authenticate
OUA_RESTRICTED_DOMAINS [] These email domains are blocked
OUA_MAX_SUSPICIOUS_ACTIVITIES 3 Threshold for account locking

Authentication Flow

API Authentication (Bearer Token)

For API clients that already have a JWT access token from the SSO:

Authorization: Bearer <access-token>

The middleware validates the JWT via JWKS, provisions/updates the local Django user, and sets:

  • request.user — authenticated Django user
  • request.oua_claims — decoded JWT payload
  • request.oua_tenant — dict with country_id, company_id, branch_id, sso_role, role_level
  • request.tenant_scopeTenantScope dataclass (set by TenantScopeMiddleware)

Browser Login (OAuth2 + PKCE)

For web apps that need to redirect users to the SSO login page:

  1. User visits a protected page
  2. @sso_login_required redirects to /sso/login/
  3. OUA generates PKCE challenge and redirects to SSO authorize endpoint
  4. User authenticates on SSO (with optional 2FA)
  5. SSO redirects back to /sso/callback/ with an authorization code
  6. OUA exchanges the code for tokens, creates a Django session
  7. User is redirected to the original page

SSO Role Hierarchy

The SSO uses a hierarchical role model:

Role Level Scope
SUPER_ADMIN 5 Platform-wide
COUNTRY_ADMIN 4 Country-wide
COMPANY_ADMIN 3 Company-wide
BRANCH_ADMIN 2 Branch-level
USER 1 Basic access

DRF Permission Classes

from oua_auth.permissions import HasSSORole, HasTenantAccess, IsSSOAuthenticated

class OrderViewSet(viewsets.ModelViewSet):
    # Require at least COMPANY_ADMIN role
    permission_classes = [HasSSORole("COMPANY_ADMIN")]

    def get_queryset(self):
        # Auto-filter by tenant scope
        return Order.objects.for_tenant(self.request.tenant_scope)

class BranchDetailView(APIView):
    # Check tenant access on object level
    permission_classes = [IsSSOAuthenticated, HasTenantAccess]

View Decorators

from oua_auth.decorators import sso_login_required, require_sso_role

@sso_login_required
def dashboard(request):
    return render(request, "dashboard.html")

@require_sso_role("BRANCH_ADMIN")
def admin_panel(request):
    return render(request, "admin.html")

Tenant-Scoped Models

from oua_auth.models import TenantMixin

class Order(TenantMixin):
    description = models.TextField()
    amount = models.DecimalField(max_digits=10, decimal_places=2)

# In a view — automatic tenant filtering:
orders = Order.objects.for_tenant(request.tenant_scope)

# Manual tenant assignment on create:
Order.objects.create(
    description="New order",
    amount=100.00,
    country_id=request.tenant_scope.country_id,
    company_id=request.tenant_scope.company_id,
    branch_id=request.tenant_scope.branch_id,
)

Token Blacklisting

from oua_auth.authentication import OUAJWTAuthentication

# Revoke a token
OUAJWTAuthentication.revoke_token(
    token=request.oua_token,
    blacklisted_by=request.user.email,
    reason="User logout",
)

Clean up expired tokens:

python manage.py clean_expired_tokens

Security Features

  • JWKS-based JWT verification with automatic key rotation retry
  • Token type validation — rejects refresh tokens used as access tokens
  • Tenant hierarchy — Country → Company → Branch scoping
  • Hierarchical role modelSUPER_ADMIN > COUNTRY_ADMIN > COMPANY_ADMIN > BRANCH_ADMIN > USER
  • Rate limiting — distributed via Django cache framework
  • Token blacklisting — persistent DB + in-memory fallback
  • Account locking — after configurable suspicious activity threshold
  • Input sanitization — XSS prevention via lxml
  • Security headers middleware — CSP, HSTS, X-Frame-Options, etc.
  • Structured logging — JSON output with sensitive data redaction

Testing

pip install -e ".[test]"
python -m pytest
python -m pytest --cov=oua_auth --cov-report=term-missing

License

MIT License — see the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oua_auth-0.9.0.tar.gz (127.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oua_auth-0.9.0-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file oua_auth-0.9.0.tar.gz.

File metadata

  • Download URL: oua_auth-0.9.0.tar.gz
  • Upload date:
  • Size: 127.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for oua_auth-0.9.0.tar.gz
Algorithm Hash digest
SHA256 463dd538081327897000b8f6e8de61146a27575839fc675f2d49cbca21aa74fb
MD5 91e539cb99ee16d07fc2639c256010cf
BLAKE2b-256 e7e82f5ee2bfda5621d9903e290293e7a672ddefe85e0e6f765b1ae6712493ff

See more details on using hashes here.

File details

Details for the file oua_auth-0.9.0-py3-none-any.whl.

File metadata

  • Download URL: oua_auth-0.9.0-py3-none-any.whl
  • Upload date:
  • Size: 52.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for oua_auth-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a80f868dca87dbb126134def2bb44760d9fbdeb0a08a4aa6b0a9c7db556c95c1
MD5 f9d1e2d522f5facad0ff57790dbea50c
BLAKE2b-256 a78e09285ad424b2aaaa06600e2de8d7f0b31c72d9048691db4b43613b9206f4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page