From a6a25d3b0bbe5f9af4cce4be0b5d374c4db8ac03 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Mon, 13 Apr 2026 13:40:21 +0500 Subject: [PATCH 01/10] docs: add ADR for standardizing authentication patterns --- .../0034-unify-auth-oauth2-dot-v2.rst | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/decisions/0034-unify-auth-oauth2-dot-v2.rst diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst new file mode 100644 index 000000000000..9b88d0f1e082 --- /dev/null +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -0,0 +1,151 @@ +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: + +* Some APIs use OAuth2, others use JWT, some use session authentication +* Multiple authentication mechanisms are enabled globally but not consistently applied +* 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 + +Decision +======== + +1. **OAuth2 via Django OAuth Toolkit (DOT) MUST be the standard authentication + mechanism for external API access** +2. **JWT authentication MUST be used only for internal service-to-service communication** +3. **Session authentication MUST be used only for browser-based UI interactions** +4. **All new APIs MUST follow these authentication patterns based on use case** +5. **Existing APIs MUST be audited and updated to follow consistent patterns** + +Implementation requirements: + +* External APIs (public, partner integrations): OAuth2 only +* Internal APIs (service-to-service): JWT only +* Browser-based APIs (UI interactions): Session only +* DRF authentication classes must match the intended use case +* No mixing of authentication mechanisms in single endpoints + +Consequences +============ + +* Pros + + * Clear, predictable authentication patterns for different API use cases + * Improved security through proper separation of auth mechanisms + * Easier integration for external developers (OAuth2 standard) + * Simplified internal service communication (JWT) + * 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 + * Some APIs may need to be split or redesigned for single auth mechanism + * Migration effort for services currently using mixed authentication + +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``). +* **Current API auth**: ``openedx/core/lib/api/view_utils.view_auth_classes`` + configures both **JWT** and **OAuth2** (Bearer) and session: + + .. code-block:: python + + # openedx/core/lib/api/view_utils.py (current) + func_or_class.authentication_classes = ( + JwtAuthentication, + BearerAuthenticationAllowInactiveUser, # OAuth2 Bearer via DOT + SessionAuthenticationAllowInactiveUser + ) + +* **Bearer auth**: ``openedx/core/lib/api/authentication.py`` implements + ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` using + ``oauth2_provider`` (DOT) for access token validation. + +Code examples (authentication patterns by use case) +=================================================== + +* **External API (OAuth2 only):** + +.. code-block:: python + + from rest_framework import viewsets + from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser + from rest_framework.permissions import IsAuthenticated + + class ExternalCourseViewSet(viewsets.ViewSet): + """External API for course data - OAuth2 authentication only.""" + authentication_classes = [BearerAuthenticationAllowInactiveUser] + permission_classes = [IsAuthenticated] + +* **Internal Service API (JWT only):** + +.. code-block:: python + + from rest_framework import viewsets + from openedx.core.lib.api.authentication import JwtAuthentication + from rest_framework.permissions import IsAuthenticated + + class InternalServiceViewSet(viewsets.ViewSet): + """Internal service-to-service API - JWT authentication only.""" + authentication_classes = [JwtAuthentication] + permission_classes = [IsAuthenticated] + +* **Browser-based API (Session only):** + +.. code-block:: python + + from rest_framework import viewsets + from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser + from rest_framework.permissions import IsAuthenticated + + class BrowserUIViewSet(viewsets.ViewSet): + """Browser UI API - Session authentication only.""" + authentication_classes = [SessionAuthenticationAllowInactiveUser] + permission_classes = [IsAuthenticated] + +Implementation Notes +==================== + +* Audit existing APIs to identify authentication pattern violations +* Create guidelines for determining appropriate authentication mechanism +* Update global authentication configurations to enforce patterns +* Provide migration guidance for APIs currently using mixed authentication +* Document authentication patterns for development teams + +Rollout Plan +------------ + +1. Audit existing APIs and categorize by intended use case (external/internal/browser) +2. Update global authentication configurations to prevent mixed authentication +3. Refactor high-priority APIs to follow single-authentication patterns +4. Create development guidelines and documentation for authentication choices +5. Implement automated testing to validate authentication pattern compliance +6. Monitor and enforce authentication patterns in code reviews + +References +========== + +* Django REST Framework - Authentication and permissions +* Django OAuth Toolkit documentation +* Open edX Authentication Patterns Guide From cfcf06dc2fad585423b48c5de5c2b4d194596c24 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Mon, 20 Apr 2026 15:15:24 +0500 Subject: [PATCH 02/10] docs: resolve confusion & update the ADR based on OEP-0042 --- .../0034-unify-auth-oauth2-dot-v2.rst | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 9b88d0f1e082..8128ec331bd4 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -11,8 +11,10 @@ Context Open edX APIs have inconsistent authentication patterns and security scheme implementations: -* Some APIs use OAuth2, others use JWT, some use session authentication * 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 @@ -23,20 +25,26 @@ This inconsistency creates confusion for: - 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. **OAuth2 via Django OAuth Toolkit (DOT) MUST be the standard authentication - mechanism for external API access** -2. **JWT authentication MUST be used only for internal service-to-service communication** +1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard + authentication mechanism for all API access** (external and internal), per `OEP-0042`_ +2. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are + deprecated and MUST NOT be used in new code** 3. **Session authentication MUST be used only for browser-based UI interactions** 4. **All new APIs MUST follow these authentication patterns based on use case** 5. **Existing APIs MUST be audited and updated to follow consistent patterns** Implementation requirements: -* External APIs (public, partner integrations): OAuth2 only -* Internal APIs (service-to-service): JWT only +* External APIs (public, partner integrations): ``JwtAuthentication`` only +* Internal APIs (service-to-service): ``JwtAuthentication`` only * Browser-based APIs (UI interactions): Session only * DRF authentication classes must match the intended use case * No mixing of authentication mechanisms in single endpoints @@ -48,15 +56,16 @@ Consequences * Clear, predictable authentication patterns for different API use cases * Improved security through proper separation of auth mechanisms - * Easier integration for external developers (OAuth2 standard) - * Simplified internal service communication (JWT) + * 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 - * Some APIs may need to be split or redesigned for single auth mechanism + * Teams need to understand and implement proper authentication choices(where to use JWT or session) + * External clients still using Bearer tokens must migrate to JWT * Migration effort for services currently using mixed authentication Relevance in edx-platform @@ -65,49 +74,49 @@ 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``). + ``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 **OAuth2** (Bearer) and session: + configures both **JWT** and **Bearer** (deprecated) and session across 49+ files: .. code-block:: python - # openedx/core/lib/api/view_utils.py (current) + # openedx/core/lib/api/view_utils.py (current — violates OEP-0042) func_or_class.authentication_classes = ( JwtAuthentication, - BearerAuthenticationAllowInactiveUser, # OAuth2 Bearer via DOT + 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. + ``oauth2_provider`` (DOT) for access token validation. This is the deprecated path. Code examples (authentication patterns by use case) =================================================== -* **External API (OAuth2 only):** +* **External API (OAuth2 JWT — clients obtain token via OAuth2 flow at** ``/oauth2/`` **):** .. code-block:: python from rest_framework import viewsets - from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser + from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from rest_framework.permissions import IsAuthenticated class ExternalCourseViewSet(viewsets.ViewSet): - """External API for course data - OAuth2 authentication only.""" - authentication_classes = [BearerAuthenticationAllowInactiveUser] + """External API — OAuth2 JWT authentication. Send as: Authorization: JWT """ + authentication_classes = [JwtAuthentication] permission_classes = [IsAuthenticated] -* **Internal Service API (JWT only):** +* **Internal Service API (JWT issued via** ``create_jwt_for_user()`` **):** .. code-block:: python from rest_framework import viewsets - from openedx.core.lib.api.authentication import JwtAuthentication + from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from rest_framework.permissions import IsAuthenticated class InternalServiceViewSet(viewsets.ViewSet): - """Internal service-to-service API - JWT authentication only.""" + """Internal service-to-service API — same JwtAuthentication, different token issuance.""" authentication_classes = [JwtAuthentication] permission_classes = [IsAuthenticated] @@ -128,8 +137,10 @@ Implementation Notes ==================== * Audit existing APIs to identify authentication pattern violations -* Create guidelines for determining appropriate authentication mechanism -* Update global authentication configurations to enforce patterns +* 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 * Provide migration guidance for APIs currently using mixed authentication * Document authentication patterns for development teams @@ -137,15 +148,19 @@ Rollout Plan ------------ 1. Audit existing APIs and categorize by intended use case (external/internal/browser) -2. Update global authentication configurations to prevent mixed authentication -3. Refactor high-priority APIs to follow single-authentication patterns -4. Create development guidelines and documentation for authentication choices -5. Implement automated testing to validate authentication pattern compliance -6. Monitor and enforce authentication patterns in code reviews +2. Check client metrics for active Bearer token usage +3. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` +4. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source +5. Refactor high-priority APIs to follow single-authentication patterns +6. Migrate external clients from Bearer tokens to JWT token flow +7. Remove ``BearerAuthentication`` classes once client migration is confirmed complete References ========== +* `OEP-0042`_ — Open edX Authentication Best Practices (primary reference) * 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 From 1c1401258b56efd13b1401fdeff81e1dbf9984ab Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Wed, 22 Apr 2026 18:52:30 +0500 Subject: [PATCH 03/10] docs: support multiple valid auth schemes & deprecate BearerAuthentication --- .../0034-unify-auth-oauth2-dot-v2.rst | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 8128ec331bd4..ee42c5ad7e54 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -35,19 +35,20 @@ Decision 1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard authentication mechanism for all API access** (external and internal), per `OEP-0042`_ -2. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are +2. **Session authentication MAY be supported alongside** ``JwtAuthentication`` + on any endpoint — this is the platform default and has no security implications +3. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are deprecated and MUST NOT be used in new code** -3. **Session authentication MUST be used only for browser-based UI interactions** -4. **All new APIs MUST follow these authentication patterns based on use case** -5. **Existing APIs MUST be audited and updated to follow consistent patterns** +4. **``OAuth2Authentication`` and ``OAuth2AuthenticationAllowInactiveUser`` are + deprecated aliases for** ``BearerAuthentication`` **and MUST NOT be used in new code** +5. **All new APIs MUST follow these authentication patterns based on use case** +6. **Existing APIs MUST be audited and updated to remove** ``BearerAuthentication`` Implementation requirements: -* External APIs (public, partner integrations): ``JwtAuthentication`` only -* Internal APIs (service-to-service): ``JwtAuthentication`` only -* Browser-based APIs (UI interactions): Session only -* DRF authentication classes must match the intended use case -* No mixing of authentication mechanisms in single endpoints +* All APIs: ``JwtAuthentication`` (+ ``SessionAuthentication`` where appropriate) +* ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser``: remove from all endpoints +* ``OAuth2Authentication`` / ``OAuth2AuthenticationAllowInactiveUser``: remove once external repos migrate Consequences ============ @@ -136,24 +137,30 @@ Code examples (authentication patterns by use case) Implementation Notes ==================== -* Audit existing APIs to identify authentication pattern violations +* Supporting both ``JwtAuthentication`` and ``SessionAuthentication`` on the same + endpoint is acceptable — this is already the platform default in + ``openedx/envs/common.py`` (``DEFAULT_AUTHENTICATION_CLASSES``) * 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 -* Provide migration guidance for APIs currently using mixed authentication -* Document authentication patterns for development teams +* ``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 by intended use case (external/internal/browser) +1. Audit existing APIs and categorize — flag any using ``BearerAuthentication`` variants 2. Check client metrics for active Bearer token usage 3. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` 4. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source -5. Refactor high-priority APIs to follow single-authentication patterns +5. Remove overdue ``JWT_AUTH_ADD_KID_HEADER`` toggle — make KID header always-on 6. Migrate external clients from Bearer tokens to JWT token flow -7. Remove ``BearerAuthentication`` classes once client migration is confirmed complete +7. Remove ``BearerAuthentication`` and its ``OAuth2Authentication`` aliases once migration is complete References ========== From f3abbe760565a212d0b80f3849032177afce657d Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Mon, 27 Apr 2026 12:51:43 +0500 Subject: [PATCH 04/10] docs: change wording for decisions a bit. --- docs/decisions/0034-unify-auth-oauth2-dot-v2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index ee42c5ad7e54..67baebd1d84f 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -34,9 +34,9 @@ Decision ======== 1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard - authentication mechanism for all API access** (external and internal), per `OEP-0042`_ + authentication mechanism for all API(external and internal) access**, per `OEP-0042`_ 2. **Session authentication MAY be supported alongside** ``JwtAuthentication`` - on any endpoint — this is the platform default and has no security implications + on any endpoint — this is the platform default and is acceptable. 3. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are deprecated and MUST NOT be used in new code** 4. **``OAuth2Authentication`` and ``OAuth2AuthenticationAllowInactiveUser`` are From b54c1d0f7c44abd1aa0d5833d82c390e613f5c32 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Tue, 5 May 2026 19:01:17 +0500 Subject: [PATCH 05/10] docs: add real examples in accordance with our updated decisions --- .../0034-unify-auth-oauth2-dot-v2.rst | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 67baebd1d84f..9b7c2306f44d 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -95,44 +95,41 @@ Relevance in edx-platform Code examples (authentication patterns by use case) =================================================== -* **External API (OAuth2 JWT — clients obtain token via OAuth2 flow at** ``/oauth2/`` **):** +* **Example APIs (Keep supporting OAuth2 JWT token & session authentication and deprecate Bearer token)** .. code-block:: python - from rest_framework import viewsets + # lms/djangoapps/course_home_api/dates/views.py — target state (BearerAuth removed as per decision #3) from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication + from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser from rest_framework.permissions import IsAuthenticated - class ExternalCourseViewSet(viewsets.ViewSet): - """External API — OAuth2 JWT authentication. Send as: Authorization: JWT """ - authentication_classes = [JwtAuthentication] - permission_classes = [IsAuthenticated] + class DatesTabView(RetrieveAPIView): + """Request details for the Dates Tab.""" + authentication_classes = ( + JwtAuthentication, + SessionAuthenticationAllowInactiveUser, + ) + permission_classes = (IsAuthenticated,) -* **Internal Service API (JWT issued via** ``create_jwt_for_user()`` **):** -.. code-block:: python +* **Browser-first API (Session primary, JWT added & deprecate Bearer Auth):** - from rest_framework import viewsets - from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication - from rest_framework.permissions import IsAuthenticated - - class InternalServiceViewSet(viewsets.ViewSet): - """Internal service-to-service API — same JwtAuthentication, different token issuance.""" - authentication_classes = [JwtAuthentication] - permission_classes = [IsAuthenticated] - -* **Browser-based API (Session only):** .. code-block:: python - from rest_framework import viewsets + # lms/djangoapps/teams/views.py — target state (BearerAuth removed & add JWT authentication support) + from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser from rest_framework.permissions import IsAuthenticated - class BrowserUIViewSet(viewsets.ViewSet): - """Browser UI API - Session authentication only.""" - authentication_classes = [SessionAuthenticationAllowInactiveUser] - permission_classes = [IsAuthenticated] + class TeamsDashboardView(GenericAPIView): + """View methods related to the teams dashboard.""" + authentication_classes = ( + SessionAuthenticationAllowInactiveUser, + JwtAuthentication, + ) + permission_classes = (IsAuthenticated,) Implementation Notes ==================== From 0ed80d7cedea1394c58da030db33d99130ee9184 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Tue, 5 May 2026 19:10:40 +0500 Subject: [PATCH 06/10] docs: sync ADR with edx-drf-extensions issue 284 openedx/edx-drf-extensions#284 --- docs/decisions/0034-unify-auth-oauth2-dot-v2.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 9b7c2306f44d..7c8da9346d4c 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -68,6 +68,8 @@ Consequences * Teams need to understand and implement proper authentication choices(where 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 ========================= @@ -152,7 +154,10 @@ Rollout Plan ------------ 1. Audit existing APIs and categorize — flag any using ``BearerAuthentication`` variants -2. Check client metrics for active Bearer token usage +2. Check active Bearer token usage via the custom monitoring attribute on + ``BearerAuthentication`` (see ``openedx/core/lib/api/authentication.py`` and + ``edx_rest_framework_extensions/auth/bearer/authentication.py`` — note both files + use the same attribute name, so correlate carefully to distinguish which class is active) 3. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` 4. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source 5. Remove overdue ``JWT_AUTH_ADD_KID_HEADER`` toggle — make KID header always-on @@ -163,6 +168,7 @@ References ========== * `OEP-0042`_ — Open edX Authentication Best Practices (primary reference) +* `DEPR: BearerAuthentication `_ — Deprecation ticket for ``BearerAuthentication`` in edx-drf-extensions and edx-platform * Django REST Framework - Authentication and permissions * Django OAuth Toolkit documentation * Open edX Authentication Patterns Guide From 39d9d1737bdac532ad39f19e2b0b31cc16a32fa2 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Thu, 7 May 2026 14:54:38 +0500 Subject: [PATCH 07/10] docs: make doc more explicit & address comments --- .../0034-unify-auth-oauth2-dot-v2.rst | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 7c8da9346d4c..cacbb210f4d0 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -35,8 +35,9 @@ Decision 1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard authentication mechanism for all API(external and internal) access**, per `OEP-0042`_ -2. **Session authentication MAY be supported alongside** ``JwtAuthentication`` - on any endpoint — this is the platform default and is acceptable. +2. **Session authentication MUST also be used when** the expected client for an API + is a Browser/MFE. This would be added alongside ``JwtAuthentication`` on the + endpoint — which is the platform default. 3. **``BearerAuthentication`` and ``BearerAuthenticationAllowInactiveUser`` are deprecated and MUST NOT be used in new code** 4. **``OAuth2Authentication`` and ``OAuth2AuthenticationAllowInactiveUser`` are @@ -97,41 +98,48 @@ Relevance in edx-platform Code examples (authentication patterns by use case) =================================================== -* **Example APIs (Keep supporting OAuth2 JWT token & session authentication and deprecate Bearer token)** +* **Standard API (JWT + Session):** + + Example: ``lms/djangoapps/course_home_api/dates/views.py (DatesTabView)`` + (`permalink `_) .. code-block:: python - # lms/djangoapps/course_home_api/dates/views.py — target state (BearerAuth removed as per decision #3) - from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication - from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser - from rest_framework.permissions import IsAuthenticated + # Current state + authentication_classes = ( + JwtAuthentication, + BearerAuthenticationAllowInactiveUser, # to be removed per Decision #3 + SessionAuthenticationAllowInactiveUser, + ) - class DatesTabView(RetrieveAPIView): - """Request details for the Dates Tab.""" - authentication_classes = ( - JwtAuthentication, - SessionAuthenticationAllowInactiveUser, - ) - permission_classes = (IsAuthenticated,) + # Target state (after BearerAuth removal) + authentication_classes = ( + JwtAuthentication, + SessionAuthenticationAllowInactiveUser, + ) +Note: use ``SessionAuthenticationAllowInactiveUser`` or ``SessionAuthentication`` based on usecase of the API. -* **Browser-first API (Session primary, JWT added & deprecate Bearer Auth):** +* **API intended for MFE/Browser clients:** + Example: ``lms/djangoapps/teams/views.py (TeamsDashboardView)`` + (`permalink `_) .. code-block:: python - # lms/djangoapps/teams/views.py — target state (BearerAuth removed & add JWT authentication support) - from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication - from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser - from rest_framework.permissions import IsAuthenticated + # Current state + authentication_classes = ( + BearerAuthenticationAllowInactiveUser, # to be removed per Decision #3 + SessionAuthenticationAllowInactiveUser, + ) + + # Target state (BearerAuth removed, JwtAuthentication added per Decision #1) + authentication_classes = ( + JwtAuthentication, + SessionAuthenticationAllowInactiveUser, + ) - class TeamsDashboardView(GenericAPIView): - """View methods related to the teams dashboard.""" - authentication_classes = ( - SessionAuthenticationAllowInactiveUser, - JwtAuthentication, - ) - permission_classes = (IsAuthenticated,) +Note: use ``SessionAuthenticationAllowInactiveUser`` or ``SessionAuthentication`` based on usecase of the API. Implementation Notes ==================== @@ -139,6 +147,13 @@ Implementation Notes * Supporting both ``JwtAuthentication`` and ``SessionAuthentication`` on the same endpoint is acceptable — this is already the platform default in ``openedx/envs/common.py`` (``DEFAULT_AUTHENTICATION_CLASSES``) +* ``SessionAuthenticationAllowInactiveUser`` is used in examples for consistency with + ``JwtAuthentication``, which also allows inactive users by default. Using standard + ``SessionAuthentication`` alongside ``JwtAuthentication`` would create inconsistent + behavior — active-user enforcement would depend on which auth method the client used. + Endpoints that need to enforce active-user status should do so via a permission class + rather than the authentication class. This ADR does not take a stance on inactive + user policy beyond noting this inconsistency. * 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 @@ -158,12 +173,17 @@ Rollout Plan ``BearerAuthentication`` (see ``openedx/core/lib/api/authentication.py`` and ``edx_rest_framework_extensions/auth/bearer/authentication.py`` — note both files use the same attribute name, so correlate carefully to distinguish which class is active) -3. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` -4. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source +3. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source +4. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` + from new code 5. Remove overdue ``JWT_AUTH_ADD_KID_HEADER`` toggle — make KID header always-on 6. Migrate external clients from Bearer tokens to JWT token flow 7. Remove ``BearerAuthentication`` and its ``OAuth2Authentication`` aliases once migration is complete +For full removal details, open questions around third-party client migration, and +token expiry considerations, see the +`DEPR: BearerAuthentication `_ ticket. + References ========== From 2bcb928b1704db3d53ab482c72af9fdcfb70deb5 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Fri, 8 May 2026 14:16:13 +0500 Subject: [PATCH 08/10] docs: move Bearer auth depr plan out of ADR Move BearerAuthentication depr plan out of this doc So that it resides in single place i.e. to its deprecation ticket. --- .../0034-unify-auth-oauth2-dot-v2.rst | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index cacbb210f4d0..42a36963b341 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -169,19 +169,11 @@ Rollout Plan ------------ 1. Audit existing APIs and categorize — flag any using ``BearerAuthentication`` variants -2. Check active Bearer token usage via the custom monitoring attribute on - ``BearerAuthentication`` (see ``openedx/core/lib/api/authentication.py`` and - ``edx_rest_framework_extensions/auth/bearer/authentication.py`` — note both files - use the same attribute name, so correlate carefully to distinguish which class is active) -3. Mark ``BearerAuthentication`` / ``BearerAuthenticationAllowInactiveUser`` deprecated in source -4. Update ``view_auth_classes`` decorator to remove ``BearerAuthenticationAllowInactiveUser`` - from new code -5. Remove overdue ``JWT_AUTH_ADD_KID_HEADER`` toggle — make KID header always-on -6. Migrate external clients from Bearer tokens to JWT token flow -7. Remove ``BearerAuthentication`` and its ``OAuth2Authentication`` aliases once migration is complete - -For full removal details, open questions around third-party client migration, and -token expiry considerations, see the +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 `_ ticket. References From 030119a3b0876d09d3e9c04ebd40d10c81d1efb0 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Fri, 8 May 2026 14:19:23 +0500 Subject: [PATCH 09/10] docs: add a pointer file in oauth_dispatch for this ADR --- .../0017-standardize-authentication-patterns.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 openedx/core/djangoapps/oauth_dispatch/docs/decisions/0017-standardize-authentication-patterns.rst diff --git a/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0017-standardize-authentication-patterns.rst b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0017-standardize-authentication-patterns.rst new file mode 100644 index 000000000000..4b92ecf344a9 --- /dev/null +++ b/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0017-standardize-authentication-patterns.rst @@ -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 `_ ticket From 6ee7a7cfc4e8470715f92601999594e0186eba25 Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Fri, 8 May 2026 14:21:56 +0500 Subject: [PATCH 10/10] docs: make decision more clear --- docs/decisions/0034-unify-auth-oauth2-dot-v2.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst index 42a36963b341..64327299a2a0 100644 --- a/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst +++ b/docs/decisions/0034-unify-auth-oauth2-dot-v2.rst @@ -34,7 +34,9 @@ Decision ======== 1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard - authentication mechanism for all API(external and internal) access**, per `OEP-0042`_ + 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. **Session authentication MUST also be used when** the expected client for an API is a Browser/MFE. This would be added alongside ``JwtAuthentication`` on the endpoint — which is the platform default.