-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[FC-0118] docs: add ADR for standardizing authentication patterns #38301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Faraz32123
merged 12 commits into
docs/ADRs-axim_api_improvements
from
docs/ADR-standardize_authentication_patterns_and_security_scheme_usage
Jun 19, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
28c4912
docs: add ADR for standardizing authentication patterns
33f24dd
docs: resolve confusion & update the ADR based on OEP-0042
f60d12e
docs: support multiple valid auth schemes & deprecate BearerAuthentic…
b554c2a
docs: change wording for decisions a bit.
b6a6b9d
docs: add real examples in accordance with our updated decisions
9dacd49
docs: sync ADR with edx-drf-extensions issue 284
f019508
docs: make doc more explicit & address comments
6d0c84d
docs: move Bearer auth depr plan out of ADR
916563e
docs: add a pointer file in oauth_dispatch for this ADR
7b66fe4
docs: make decision more clear
f5cbaa8
docs: make authentication_classes usage more clearer
6c35624
docs: adress the comment related to session authentication
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| Standardize Authentication Patterns and Security Schemes | ||
| ======================================================== | ||
|
|
||
| :Status: Proposed | ||
| :Date: 2026-04-07 | ||
| :Deciders: Open edX Platform / API Working Group | ||
| :Technical Story: Open edX REST API Standards - Consistent authentication patterns and security scheme usage | ||
|
|
||
| Context | ||
| ======= | ||
|
|
||
| Open edX APIs have inconsistent authentication patterns and security scheme implementations: | ||
|
|
||
| * Multiple authentication mechanisms are enabled globally but not consistently applied | ||
| * OAuth2 and JWT are not separate mechanisms DOT issues JWTs as OAuth2 tokens, | ||
| validated by ``JwtAuthentication``. The deprecated ``BearerAuthentication`` | ||
| handles old Bearer tokens and must not be confused with this. | ||
| * Security scheme declarations don't match actual authentication behavior | ||
| * External integrators cannot reliably predict which authentication method to use | ||
| * Internal APIs mix authentication mechanisms without clear patterns | ||
|
|
||
| This inconsistency creates confusion for: | ||
| - External developers determining which auth method to implement | ||
| - Internal teams maintaining consistent authentication patterns | ||
| - Security reviews and compliance assessments | ||
| - Automated tools expecting predictable authentication | ||
|
|
||
| The codebase has two JWT issuance paths, both validated by ``JwtAuthentication``: | ||
|
|
||
| * ``create_jwt_token_dict()`` — wraps a DOT OAuth2 access token into a JWT (DB-backed, revocable, for external clients) | ||
| * ``create_jwt_for_user()`` — issues a JWT directly with no OAuth2 flow and no DB row (non-revocable, for internal service communication) | ||
|
|
||
| Decision | ||
| ======== | ||
|
|
||
| 1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard | ||
| authentication mechanism for all DRF API endpoints that take user-authenticated | ||
| requests**, per `OEP-0042`_. This excludes admin views, ``/oauth2/access_token/``, | ||
| and HMAC/webhook endpoints, which have their own authentication mechanisms. | ||
| 2. **Both** ``JwtAuthentication`` **and** ``SessionAuthentication`` **are accepted | ||
| authentication schemes** which is the platform default. It is specifically the ``BearerAuthentication`` | ||
| family that is deprecated and MUST NOT be used. | ||
| 3. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are | ||
| deprecated and MUST NOT be used in new code** | ||
| 4. **``OAuth2Authentication`` and ``OAuth2AuthenticationAllowInactiveUser`` are | ||
| deprecated aliases for** ``BearerAuthentication`` **and MUST NOT be used in new code.** | ||
| 5. **New API endpoints MUST NOT set** ``authentication_classes`` **explicitly unless | ||
| deviating from platform defaults.** Platform defaults already supply ``JwtAuthentication`` | ||
| and ``SessionAuthentication``. Deviations (e.g. service-to-service JWT-only, or | ||
| inactive-user session access via ``SessionAuthenticationAllowInactiveUser``) must be | ||
| explicit and commented. | ||
| 6. **Existing APIs MUST be audited and updated to remove** ``BearerAuthentication``. | ||
|
|
||
| Implementation requirements: | ||
|
|
||
| * **New endpoints:** do not set ``authentication_classes`` explicitly. The platform | ||
| defaults (``DEFAULT_AUTHENTICATION_CLASSES`` in ``openedx/envs/common.py``) supply | ||
| ``DefaultJwtAuthentication`` and ``DefaultSessionAuthentication``, which is correct | ||
| for most endpoints. | ||
| * **Existing endpoints:** the migration goal is to remove ``BearerAuthentication`` | ||
| from the ``authentication_classes`` tuple. What remains depends on what the endpoint | ||
| currently has: | ||
|
|
||
| * Had ``(JwtAuthentication, BearerAuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser)`` | ||
| → becomes ``(JwtAuthentication, SessionAuthenticationAllowInactiveUser)`` | ||
| * Had ``(BearerAuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser)`` | ||
| → becomes ``(JwtAuthentication, SessionAuthenticationAllowInactiveUser)`` | ||
|
|
||
| * **Explicit exceptions** (require a comment justifying the deviation): | ||
|
|
||
| * Service-to-service endpoints that must exclude session auth: | ||
| ``authentication_classes = (JwtAuthentication,)`` | ||
|
|
||
| * ``OAuth2Authentication`` / ``OAuth2AuthenticationAllowInactiveUser``: remove once external repos migrate | ||
|
|
||
| Consequences | ||
| ============ | ||
|
|
||
| * Pros | ||
|
|
||
| * Clear, predictable authentication patterns for different API use cases | ||
| * Improved security through proper separation of auth mechanisms | ||
| * Aligns with OEP-0042 — removes deprecated ``BearerAuthentication`` from active use | ||
| * Easier integration for external developers (single standard: JWT) | ||
| * Simplified internal service communication (same ``JwtAuthentication`` class) | ||
| * Better browser experience (session-based auth) | ||
|
|
||
| * Cons / Costs | ||
|
|
||
| * Existing APIs need audit and potential refactoring to match patterns | ||
| * Teams need to understand and implement proper authentication choices (when to use JWT or session) | ||
| * External clients still using Bearer tokens must migrate to JWT | ||
| * Migration effort for services currently using mixed authentication | ||
| * Depending on configs, Bearer tokens last ~2 weeks; JWTs expire in ~1 hour — long-running jobs that reuse | ||
| a token without checking expiry will start failing after migration | ||
|
|
||
| Relevance in edx-platform | ||
| ========================= | ||
|
|
||
| * **OAuth2/DOT**: LMS uses Django OAuth Toolkit at ``/oauth2/`` | ||
| (``lms/urls.py``, ``openedx/core/djangoapps/oauth_dispatch``). Settings include | ||
| ``OAUTH2_PROVIDER_APPLICATION_MODEL``, ``OAUTH2_VALIDATOR_CLASS`` (e.g. | ||
| ``EdxOAuth2Validator``). DOT issues JWTs as access tokens via ``create_jwt_token_dict()``. | ||
| * **Current API auth**: ``openedx/core/lib/api/view_utils.view_auth_classes`` | ||
| configures both **JWT** and **Bearer** (deprecated) and session across 49+ files: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # openedx/core/lib/api/view_utils.py (current — violates OEP-0042) | ||
| func_or_class.authentication_classes = ( | ||
| JwtAuthentication, | ||
| BearerAuthenticationAllowInactiveUser, # deprecated per OEP-0042 | ||
| SessionAuthenticationAllowInactiveUser | ||
| ) | ||
|
|
||
| * **Bearer auth**: ``openedx/core/lib/api/authentication.py`` implements | ||
| ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` using | ||
| ``oauth2_provider`` (DOT) for access token validation. This is the deprecated path. | ||
|
|
||
| Code examples (authentication patterns by use case) | ||
| =================================================== | ||
|
|
||
| * **Standard API — had JWT + Bearer + SessionAllowInactive (remove Bearer only):** | ||
|
|
||
| Example: ``lms/djangoapps/course_home_api/dates/views.py (DatesTabView)`` | ||
| (`permalink <https://github.com/openedx/openedx-platform/blob/be3fc121148587fb1da507519534063c89387091/lms/djangoapps/course_home_api/dates/views.py#L68-L72>`_) | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # Current state | ||
| authentication_classes = ( | ||
| JwtAuthentication, | ||
| BearerAuthenticationAllowInactiveUser, # to be removed per Decision #3 | ||
| SessionAuthenticationAllowInactiveUser, | ||
| ) | ||
|
|
||
| # Target state — remove BearerAuthentication; keep the rest unchanged. | ||
| authentication_classes = ( | ||
| JwtAuthentication, | ||
| SessionAuthenticationAllowInactiveUser, | ||
| ) | ||
|
feanil marked this conversation as resolved.
|
||
|
|
||
| * **MFE/Browser API — had Bearer + SessionAllowInactive but no JWT (add JWT, remove Bearer):** | ||
|
|
||
| Example: ``lms/djangoapps/teams/views.py (TeamsDashboardView)`` | ||
| (`permalink <https://github.com/openedx/openedx-platform/blob/be3fc121148587fb1da507519534063c89387091/lms/djangoapps/teams/views.py#L114-L122>`_) | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| # Current state | ||
| authentication_classes = ( | ||
| BearerAuthenticationAllowInactiveUser, # to be removed per Decision #3 | ||
| SessionAuthenticationAllowInactiveUser, | ||
| ) | ||
|
|
||
| # Target state — add JwtAuthentication per Decision #1; remove BearerAuthentication. | ||
| authentication_classes = ( | ||
| JwtAuthentication, | ||
| SessionAuthenticationAllowInactiveUser, | ||
| ) | ||
|
|
||
| Implementation Notes | ||
| ==================== | ||
|
|
||
| * The platform default in ``openedx/envs/common.py`` (``DEFAULT_AUTHENTICATION_CLASSES``) | ||
| supplies ``DefaultJwtAuthentication`` and ``DefaultSessionAuthentication`` (standard | ||
| ``SessionAuthentication``, which blocks inactive users via session). New endpoints should | ||
| rely on this default rather than duplicating it explicitly. | ||
| * ``JwtAuthentication`` does **not** check ``user.is_active`` — it allows inactive users by | ||
| default. ``SessionAuthenticationAllowInactiveUser`` similarly skips the active-user check. | ||
| Endpoints that need to enforce active-user status should use a permission class rather | ||
| than an authentication class. | ||
| * Existing endpoints using ``SessionAuthenticationAllowInactiveUser`` must keep that class | ||
| explicitly when migrating — removing the override entirely would silently switch to | ||
| ``DefaultSessionAuthentication``, which blocks inactive users via session and changes behavior. | ||
| * The primary migration target is the ``view_auth_classes`` decorator — one change | ||
| removes ``BearerAuthentication`` from 49+ endpoints. | ||
| * Verify no active external clients are still sending Bearer tokens before | ||
| removing ``BearerAuthentication`` from any endpoint. | ||
| * ``JWT_AUTH_ADD_KID_HEADER`` toggle in ``openedx/core/djangoapps/oauth_dispatch/jwt.py`` | ||
| is past its removal date (target: 2024-04-20) — KID header should be made always-on | ||
| and the toggle removed. | ||
| * ``OAuth2Authentication`` / ``OAuth2AuthenticationAllowInactiveUser`` in | ||
| ``openedx/core/lib/api/authentication.py`` are deprecated aliases that exist only | ||
| to avoid breaking external repos — remove once those repos migrate to ``JwtAuthentication``. | ||
|
|
||
| Rollout Plan | ||
| ------------ | ||
|
|
||
| 1. Audit existing APIs and categorize — flag any using ``BearerAuthentication`` variants | ||
| 2. Remove overdue ``JWT_AUTH_ADD_KID_HEADER`` toggle — make KID header always-on | ||
|
|
||
| For all steps related to ``BearerAuthentication`` deprecation and removal (monitoring | ||
| active usage, marking deprecated in source, migrating external clients, token expiry | ||
| considerations, and third-party communication), see the | ||
| `DEPR: BearerAuthentication <https://github.com/openedx/edx-drf-extensions/issues/284>`_ ticket. | ||
|
|
||
| References | ||
| ========== | ||
|
|
||
| * `OEP-0042`_ — Open edX Authentication Best Practices (primary reference) | ||
| * `DEPR: BearerAuthentication <https://github.com/openedx/edx-drf-extensions/issues/284>`_ — Deprecation ticket for ``BearerAuthentication`` in edx-drf-extensions and edx-platform | ||
|
Faraz32123 marked this conversation as resolved.
|
||
| * Django REST Framework - Authentication and permissions | ||
| * Django OAuth Toolkit documentation | ||
| * Open edX Authentication Patterns Guide | ||
|
|
||
| .. _OEP-0042: https://docs.openedx.org/projects/openedx-proposals/en/latest/best-practices/oep-0042-bp-authentication.html | ||
13 changes: 13 additions & 0 deletions
13
...apps/oauth_dispatch/docs/decisions/0017-standardize-authentication-patterns.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| 17. Standardize Authentication Patterns and Security Schemes | ||
| ------------------------------------------------------------ | ||
|
|
||
| This decision has been documented in the platform-level ADR: | ||
|
|
||
| :doc:`docs/decisions/0034-unify-auth-oauth2-dot-v2` | ||
|
|
||
| See that document for: | ||
|
|
||
| * The decision to standardize on ``JwtAuthentication`` for all DRF user-authenticated APIs | ||
| * Deprecation of ``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` | ||
| * Code examples showing current and target states for existing views | ||
| * Rollout plan and reference to the `DEPR: BearerAuthentication <https://github.com/openedx/edx-drf-extensions/issues/284>`_ ticket |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.