Skip to content

[FC-0118] docs: add ADR for standardizing authentication patterns#38301

Open
Faraz32123 wants to merge 7 commits intodocs/ADRs-axim_api_improvementsfrom
docs/ADR-standardize_authentication_patterns_and_security_scheme_usage
Open

[FC-0118] docs: add ADR for standardizing authentication patterns#38301
Faraz32123 wants to merge 7 commits intodocs/ADRs-axim_api_improvementsfrom
docs/ADR-standardize_authentication_patterns_and_security_scheme_usage

Conversation

@Faraz32123
Copy link
Copy Markdown
Contributor

@Faraz32123 Faraz32123 commented Apr 8, 2026

related issue: #38169

@Faraz32123
Copy link
Copy Markdown
Contributor Author

related doc worth reading: https://docs.openedx.org/projects/openedx-proposals/en/latest/best-practices/oep-0042-bp-authentication.html#consequences, we can make changes to the ADR based on the attached doc if needed.

@Faraz32123 Faraz32123 requested a review from feanil April 8, 2026 08:29
@Faraz32123 Faraz32123 changed the title docs: add ADR for standardizing authentication patterns [FC-0118] docs: add ADR for standardizing authentication patterns Apr 13, 2026
@Faraz32123 Faraz32123 force-pushed the docs/ADR-standardize_authentication_patterns_and_security_scheme_usage branch from f5cb0c1 to a6a25d3 Compare April 13, 2026 08:40
Decision
========

1. **OAuth2 via Django OAuth Toolkit (DOT) MUST be the standard authentication
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this different from JWT authentication? My understanding was that JWTs were what the OAUTH2 mechanism produced for use in the authorization header?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right! I think the wording needs to be changed a bit because showing Oauth2 & JWT as separate can cause confusion. In the codebase, we have two JWT issuance paths:

  • 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 calls), it is used for internal service-to-service communication.
    Both are validated by the same JwtAuthentication class.

The real distinction is between JwtAuthentication (supported) and BearerAuthentication (deprecated per OEP-0042) and the use of multiple authentication methods in single API endpoint. I am updating the ADR to reflect this and we will no longer treat "OAuth2" and "JWT" as separate mechanisms.

@Faraz32123 Faraz32123 requested a review from feanil April 20, 2026 10:15
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**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this distinction is super important. What is the bad thing that happens if your API endpoints support session auth also? Right now DRF in openedx-platform supports both by default: https://github.com/openedx/openedx-platform/blob/master/openedx/envs/common.py#L822-L825 and I don't see a problem with that. I think it's more valuable that all endpoints support any valid auth scheme than having API calls not support the browser.

Copy link
Copy Markdown
Contributor Author

@Faraz32123 Faraz32123 Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feanil I think you have valid point here that we should support any valid auth scheme instead of categorizing them on the basis of their usage.
I am adding a new commit that addresses above point with some additional remains(includes removal of JWT issuers & use of asymmetric keys) of OEP-0042, But it had some consequences(Link) that you might want to look in. That way we can decide the scope of this ADR. And we can remove the part that is not needed for this ADR accordingly.

1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard
authentication mechanism for all API access** (external and internal), per `OEP-0042`_
2. **Session authentication MAY be supported alongside** ``JwtAuthentication``
on any endpoint — this is the platform default and has no security implications
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think saying there are zero implications is inaccurate. maybe soften it to something like "this is the platform default and is acceptable for most endpoints", since session auth carries CSRF concerns, cookie-handling rules, and a different threat model from JWT

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me change the wording a bit.


class BrowserUIViewSet(viewsets.ViewSet):
"""Browser UI API - Session authentication only."""
authentication_classes = [SessionAuthenticationAllowInactiveUser]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Browser-based API example contradicts Decision #1 here. Decision #1 says JwtAuthentication MUST be used for all API access, but this example only has SessionAuthenticationAllowInactiveUser. Should we either add JwtAuthentication alongside session here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can miss it, decision no.1 says, JwtAuthentication MUST be the standard authentication mechanism for all API access i.e. for external and internal API types. May be I can change decision no.1 to all API(external and internal) access to resolve the confusion.

For browser based APIs, session authentication is kind of must with an option of JWTAuthentication to support any valid auth scheme.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@feanil: Can you point to an example in edx-platform? I'm not clear on what this is, and thus not clear on the recommendation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robrap can u please take a look at it again? I have changed the examples according to our decisions & also adjusted missing items from openedx/edx-drf-extensions#284.

@robrap
Copy link
Copy Markdown
Contributor

robrap commented Apr 30, 2026

@feanil: You should review openedx/edx-drf-extensions#284 and all its comments to see if you are missing anything, and you may want to reference it in this ADR.

@Faraz32123 Faraz32123 force-pushed the docs/ADR-standardize_authentication_patterns_and_security_scheme_usage branch from 6b097b7 to 7504911 Compare May 5, 2026 14:09
@Faraz32123 Faraz32123 force-pushed the docs/ADR-standardize_authentication_patterns_and_security_scheme_usage branch from 7504911 to 0ed80d7 Compare May 5, 2026 14:11
Copy link
Copy Markdown
Contributor

@robrap robrap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional thoughts. Thanks.


.. code-block:: python

# lms/djangoapps/course_home_api/dates/views.py — target state (BearerAuth removed as per decision #3)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BearerAuth reference won't make much sense soon. (Same for example below). Recommend that you either include a before and after for authentication_classes, or give a permalink like

authentication_classes = (
JwtAuthentication,
BearerAuthenticationAllowInactiveUser,
SessionAuthenticationAllowInactiveUser,
)
permission_classes = (IsAuthenticated,)
serializer_class = DatesTabSerializer
if someone wanted to know what you were referring to.

permission_classes = (IsAuthenticated,)


* **Browser-first API (Session primary, JWT added & deprecate Bearer Auth):**
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was still getting caught up on Browser-first API. Definitely could have been just me. I was getting confused with server-side templates/views. This could just be me, so you don't have to make any changes.

Here is a proposed change:

  • API intended for MFE/Browser client

Feel free to adjust or not as you see fit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, as Browser-first can be confusing for others as well.

==========

* `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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deprecation for BearerAuthentication is simpler than removal, and the DEPR ticket brings up issues and questions around removal that are not addressed by this doc. I'm wondering if you need/want this document to address all the details, or if that should be worked out on the DEPR, and if this doc should simply refer to the DEPR for those details (in the rollout plan)? You are welcome to update the DEPR as-needed, but please don't remove things that aren't clear to you. Thank you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Questions we had on depr ticket were

  1. third-party clients still using Bearer tokens. (this should be done with proper notice/depr_ticket/community_post).
  2. how do we handle the 1-hour JWT expiry for long-running jobs. (This should be handled via refresh token).

May be I have missed some,
But I think that discussion should be done on deprecation ticket instead.

Comment on lines +38 to +39
2. **Session authentication MAY be supported alongside** ``JwtAuthentication``
on any endpoint — this is the platform default and is acceptable.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be more clear and definitive about when it should be used? Here is a take on this:

Suggested change
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.

If I got something wrong, you'll have an idea how to make it more clear.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your suggestion makes sense.

Comment on lines +111 to +114
authentication_classes = (
JwtAuthentication,
SessionAuthenticationAllowInactiveUser,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Does this doc, or this section, need to address why this isn't just using the default, and what SessionAuthenticationAllowInactiveUser is vs. SessionAuthentication?
  • Do we want to take on stance related to the difference between these two? I believe JwtAuthentication has AllowInactiveUser built-in, so we're just giving mixed permissions based on which kind of authentication is used.
    • Brainstorming:
      • Would it make sense for the default to include SessionAuthenticationAllowInactiveUser for consistency?
      • Do we want APIs to have to opt-in (vs opt-out) of blocking Inactive users? If we use a permission, it could be something like IsActiveUser. If we want to block authentication altogether, it gets complicated how to do this. We'd need a JwtAuthenticationActiveUserOnly or something like that, and linting or other code to ensure that the consistent Session Authentication version is used.

As I said, I'm not sure if you want to address or avoid this discussion in this doc, but using SessionAuthenticationAllowInactiveUser in the example doesn't fully avoid or address it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to block authentication altogether, it gets complicated how to do this. We'd need a JwtAuthenticationActiveUserOnly or something like that, and linting or other code to ensure that the consistent Session Authentication version is used.

Yes, we have mixed permissions based on authentication classes being used. I think that would be another discussion to have IsActiveUser /JwtAuthenticationActiveUserOnly or not.

Would it make sense for the default to include SessionAuthenticationAllowInactiveUser for consistency?

No, cause we don't want to allow inactive users to sign in everytime.

And as SessionAuthenticationAllowInactiveUser is inherited from SessionAuthentication(already in defaults) and both are allowed that's why I had not added SessionAuthentication in the examples before. And as said above we should allow any valid auth or combination of auth classes to facilitate the clients. We can say this ADR will include more of depr/removal work for bearer authentication 😅.

We may be can skip comparison between both types of session authentication classes in this doc & add a Note below examples to use SessionAuthenticationAllowInactiveUser or SessionAuthentication based on usecase of the API.

@Faraz32123 Faraz32123 requested a review from robrap May 7, 2026 09:55
@@ -0,0 +1,196 @@
Standardize Authentication Patterns and Security Schemes
========================================================
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other auth ADRs (0003, 0008, 0009, 0010, 0013, 0014) live under openedx/core/djangoapps/oauth_dispatch/docs/decisions/. Add a pointer file there linking to this one.


1. **JWT authentication via** ``JwtAuthentication`` **MUST be the standard
authentication mechanism for all API(external and internal) access**, per `OEP-0042`_
2. **Session authentication MUST also be used when** the expected client for an API
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"For all API access" would include admin views, /oauth2/access_token/, and HMAC/webhook endpoints — none of which use JwtAuthentication. Narrow this to "DRF API endpoints that take user-authenticated requests" and list the exclusions for context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants