Skip to content

About

Reference implementation of enterprise SSO with Django + AWS Cognito. Covers OIDC provider integration (Azure AD, Okta), role mapping, webhook-based deprovisioning, and multi-tenant architecture.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Repository files navigation

Django Cognito SSO Demo

A reference implementation of enterprise SSO authentication with Django and AWS Cognito, supporting multiple OIDC providers (Azure AD, Okta). This repository accompanies a series of technical articles on building production-grade SSO flows.

Article Series

# Article
— From Django Library to AWS Cognito: My Journey Building Enterprise SSO
1 How to Configure AWS Cognito for SSO: A Step-by-Step Guide
2 Azure Single Sign-On (SSO) Setup: A Step-by-Step Guide
3 Okta Single Sign-On (SSO) Setup: A Step-by-Step Guide
4 How to Set Up SSO Authentication in Your App's Admin Panel
5 How to Test SSO Authentication with Cognito and OIDC Providers

Overview

The project demonstrates how to:

  • Integrate AWS Cognito as an identity broker for external OIDC providers
  • Support multiple SSO providers per tenant (Azure AD, Okta, generic OIDC)
  • Map external identity provider roles to application-level roles
  • Handle provider lifecycle events (user deprovisioning) via webhooks
  • Structure a multi-app Django project with a clean layered architecture

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                          Client / Browser                           │
└─────────────────┬───────────────────────────────┬───────────────────┘
                  │                               │
                  ▼                               ▼
    GET /api/oidc/check_config_exists    GET /api/oidc/cognito/callback/
    (discover SSO config by email)        (exchange code for tokens)
                  │                               │
                  ▼                               ▼
┌─────────────────────────────────────────────────────────────────────┐
│                          Django (this repo)                         │
│                                                                     │
│  ┌──────────────┐   ┌───────────────┐   ┌──────────────────────┐    │
│  │  SSOClient   │   │   Cognito     │   │  Notification        │    │
│  │  Service     │   │   Backend     │   │  Service             │    │
│  │  (sso.py)    │   │  (backends.py)│   │  (notification.py)   │    │
│  └──────────────┘   └───────────────┘   └──────────────────────┘    │
│          │                  │                      │                │
│          └──────────────────┴──────────────────────┘                │
│                             │                                       │
│                    ┌────────▼────────┐                              │
│                    │   PostgreSQL    │                              │
│                    │   SSOAccount    │                              │
│                    │   Config +      │                              │
│                    │   Members       │                              │
│                    └─────────────────┘                              │
└─────────────────────────────────────────────────────────────────────┘
                  │                               │
                  ▼                               ▼
         AWS Cognito User Pool          Azure AD / Okta
         (identity broker)              (external OIDC providers)

Application structure

apps/
├── accounts/          # Account (tenant) and membership management
│   ├── models.py      # Account, Member
│   ├── permissions.py # DRF permission classes
│   ├── repositories/  # Data access layer
│   └── services/      # RightsHolderService
├── common/            # Shared base classes and utilities
│   ├── exceptions.py
│   ├── repositories/base.py
│   └── services/jwt.py
├── oidc_auth/         # Core SSO module
│   ├── backends.py    # Django authentication backend (Cognito)
│   ├── models.py      # SSOAccountConfig, NotificationProviderConfig
│   ├── tasks.py       # Celery task: refresh Azure subscriptions
│   ├── api/
│   │   ├── authentication.py  # OAuth2 callback handler
│   │   ├── cognito.py         # Cognito setup/teardown endpoints
│   │   ├── notification.py    # Azure / Okta webhook endpoints
│   │   └── views.py           # SSO config lookup, logout
│   ├── repositories/
│   │   └── sso_account_config.py
│   └── services/
│       ├── sso.py             # Core SSO orchestration
│       └── notification.py    # Webhook event processing
└── users/             # Custom user model + Cognito helper
    ├── models.py
    └── cognito.py     # AWSCognitoHelper (boto3 wrapper)

SSO Authentication Flow

1. Browser                  Django                      Cognito
   |                           |                           |
   |-- GET /check_config ----->|                           |
   |<-- {client_id, ...} ------|                           |
   |                           |                           |
   |-- Redirect to Cognito ------------------------------->|
   |                           |              (user authenticates
   |                           |               with Azure/Okta/etc.)
   |<-- Redirect with ?code=...&state=<client_id> ---------|
   |                           |                           |
   |-- GET /cognito/callback ->|                           |
   |                           |-- POST /oauth2/token ---->|
   |                           |<-- {id_token, ...} -------|
   |                           |                           |
   |                           | decode id_token
   |                           | validate SSO config
   |                           | map roles -> Member.role
   |                           | create/update User + Member
   |                           |
   |<-- {access_token, ...} ---|

API Endpoints

Method Endpoint Auth Description
GET /api/oidc/check_config_exists/<email>/ None Look up SSO configs for an email domain
GET /api/oidc/cognito/callback/ None Handle OAuth2 authorization code callback
GET/POST /api/oidc/cognito/logout/ None Clear session
POST /api/oidc/cognito/setup IsAuthenticated + RightsHolderOrManagementGroup Create Cognito identity provider + app client
DELETE /api/oidc/cognito/delete IsAuthenticated + RightsHolder Remove Cognito SSO infrastructure
POST /api/oidc/notifications/azure/ None (Azure-signed) Receive Azure AD change notifications
POST /api/oidc/notifications/okta/ OKTA_HOOK_AUTHORIZATION header Receive Okta event hooks

Data Models

Account

Represents a tenant organisation. Can be of type RIGHTS_HOLDER, PARTNER, or MANAGEMENT_GROUP.

Member

Join table linking User to Account with a role (ADMIN or MANAGER) and is_active flag used for soft-deletion during deprovisioning events.

SSOAccountConfig

One active SSO configuration per account. Stores the encrypted Cognito client_id / client_secret, allowed email domains, OIDC provider groups, and the issuer URL.

NotificationProviderConfig

Azure-specific configuration attached to an SSOAccountConfig. Stores the credentials used to call the Microsoft Graph API and the webhook URL for change notifications.

Getting Started

Prerequisites

  • Docker + Docker Compose
  • AWS account with a Cognito User Pool configured

1. Clone and configure

git clone https://github.com/soldatov-ss/django-cognito-sso-demo.git
cd django-cognito-sso-demo
cp .env.sample .env

Edit .env with your values:

# Database
POSTGRES_DB=django_db
POSTGRES_USER=django_user
POSTGRES_PASSWORD=django_password
DATABASE_URL=postgresql://django_user:django_password@db:5432/django_db

# Django
DEBUG=True
FIELD_ENCRYPTION_KEY=<generate: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())">
FRONT_END_ROOT=http://localhost:8000

# AWS Cognito (required for SSO)
COGNITO_AWS_REGION=us-east-1
COGNITO_USER_POOL=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=your-app-client-id
COGNITO_DOMAIN=https://your-domain.auth.us-east-1.amazoncognito.com
USE_COGNITO=True
COGNITO_STRICT_SYNC=True

2. Start the services

docker compose up --build

This runs migrations and starts the development server at http://localhost:8000.

3. Run tests

docker compose run --rm web uv run pytest

Or inside a running container:

uv run pytest

AWS Cognito Setup

The demo assumes a Cognito User Pool configured with:

  1. Custom attributes on the User Pool:

    • custom:groups — groups from the external OIDC provider
    • custom:roles — roles from the external OIDC provider
  2. An App Client with:

    • Authorization code grant flow enabled
    • Callback URL pointing to /api/oidc/cognito/callback/
    • Read/write access to the custom attributes above
  3. External identity providers (Azure AD, Okta, etc.) are provisioned programmatically via POST /api/oidc/cognito/setup once a tenant's SSO configuration is created.

Key Implementation Notes

RightsHolderService

get_rights_holder_from_request is intentionally simplified for this demo. In a production system, the rights holder would typically be resolved from a partnership or tenant context injected by middleware. Here it resolves the caller's first active RIGHTS_HOLDER account directly from the request user — replace this with your own tenant resolution logic.

Role mapping

External provider roles (admin, manager) are mapped to application MemberRoles in SSOClientService._map_sso_roles_to_app_role. Extend this mapping to match your provider's group/role naming.

Soft deletion

When Azure AD or Okta reports that a user has been removed from a synced group or deleted from the directory, Member.soft_delete() is called — it sets is_active=False without removing the database record. User.active_accounts() filters by this flag throughout the codebase.

Encrypted fields

SSOAccountConfig.encrypted_client_id and encrypted_client_secret use django-encrypted-model-fields. The FIELD_ENCRYPTION_KEY environment variable must be a valid Fernet key. A default development key is provided in settings.py — never use it in production.

Development

Code style

uv run ruff check .
uv run ruff format .

Pre-commit hooks

pip install pre-commit
pre-commit install

License

This package is licensed under the MIT License. See the LICENSE file for more details.

About

Reference implementation of enterprise SSO with Django + AWS Cognito. Covers OIDC provider integration (Azure AD, Okta), role mapping, webhook-based deprovisioning, and multi-tenant architecture.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Contributors

Languages