Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# Copilot Instructions for Identity Library

## Repository Overview

This is the **Identity** library repository, a Python authentication/authorization library that provides high-level APIs for Microsoft identity platform integration. The library is built on top of Microsoft's MSAL Python and is designed specifically for web applications.

### Key Facts
- **Language**: Python (3.8-3.12 supported)
- **Type**: Authentication/Authorization library
- **Target**: Web applications (Django, Flask, Quart)
- **Current Version**: 0.11.0
- **License**: MIT
- **Package Name**: `identity` (available on PyPI)

### Purpose
The library provides simplified APIs for:
- Microsoft Entra ID authentication
- Microsoft Entra External ID
- Azure AD B2C integration
- Web app sign-in/sign-out with automatic session renewal
- Access token acquisition for web APIs
- Incremental consent handling

## Build Instructions & Environment Setup

### Python Environment Setup
**ALWAYS configure the Python environment first before any operations:**
```bash
# The repository uses tox for environment management
# Configure Python environment using the configure_python_environment tool
# Or use tox which creates isolated environments automatically
```

### Installation & Dependencies
```bash
# 1. Install dependencies (REQUIRED before any other operations)
pip install -r requirements.txt

# This installs the package in editable mode with ALL dependencies including:
# - Django, Flask, Quart web frameworks
# - MSAL, requests for authentication
# - pytest, pytest-asyncio for testing
```


### Coding Style

For existing source code, do not make modification only for coding style.
For newly added code snippets or lines that need to be changed in the current task,
follow these guidelines:

- Follow PEP 8 guidelines for Python code.
- Use 4 spaces per indentation level.
- Keep lines ideally under 79 characters, don't exceed 100 characters,
unless the line contains a long url which we don't want to split into two lines.
- Use docstrings to describe public classes and methods.
- Include type hints for function signatures, using Python 3.9 syntax.

### Running Tests
```bash
# Method 1: Direct pytest (after installing requirements.txt)
pytest

# Method 2: Using tox (RECOMMENDED - creates isolated environment)
tox -e py3

# Tests are located in tests/ directory:
# - test_django.py: Django integration tests
# - test_flask.py: Flask integration tests
# - test_quart.py: Quart integration tests

# Expected results: 9 tests pass with some deprecation warnings
```

### Building Documentation
```bash
# Install documentation dependencies
pip install -r docs/requirements.txt

# Build docs using Sphinx (from repository root)
sphinx-build docs docs/_build

# OR using tox (RECOMMENDED)
tox -e docs

# Documentation outputs to docs/_build/
```

### Type Checking
```bash
# Run type checking with mypy (NOTE: currently has known issues)
tox -e type

# Known issues: Missing type stubs for external libraries
# These errors are expected and do not block development
```

### Building Package
```bash
# Clean previous builds
rm -rf build dist

# Install build dependencies
pip install build

# Build source and wheel distributions
python -m build --sdist --wheel --outdir dist/ .
```

## Project Layout & Architecture

### Root Directory Structure
```
├── identity/ # Main package source code
│ ├── __init__.py # Package initialization
│ ├── version.py # Version information (__version__ = "0.11.0")
│ ├── web.py # Core web framework-agnostic Auth class
│ ├── django.py # Django-specific integrations
│ ├── flask.py # Flask-specific integrations
│ ├── quart.py # Quart-specific integrations
│ ├── pallet.py # Shared utilities for Flask/Quart
│ └── templates/identity/ # HTML templates for login/error pages
├── tests/ # Test suite
├── docs/ # Sphinx documentation
├── .github/workflows/ # CI/CD pipeline
├── requirements.txt # Development dependencies
├── setup.cfg # Package configuration
├── pyproject.toml # Build system configuration
├── tox.ini # Testing environments
└── README.md # User documentation
```

### Key Configuration Files
- **setup.cfg**: Main package metadata and dependencies
- **pyproject.toml**: Build system (setuptools)
- **tox.ini**: Test environments and commands
- **requirements.txt**: Development dependencies with ALL web frameworks
- **.github/workflows/python-package.yml**: CI/CD pipeline

### Core Architecture
1. **web.py**: Contains the main `Auth` class that is web framework-agnostic
2. **Framework-specific modules**: django.py, flask.py, quart.py provide decorators and helpers
3. **pallet.py**: Shared code between Flask and Quart (both use Pallets ecosystem)
4. **Templates**: Built-in HTML templates for authentication flows

### Known Issues & TODOs
Based on code analysis, there are several TODOs in web.py:
- Line 231: "TODO: Reject a re-log-in with a different account?"
- Line 340: "TODO: Where shall token cache come from?"
- Line 433: "TODO: Support custom domain" for B2C

## CI/CD & Validation Pipeline

### GitHub Actions Workflow
Located in `.github/workflows/python-package.yml`:

**Triggers**: Push to any branch, labeled PRs to dev branch

**Test Matrix**: Python 3.8, 3.9, 3.10, 3.11, 3.12 on Ubuntu

**Pipeline Steps**:
1. Install dependencies (pip, flake8, pytest, requirements.txt)
2. ~~Linting with flake8~~ (currently commented out)
3. Run pytest test suite
4. **Release Pipeline** (conditional):
- Triggers on tags or release-* branches
- Builds source and wheel distributions
- Publishes to TestPyPI (release branches) or PyPI (tags)

### Pre-commit Validation
When making changes, ensure:
1. **Tests pass**: `tox -e py3` or `pytest`
2. **Documentation builds**: `tox -e docs`
3. **Type checking** (optional due to known issues): `tox -e type`

### Dependencies by Framework
The package supports conditional installation:
- **Django**: `pip install "identity[django]"`
- **Flask**: `pip install "identity[flask]"`
- **Quart**: `pip install "identity[quart]"`
- **All (for docs)**: `pip install "identity[all_for_docs]"`

### Working with the Repository

**ALWAYS follow this sequence for development:**
1. Configure Python environment (already done if using tox)
2. Install requirements: `pip install -r requirements.txt`
3. Make your changes
4. Run tests: `tox -e py3` or `pytest`
5. Build docs if needed: `tox -e docs`
6. For releases: Update version in `identity/version.py`

**Package Installation Patterns**:
- Development: `pip install -r requirements.txt` (editable install with all frameworks)
- Production: `pip install "identity[framework]"` where framework is django/flask/quart

**Trust these instructions** - they have been validated against the actual repository structure and build process. Only search for additional information if these instructions are incomplete or incorrect for your specific task.

### Framework-Specific Notes
- **Django**: Uses Django's session system, provides `@login_required` decorator
- **Flask**: Requires Flask-Session, provides `@login_required` decorator
- **Quart**: Async framework, uses Quart-Session, provides `@login_required` decorator
- **All frameworks**: Share the core `Auth` class from web.py for authentication logic

The library is designed to minimize Microsoft identity platform integration complexity while supporting multiple Python web frameworks through a unified API.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
# See also https://endoflife.date/python
python-version: [3.8, 3.9, "3.10", 3.11, 3.12]
python-version: [3.9, "3.10", 3.11, 3.12, 3.13]

steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ you can read its HTML source code, and find the how-to instructions there.
</tr>

<tr>
<th>Web App Calls a web API</th>
<th>Your Web App Calls a Web API on behalf of the user</th>
<td colspan=4>

This library supports:
Expand All @@ -118,7 +118,22 @@ They are demonstrated by the same samples above.
</tr>

<tr>
<th>Web API Calls another web API (On-behalf-of)</th>
<th>Your Web API protected by an access token</th>
<td colspan=4>

By using this library, it will automatically emit
HTTP 401 or 403 error when the access token is absent or invalid.

* Sample written in ![Django](https://raw.githubusercontent.com/rayluo/identity/dev/docs/django.webp)(Coming soon)
* [Sample written in ![Flask](https://raw.githubusercontent.com/rayluo/identity/dev/docs/flask.webp)](https://github.com/rayluo/python-webapi-flask.git)
* Need support for more web frameworks?
[Upvote existing feature request or create a new one](https://github.com/rayluo/identity/issues)

</td>
</tr>

<tr>
<th>Your Web API Calls another web API on behalf of the user (OBO)</th>
<td colspan=4>

In roadmap.
Expand Down
30 changes: 30 additions & 0 deletions docs/app-vs-api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. note::

Web Application (a.k.a. website) and Web API are different scenarios,
both supported by the same Identity Auth component with different decorators.
Make sure you are using the right decorator for your scenario.

.. list-table::
:header-rows: 1
:widths: 20 40 40

* - Aspects
- Web Application (a.k.a. website)
- Web API
* - **Definition**
- A complete solution that users interact with directly through their browsers.
- A back-end system that provides data (typically in JSON format) to front-end or other system.
* - **Functionality**
- | - Users interact with views (HTML user interfaces) and data.
| - Users sign in and establish their sessions.
- | - Does not return views (in HTML); only provides data.
| - Other systems (clients) hit its endpoints.
| - Clients presents a token to access your API.
| - Each request has no session. They are stateless.
* - **Identity component**
- Same ``Auth`` class
- Same ``Auth`` class
* - **Decorator to use**
- ``@auth.login_required``
- ``@auth.authorization_required``

77 changes: 77 additions & 0 deletions docs/django-webapi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Identity for a Django Web API
=============================

.. include:: app-vs-api.rst

Prerequisite
------------

Create a hello world web project in Django.

You can use
`Django's own tutorial, part 1 <https://docs.djangoproject.com/en/5.0/intro/tutorial01/>`_
as a reference. What we need are basically these steps:

#. ``django-admin startproject mysite``
#. ``python manage.py migrate`` (Optinoal if your project does not use a database)
#. ``python manage.py runserver localhost:5000``

#. Now, add a new `mysite/views.py` file with an `index` view to your project.
For now, it can simply return a "hello world" page to any visitor::

from django.http import JsonResponse
def index(request):
return JsonResponse({"message": "Hello, world!"})

Configuration
-------------

#. Install dependency by ``pip install identity[django]``

#. Create an instance of the :py:class:`identity.django.Auth` object,
and assign it to a global variable inside your ``settings.py``::

import os
from identity.django import Auth
AUTH = Auth(
client_id=os.getenv('CLIENT_ID'),
...=..., # See below on how to feed in the authority url parameter
)

.. include:: auth.rst


Django Web API protected by an access token
-------------------------------------------

#. In your web project's ``views.py``, decorate some views with the
:py:func:`identity.django.Auth.authorization_required` decorator::

from django.conf import settings

@settings.AUTH.authorization_required(expected_scopes={
"your_scope_1": "api://your_client_id/your_scope_1",
"your_scope_2": "api://your_client_id/your_scope_2",
})
def index(request, *, context):
claims = context['claims']
# The user is uniquely identified by claims['sub'] or claims["oid"],
# claims['tid'] and/or claims['iss'].
return JsonResponse(
{"message": f"Data for {claims['sub']}@{claims['tid']}"}
)


All of the content above are demonstrated in
`this django web app sample <https://github.com/Azure-Samples/ms-identity-python-webapi-django>`_.


API for Django web projects
---------------------------

.. autoclass:: identity.django.Auth
:members:
:inherited-members:

.. automethod:: __init__

10 changes: 6 additions & 4 deletions docs/django.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Identity for Django
===================
Identity for a Django Web App
=============================

.. include:: app-vs-api.rst

Prerequisite
------------
Expand All @@ -15,15 +17,15 @@ as a reference. What we need are basically these steps:
#. ``python manage.py runserver localhost:5000``
You must use a port matching your redirect_uri that you registered.

#. Now, add an `index` view to your project.
#. Now, add a new `mysite/views.py` file with an `index` view to your project.
For now, it can simply return a "hello world" page to any visitor::

from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. Everyone can read this line.")

Configuration
---------------------------------
-------------

#. Install dependency by ``pip install identity[django]``

Expand Down
Loading