django-azuread-token-validator


Namedjango-azuread-token-validator JSON
Version 0.1.10 PyPI version JSON
download
home_pageNone
SummaryDjango middleware to validate Azure AD JWT tokens and enrich requests with user data
upload_time2025-07-10 19:29:58
maintainerNone
docs_urlNone
authorMarlon Passos
requires_python>=3.7
licenseNone
keywords django azure ad jwt middleware authentication sso drf
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            
# django-azuread-token-validator (azvalidator)

Django middleware for validating JWT tokens issued by Azure AD and enriching the request object with additional user profile information. This middleware is designed exclusively for use with Django REST Framework (DRF) to securely and seamlessly protect API routes.

---

## Installation

You can install the package via pip directly from PyPI (or your private repository):

```bash
pip install django-azuread-token-validator
```

Or, if you prefer to install it from the local source code:

```bash
git clone https://github.com/MarlonPassos/django-azuread-token-validator.git
cd django-azuread-token-validator
pip install .
```

---

## Middleware Configuration

### 1. Add the middleware to your `settings.py`

Include the full path of the middleware in the `MIDDLEWARE` list:

```python
MIDDLEWARE = [
    # Default Django middlewares...
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',

    # Azure AD validation middleware
    'azvalidator.middleware.AzureADTokenValidatorMiddleware',  # adjust the path as needed

    # Other middlewares...
]
```

### 2. Environment variables for configuration

In `settings.py`, configure the following variables:

```python
# Azure AD authentication endpoint URL
AZURE_AD_URL = "https://login.microsoftonline.com"

# Azure AD JWKS endpoint URL (to fetch public verification keys) "https://login.microsoftonline.com/<tenant_id>/discovery/keys"
AZURE_AD_TENANT_ID = "<tenant_id>"

# Expected client_id  identifier in the JWT token (usually the audience or App ID URI)
AZURE_AD_CLIENT_ID = "api://<client_id>"

# JWKS cache timeout in seconds (default: 3600)
AZURE_AD_CACHE_TIMEOUT = 3600

# Enable or disable token signature verification (default: True)
AZURE_AD_VERIFY_SIGNATURE = True

# List of accepted JWT algorithms (default: ["RS256"])
AZURE_AD_ALGORITHMS = ["RS256"]

# Default username for Client Credentials tokens (app-to-app)
AZURE_AD_DEFAULT_APP_USERNAME = "app"

# Default role for Client Credentials tokens
AZURE_AD_DEFAULT_APP_ROLE = "AppRole"

# External service URL to fetch additional user information (optional)
# Configure client_credentials authentication needed
AZURE_AD_AUX_USERINFO_SERVICE_URL = "https://api.example.com/userinfo"

# Timeout for requests to the additional service in seconds (default: 10)
AZURE_AD_AUX_USERINFO_SERVICE_TIMEOUT = 10

# Time in seconds to cache the Azure AD JWKS (JSON Web Key Set) used for token signature validation. Default is 3600 seconds (1 hour)
AZURE_AD_JWK_CACHE_TIMEOUT = 3600  # 1 hora

# Time in seconds to cache the additional user information retrieved from the external service. Default is 3600 seconds (1 hour).
AZURE_AD_AUX_USERINFO_CACHE_TIMEOUT = 3600  # 1 hora

# Mapping between fields returned by the additional service and request attributes
# Format: {"service_field": "request_attribute_name"}
AZURE_AD_AUX_USERINFO_MAPPING = {
    "department": "azure_department",
    "department_number": "azure_department_number",
    "company": "azure_company",
    "employee_number": "azure_employee_role",
}


```

### 3. Usage in views

To enable token validation in a DRF view or viewset, set the attribute `azure_authentication = True` in the view class:

```python
from rest_framework import viewsets, routers
from rest_framework.response import Response


class DummyViewSet(viewsets.ViewSet):
    azure_authentication = True

    def list(self, request):
        return Response(
            {
                "user": getattr(request, "azure_username", None),
                "roles": getattr(request, "azure_roles", []),
                "email": getattr(request, "azure_email", None),
            }
        )
```

---

## Application Authentication in Azure AD

The package provides a utility function to authenticate applications in Azure AD using the `client_credentials` flow. This function is useful for scenarios where an application needs to communicate with another protected application.

### Function: `generate_app_azure_token`

The `generate_app_azure_token` function returns a valid access token for the application. The token is cached and automatically renewed upon expiration.

#### Example usage:

```python
from azvalidator.utils import generate_app_azure_token

# Get the access token
access_token = generate_app_azure_token()
print(f"Access token: {access_token}")
```
#### Cache Configuration (New)
To ensure proper caching of Azure AD tokens used by `generate_app_azure_token`, you must configure Django’s cache settings in your `settings.py`. For local development or testing, you can use the simple in-memory cache backend:
Ensure the following variables are configured correctly for the function to work:
```python 
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    }
}
```
#### Configuration for application authentication (client credentials)
```python
# Azure AD authentication endpoint URL
AZURE_AD_URL = "https://login.microsoftonline.com"

# Azure AD Tenant ID
AZURE_AD_TENANT_ID = "<tenant_id>"

# Grant type (must be "client_credentials")
AZURE_AD_APP_GRANT_TYPE = "client_credentials"

# Client ID registered in Azure AD
AZURE_AD_APP_CLIENT_ID = "<client_id>"

# Client secret registered in Azure AD
AZURE_AD_APP_CLIENT_SECRET = "<client_secret>"

# Required access scope
AZURE_AD_APP_SCOPE = "https://graph.microsoft.com/.default"
```

---

## External Service for Additional User Information

### Purpose

The middleware can enrich the `request` object with extra data fetched from an external service, in addition to the basic Azure AD token data.

### How it works

- After validating the token, the middleware makes an HTTP GET request to:

  ```
  {AZURE_AD_AUX_USERINFO_SERVICE_URL}/{username}/
  ```

- If configured, it sends a Bearer token via the `Authorization` header for authentication.

- The external service must return a JSON response with the additional user data.

### External service specifications

- **Endpoint:** REST, accepting GET requests at the URL `/username/`
- **Authentication:** Optional via Bearer Token
- **Response:** JSON with fields containing user data (example below)

Example JSON response:

```json
{
  "department": "Information Technology",
  "department_number": "123",
  "company": "MyCompany",
  "employee_number": "456789",
  "other_field": "value"
}
```

### Mapping data to the `request`

The dictionary `AZURE_AD_AUX_USERINFO_MAPPING` defines which fields from the JSON response will be added to the `request` object and under which names, for example:

```python
AZURE_AD_AUX_USERINFO_MAPPING = {
    "department": "azure_department",
    "department_number": "azure_department_number",
    "company": "azure_company",
    "employee_number": "azure_employee_role",
}
```

### Timeout and Resilience

- Request timeout is configurable via `AZURE_AD_AUX_USERINFO_SERVICE_TIMEOUT` (default: 10 seconds).
- If the service is unavailable or an error occurs, the middleware logs the failure and continues the request without additional data.

---


## Tests

The package's test suite covers the main middleware scenarios to ensure robustness:

- Validation of a valid token with the `preferred_username` claim.
- Rejection of a token without the `preferred_username` claim (returns HTTP 401).
- Rejection of a token with an invalid signature.
- Rejection of an expired token.
- Handling of Client Credentials tokens (app-to-app).
- Enrichment of the request with additional information via an external service.
- Appropriate responses with status 401 or 500 based on detected errors.

### Basic test example:

```bash
cd tests && pip install -r requirements.txt && python manage.py test
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "django-azuread-token-validator",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": null,
    "keywords": "django azure ad jwt middleware authentication sso drf",
    "author": "Marlon Passos",
    "author_email": "marlonjbpassos@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/06/2f/c6b6f77cc25807a686b378babf32fda7dc4a7040e648fc0620da48149370/django_azuread_token_validator-0.1.10.tar.gz",
    "platform": null,
    "description": "\n# django-azuread-token-validator (azvalidator)\n\nDjango middleware for validating JWT tokens issued by Azure AD and enriching the request object with additional user profile information. This middleware is designed exclusively for use with Django REST Framework (DRF) to securely and seamlessly protect API routes.\n\n---\n\n## Installation\n\nYou can install the package via pip directly from PyPI (or your private repository):\n\n```bash\npip install django-azuread-token-validator\n```\n\nOr, if you prefer to install it from the local source code:\n\n```bash\ngit clone https://github.com/MarlonPassos/django-azuread-token-validator.git\ncd django-azuread-token-validator\npip install .\n```\n\n---\n\n## Middleware Configuration\n\n### 1. Add the middleware to your `settings.py`\n\nInclude the full path of the middleware in the `MIDDLEWARE` list:\n\n```python\nMIDDLEWARE = [\n    # Default Django middlewares...\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n\n    # Azure AD validation middleware\n    'azvalidator.middleware.AzureADTokenValidatorMiddleware',  # adjust the path as needed\n\n    # Other middlewares...\n]\n```\n\n### 2. Environment variables for configuration\n\nIn `settings.py`, configure the following variables:\n\n```python\n# Azure AD authentication endpoint URL\nAZURE_AD_URL = \"https://login.microsoftonline.com\"\n\n# Azure AD JWKS endpoint URL (to fetch public verification keys) \"https://login.microsoftonline.com/<tenant_id>/discovery/keys\"\nAZURE_AD_TENANT_ID = \"<tenant_id>\"\n\n# Expected client_id  identifier in the JWT token (usually the audience or App ID URI)\nAZURE_AD_CLIENT_ID = \"api://<client_id>\"\n\n# JWKS cache timeout in seconds (default: 3600)\nAZURE_AD_CACHE_TIMEOUT = 3600\n\n# Enable or disable token signature verification (default: True)\nAZURE_AD_VERIFY_SIGNATURE = True\n\n# List of accepted JWT algorithms (default: [\"RS256\"])\nAZURE_AD_ALGORITHMS = [\"RS256\"]\n\n# Default username for Client Credentials tokens (app-to-app)\nAZURE_AD_DEFAULT_APP_USERNAME = \"app\"\n\n# Default role for Client Credentials tokens\nAZURE_AD_DEFAULT_APP_ROLE = \"AppRole\"\n\n# External service URL to fetch additional user information (optional)\n# Configure client_credentials authentication needed\nAZURE_AD_AUX_USERINFO_SERVICE_URL = \"https://api.example.com/userinfo\"\n\n# Timeout for requests to the additional service in seconds (default: 10)\nAZURE_AD_AUX_USERINFO_SERVICE_TIMEOUT = 10\n\n# Time in seconds to cache the Azure AD JWKS (JSON Web Key Set) used for token signature validation. Default is 3600 seconds (1 hour)\nAZURE_AD_JWK_CACHE_TIMEOUT = 3600  # 1 hora\n\n# Time in seconds to cache the additional user information retrieved from the external service. Default is 3600 seconds (1 hour).\nAZURE_AD_AUX_USERINFO_CACHE_TIMEOUT = 3600  # 1 hora\n\n# Mapping between fields returned by the additional service and request attributes\n# Format: {\"service_field\": \"request_attribute_name\"}\nAZURE_AD_AUX_USERINFO_MAPPING = {\n    \"department\": \"azure_department\",\n    \"department_number\": \"azure_department_number\",\n    \"company\": \"azure_company\",\n    \"employee_number\": \"azure_employee_role\",\n}\n\n\n```\n\n### 3. Usage in views\n\nTo enable token validation in a DRF view or viewset, set the attribute `azure_authentication = True` in the view class:\n\n```python\nfrom rest_framework import viewsets, routers\nfrom rest_framework.response import Response\n\n\nclass DummyViewSet(viewsets.ViewSet):\n    azure_authentication = True\n\n    def list(self, request):\n        return Response(\n            {\n                \"user\": getattr(request, \"azure_username\", None),\n                \"roles\": getattr(request, \"azure_roles\", []),\n                \"email\": getattr(request, \"azure_email\", None),\n            }\n        )\n```\n\n---\n\n## Application Authentication in Azure AD\n\nThe package provides a utility function to authenticate applications in Azure AD using the `client_credentials` flow. This function is useful for scenarios where an application needs to communicate with another protected application.\n\n### Function: `generate_app_azure_token`\n\nThe `generate_app_azure_token` function returns a valid access token for the application. The token is cached and automatically renewed upon expiration.\n\n#### Example usage:\n\n```python\nfrom azvalidator.utils import generate_app_azure_token\n\n# Get the access token\naccess_token = generate_app_azure_token()\nprint(f\"Access token: {access_token}\")\n```\n#### Cache Configuration (New)\nTo ensure proper caching of Azure AD tokens used by `generate_app_azure_token`, you must configure Django\u2019s cache settings in your `settings.py`. For local development or testing, you can use the simple in-memory cache backend:\nEnsure the following variables are configured correctly for the function to work:\n```python \nCACHES = {\n    'default': {\n        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',\n    }\n}\n```\n#### Configuration for application authentication (client credentials)\n```python\n# Azure AD authentication endpoint URL\nAZURE_AD_URL = \"https://login.microsoftonline.com\"\n\n# Azure AD Tenant ID\nAZURE_AD_TENANT_ID = \"<tenant_id>\"\n\n# Grant type (must be \"client_credentials\")\nAZURE_AD_APP_GRANT_TYPE = \"client_credentials\"\n\n# Client ID registered in Azure AD\nAZURE_AD_APP_CLIENT_ID = \"<client_id>\"\n\n# Client secret registered in Azure AD\nAZURE_AD_APP_CLIENT_SECRET = \"<client_secret>\"\n\n# Required access scope\nAZURE_AD_APP_SCOPE = \"https://graph.microsoft.com/.default\"\n```\n\n---\n\n## External Service for Additional User Information\n\n### Purpose\n\nThe middleware can enrich the `request` object with extra data fetched from an external service, in addition to the basic Azure AD token data.\n\n### How it works\n\n- After validating the token, the middleware makes an HTTP GET request to:\n\n  ```\n  {AZURE_AD_AUX_USERINFO_SERVICE_URL}/{username}/\n  ```\n\n- If configured, it sends a Bearer token via the `Authorization` header for authentication.\n\n- The external service must return a JSON response with the additional user data.\n\n### External service specifications\n\n- **Endpoint:** REST, accepting GET requests at the URL `/username/`\n- **Authentication:** Optional via Bearer Token\n- **Response:** JSON with fields containing user data (example below)\n\nExample JSON response:\n\n```json\n{\n  \"department\": \"Information Technology\",\n  \"department_number\": \"123\",\n  \"company\": \"MyCompany\",\n  \"employee_number\": \"456789\",\n  \"other_field\": \"value\"\n}\n```\n\n### Mapping data to the `request`\n\nThe dictionary `AZURE_AD_AUX_USERINFO_MAPPING` defines which fields from the JSON response will be added to the `request` object and under which names, for example:\n\n```python\nAZURE_AD_AUX_USERINFO_MAPPING = {\n    \"department\": \"azure_department\",\n    \"department_number\": \"azure_department_number\",\n    \"company\": \"azure_company\",\n    \"employee_number\": \"azure_employee_role\",\n}\n```\n\n### Timeout and Resilience\n\n- Request timeout is configurable via `AZURE_AD_AUX_USERINFO_SERVICE_TIMEOUT` (default: 10 seconds).\n- If the service is unavailable or an error occurs, the middleware logs the failure and continues the request without additional data.\n\n---\n\n\n## Tests\n\nThe package's test suite covers the main middleware scenarios to ensure robustness:\n\n- Validation of a valid token with the `preferred_username` claim.\n- Rejection of a token without the `preferred_username` claim (returns HTTP 401).\n- Rejection of a token with an invalid signature.\n- Rejection of an expired token.\n- Handling of Client Credentials tokens (app-to-app).\n- Enrichment of the request with additional information via an external service.\n- Appropriate responses with status 401 or 500 based on detected errors.\n\n### Basic test example:\n\n```bash\ncd tests && pip install -r requirements.txt && python manage.py test\n```\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Django middleware to validate Azure AD JWT tokens and enrich requests with user data",
    "version": "0.1.10",
    "project_urls": null,
    "split_keywords": [
        "django",
        "azure",
        "ad",
        "jwt",
        "middleware",
        "authentication",
        "sso",
        "drf"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "89d965ef9a3365fa27e8bd7027732f8ccc69e434c33235a6356ac476f4d118b9",
                "md5": "96e75e62276181ab0734b89c7aa0fd6d",
                "sha256": "2f4c4bd36896abc3aeabc2e5f5efee8eb021511bc473918326bee6158f813adb"
            },
            "downloads": -1,
            "filename": "django_azuread_token_validator-0.1.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "96e75e62276181ab0734b89c7aa0fd6d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 9691,
            "upload_time": "2025-07-10T19:29:58",
            "upload_time_iso_8601": "2025-07-10T19:29:58.011314Z",
            "url": "https://files.pythonhosted.org/packages/89/d9/65ef9a3365fa27e8bd7027732f8ccc69e434c33235a6356ac476f4d118b9/django_azuread_token_validator-0.1.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "062fc6b6f77cc25807a686b378babf32fda7dc4a7040e648fc0620da48149370",
                "md5": "92f88c4257b8db74eaf17c964c16baf2",
                "sha256": "ca165d2183ee8bd965ae8f5a32014a651be6a02a7912656ef48256cb878a4452"
            },
            "downloads": -1,
            "filename": "django_azuread_token_validator-0.1.10.tar.gz",
            "has_sig": false,
            "md5_digest": "92f88c4257b8db74eaf17c964c16baf2",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 11352,
            "upload_time": "2025-07-10T19:29:58",
            "upload_time_iso_8601": "2025-07-10T19:29:58.933100Z",
            "url": "https://files.pythonhosted.org/packages/06/2f/c6b6f77cc25807a686b378babf32fda7dc4a7040e648fc0620da48149370/django_azuread_token_validator-0.1.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-10 19:29:58",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "django-azuread-token-validator"
}
        
Elapsed time: 0.62705s