Update dependency django-oauth-toolkit to v3.4.0 #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "renovate/django-oauth-toolkit-3.x-lockfile"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR contains the following updates:
3.3.0→3.4.0Release Notes
django-oauth/django-oauth-toolkit (django-oauth-toolkit)
v3.4.0Compare Source
The headline of this release is first-class support for the Model Context Protocol (MCP)
authorization server role.
MCP's authorization spec is built on a stack of modern OAuth RFCs,
and this cycle landed the whole stack: Authorization Server Metadata (RFC 8414) and Protected
Resource Metadata (RFC 9728) for discovery, Dynamic Client Registration (RFC 7591 / RFC 7592) and
OAuth Client ID Metadata Documents (CIMD) so clients can register themselves, Resource Indicators
(RFC 8707) for audience-bound access tokens, and the OAuth 2.0 Security Best Current Practice
(RFC 9700) together with the RFC 9207
issparameter. The RFC 9700 compliance gates double as aconfigurable OAuth 2.1 security posture — they can reject the implicit and password grants and
enforce S256-only PKCE (legacy behavior by default in 3.4, scheduled to flip to compliant in 4.0).
The new
ALLOW_LOCALHOST_LOOPBACKsetting smooths the ephemeral-port loopback callback used bynative clients such as Claude Code, MCP Inspector, and mcp-remote.
Beyond MCP, the release adds a Django Ninja integration alongside the existing DRF support and
support for RP-Initiated Registration, lifts the 255-character cap on refresh tokens (mirroring the
access-token checksum scheme), makes
cleartokensreclaim revoked refresh tokens sooner, andharmonizes Bearer
Authorizationheader parsing across the middleware.It also carries a batch of security fixes: an unauthenticated open redirect from the
authorization endpoint (
prompt=none), HS256 ID tokens being signed with the hashed clientsecret, cleartext tokens and codes exposed in the Django admin, client secrets written to debug
logs, and predictable device-flow
user_codegeneration. Longstanding operational bugs are fixedtoo, including a multi-database
migratedeadlock (#1591) and duplicate unique indexes that brokefresh installs on Oracle and strict MySQL (#1656).
Before upgrading, read the breaking-changes section below: most items are
makemigrationssteps for swapped models, but applications using the
HS256signing algorithm now requirehash_client_secret=False.WARNING - POTENTIAL BREAKING CHANGES
HS256signing algorithm must now be configured withhash_client_secret=False. Previously such applications signed ID tokens with the hashed clientsecret, producing tokens that relying parties could not verify.
Application.clean()now raises aValidationErrorforHS256+hash_client_secret=True, andApplication.jwk_keyraisesImproperlyConfiguredat signing time if the secret is hashed. To migrate an affectedapplication, recreate it (or reset its secret) with
hash_client_secret=Falseso the plaintextsecret is stored and can be used as the shared HMAC key.
AbstractRefreshTokenmodel require doing amanage.py migrateafter upgrading.OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL) you will need toupdate your custom model with
manage.py makemigrations. If your table already contains refreshtokens you must also backfill
token_checksumwith a data migration — adapt the batched backfillloop from
forwards_funcinoauth2_provider/migrations/0015_refreshtoken_token_checksum.py(dropping its swapped-modelguard, the early return, and resolving your own model instead) and keep the same operation order:
add nullable checksum → drop the old
("token", "revoked")unique constraint → widentokentoTextField→ backfill → make checksum non-nullable → add the("token_checksum", "revoked")unique constraint.
OAUTH2_PROVIDER_APPLICATION_MODEL), runmanage.py makemigrationsafter upgrading:AbstractApplicationgained aregistration_sourceCharField(choicesmanual/dcr/cimd, defaultmanual) to markhow an application was registered — for example via Dynamic Client Registration (#670). This
replaces the never-released
dcr_createdBooleanField. Installs using the built-in Applicationmodel just need
manage.py migrate(migration0019).OAUTH2_PROVIDER_APPLICATION_MODEL), runmanage.py makemigrationsafter upgrading: for CIMD (#1742)AbstractApplicationgained anullable
cimd_expires_atDateTimeField, andclient_idwidened frommax_length=100to255so a metadata-document URL fits. Installs using the built-in Application model just needmanage.py migrate(migration0020).OAUTH2_PROVIDER_DEVICE_GRANT_MODEL), runmanage.py makemigrationsafter upgrading: the redundant field-levelunique=Truewas removedfrom
AbstractDeviceGrant.device_code(#1656), andAbstractDeviceGrant.scopechanged fromCharField(max_length=64, null=True)to a non-nullableTextField(blank=True)(#1693). Whenprompted for a default for existing NULL
scoperows, provide the one-off default""—matching
oauth2_provider/migrations/0016_alter_devicegrant_scope.py. Uniqueness remains enforced by the<app_label>_<class>_unique_device_codeconstraint. If you are doing a fresh install onOracle (or a MySQL backend that raises warnings as errors), you must also regenerate — or
hand-edit — your existing
CreateModelmigration for the swapped model, since it still declaresboth uniqueness rules and will fail the same way migration
0013did.OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL) and have notyet applied the
0012_add_token_checksummigration (i.e. you are upgrading from a versionbelow 3.0), its
token_checksumbackfill now deterministically skips the swapped model — theschema operations in that migration never applied to swapped models, and the old backfill only
worked when the ordering of your app's migrations happened to allow it.
migratelogs a warningwhen the backfill is skipped and your table contains access tokens. Until
token_checksumisbackfilled those tokens will not validate; no data is lost, and tokens work again as soon as the
checksum is populated. To backfill, add a data migration to your app (ordered after your
migration that adds
token_checksum): adapt the batched backfill loop fromforwards_funcinoauth2_provider/migrations/0012_add_token_checksum.py, dropping its swapped-model guard (theearly return) and resolving your own model instead. You can check for affected rows with
YourAccessToken.objects.filter(token_checksum__isnull=True).exists(). Installs that alreadyapplied
0012(any 3.x deployment) are unaffected.Added
/.well-known/oauth-authorization-server)/.well-known/oauth-protected-resource), plus opt-inmixins/decorators (
ProtectedResourceMetadataMixin,protected_resource_metadata) and a DRF authenticator(
OAuth2ProtectedResourceAuthentication) that advertise it via theresource_metadataWWW-Authenticatechallenge parameterclient_secretfield, warning users to copy thesecret on creation and explaining it is hashed and unrecoverable when editing. The help text is
shared by both the Django admin application form and the front-end register/edit views: the
ApplicationAdminusesApplicationForm, and a sharedoauth2_provider/js/application_form.jsupdates the text live as the
hash_client_secretcheckbox is toggled on either surface. Theform also warns immediately when the
HS256algorithm is selected while the client secret is —or will be — hashed, instead of only surfacing the error on save. (#1697, #1740)
DynamicClientRegistrationViewandDynamicClientRegistrationManagementViewwith configurable permission classes and registration accesstokens. Dynamically registered applications are flagged with
AbstractApplication.registration_sourceset to
"dcr"and can be filtered in the Django admin.ALLOW_LOCALHOST_LOOPBACKsetting to extend the RFC 8252 §7.3 any-port loopback exemption tohttp://localhostredirect URIs (opt-in, defaultFalse)draft-ietf-oauth-client-id-metadata-document). A client may present anhttpsURL as itsclient_id; whenCIMD_ENABLEDis on the server fetches, validates and persists the metadatadocument as a public application (SSRF-hardened fetch, failure backoff and an in-flight fetch cap).
Applications resolved this way carry
AbstractApplication.registration_sourceset to"cimd".Registration can be gated with
CIMD_REGISTRATION_PERMISSION_CLASSES(default allow-all;HostAllowlistCIMDPermissionrestricts it toCIMD_ALLOWED_HOSTS), and theclearcimdapplicationsmanagement command prunes expired CIMD applications that hold no livetokens. See
docs/cimd.rst.registration_endpointin the RFC 8414authorization server metadata document when
DCR_ENABLEDis onresourceparameter during authorization or access token requestsaudclaim for tokens with resource indicatorsgates, each controlled by a
COMPLIANT_BCP_RFC9700_<topic>setting that defaults toFalse(current behavior,warns when the discouraged behavior is used) and is scheduled to default to
Truein 4.0 (enforces thecompliant behavior):
COMPLIANT_BCP_RFC9700_IMPLICIT_GRANT(§2.1.2),COMPLIANT_BCP_RFC9700_PASSWORD_GRANT(§2.4),COMPLIANT_BCP_RFC9700_PKCE_METHOD(§2.1.1),COMPLIANT_BCP_RFC9700_ACCESS_TOKEN_TRANSPORT(§4.3.2),COMPLIANT_BCP_RFC9700_AUTHZ_RESPONSE_ISS(§4.4),and
COMPLIANT_BCP_RFC9700_TOKEN_STORAGE(§4). Enforced behaviors are also removed from the RFC 8414authorization-server metadata and the OIDC discovery document, so both stay consistent with what the
server accepts.
issauthorization-response parameter and theauthorization_response_iss_parameter_supportedmetadata field (mix-up defense), gated byCOMPLIANT_BCP_RFC9700_AUTHZ_RESPONSE_ISS.canonical; the gate only sets validation severity — insecure value → check Warning while the gate is
False,check Error once it is
True):COMPLIANT_BCP_RFC9700_REFRESH_TOKEN(
REFRESH_TOKEN_REUSE_PROTECTION, §4.14.2),COMPLIANT_BCP_RFC9700_REDIRECT_URI_SCHEME(
ALLOWED_REDIRECT_URI_SCHEMES, §2.1),COMPLIANT_BCP_RFC9700_REDIRECT_URI_MATCHING(
ALLOW_URI_WILDCARDS, §4.1.1), andCOMPLIANT_BCP_RFC9700_PKCE_REQUIRED(PKCE_REQUIRED, §2.1.1).--deploysecurity system check that flags every RFC 9700 recommendation currently on a non-compliant value(warnings
oauth2_provider.W001–W010, errorsoauth2_provider.E002–E005when the correspondingconfig-validation gate is enabled), plus an error (
oauth2_provider.E001) for the incompatible combination ofhashed token storage and a non-zero
REFRESH_TOKEN_GRACE_PERIOD_SECONDS.docs/security.rstpage mapping each RFC 9700 recommendation to the corresponding setting. The demo IdPexposes every gate as an
OAUTH2_PROVIDER_COMPLIANT_BCP_RFC9700_*environment variable so the Docker image and thee2e suite can exercise both gate positions.
HttpRequestcreation inOAuth2Validator.validate_userinto an overridablebuild_http_requestmethod, so subclasses can pass extra attributes through to their authentication backends.Changed
Authorizationheader parsing is now harmonized across the codebase via a sharedoauth2_provider.utils.parse_bearer_tokenhelper implementing RFC 7235 / RFC 6750 semantics.As a result,
OAuth2TokenMiddlewareandOAuth2ExtraTokenMiddlewarenow accept the schemecase-insensitively (e.g. a lowercase
bearerheader, which is RFC-correct, is no longerignored) and no longer mis-parse non-Bearer schemes that merely start with
Bearer(e.g.
BearerX tokenwas previously treated as a Bearer token and is now rejected).cleartokensnow removes revoked refresh tokens onceREFRESH_TOKEN_GRACE_PERIOD_SECONDShas passed, instead of keeping them until
REFRESH_TOKEN_EXPIRE_SECONDS. WhenREFRESH_TOKEN_REUSE_PROTECTIONis enabled, revoked tokens are still kept until they expire sothat token reuse can be detected.
RefreshToken.tokenis now aTextFieldand lookups use a new SHA-256token_checksumfield, removing the 255 character limit so long refresh tokens (e.g. Microsoft's JWT refresh
tokens) are supported. This mirrors the
AccessToken.token_checksumapproach introduced in 3.0.0(#1447). The revocation endpoint also looks up access tokens by checksum now, restoring an indexed
lookup there.
0012_add_token_checksumbackfill now computes checksums in batchedbulk_updatecalls (1000 rows per statement) instead of saving each access token individually, sharply
reducing how long the migration locks the access token table on large installations. Running
cleartokensbefore upgrading is still the best preparation for tables with many expiredtokens. See the warning above if you use a swapped access token model.
Deprecated
plaincode_challenge_method, or an access token in the URI query string now emits aDeprecationWarning, perRFC 9700. Each is gated by the corresponding
COMPLIANT_BCP_RFC9700_*setting, whose default is scheduled to flip toTrue(enforcing rejection) in 4.0.AUTHENTICATION_SERVER_EXP_TIME_ZONEsetting. Token introspectionexpvalues areUnix timestamps and are always interpreted as UTC per RFC 7662/RFC 7519. The setting still works
for backwards compatibility but now emits a
DeprecationWarningand will be removed in a futurerelease.
Fixed
redirect_uriswhose hostname uses the double-dash form required forNetlify deploy-preview URLs (
https://*--sitename.netlify.app). The validator previously strippedonly a single leading hyphen after removing the
*, leaving a hostname that began with-and wasrejected by
URIValidator; it now strips up to two leading hyphens while rejecting longer runs.ReadWriteScopedResourceMixin.__new__()no longer forwards positional/keyword arguments toobject.__new__(), which raisedTypeError: object.__new__() takes exactly one argumentwheninstantiating any view mixing this in with any argument at all — notably breaking Django REST
Framework's
cls(**initkwargs)view instantiation.client_idorusernamecontaining a NUL (\x00) byte no longer causes a 500 erroron database backends (e.g. PostgreSQL) that raise
ValueErrorinstead of executing the query;such values are now correctly treated as not matching any client/user.
rw_protected_resourcedecorator accumulating the read/write scope on a shared listacross requests. The required-scope list was built once at decoration time and appended to on
every request, so after a write (
POST) request thewritescope stayed in the list and asubsequent read (
GET) request with a read-only token was wrongly rejected. The behaviour wasrequest-order dependent, not thread-safe, and also mutated a caller-supplied
scopeslist. Theread/write scope is now added to a fresh per-request list.
AbstractDeviceGrant.scopeis now aTextField(blank=True)like the other grant and tokenmodels, instead of
CharField(max_length=64, null=True). 64 characters is well below the limitscommon in the broader OAuth ecosystem (Okta allows 1024, Google 2048), so longer scope strings
no longer fail or get truncated in the device authorization flow. Existing rows with a NULL
scope are backfilled to an empty string by migration
0016.pkinstead ofidinclear_expired()andRefreshToken.revoke()so token models with a custom primary key field are supported.datetime.utcfromtimestamp.auth_timein oauth2 validator when user has never logged in.OIDC_SERVER_CLASSwhenOIDC_ENABLEDisTrueandOAUTH2_SERVER_CLASSis not explicitly set; previously only the default was used in this fallback path.migratehanging on0012_add_token_checksumwhen a database router ormulti-database configuration is in use. The
RunPythondata migrations in0006and0012nowpin their queries to
schema_editor.connection.alias, so the backfill runs on the connectionperforming the migration instead of being routed to a second connection that deadlocks against
the migration transaction's own locks. This also makes both migrations backfill the correct
database when migrating a non-default alias (
migrate --database=...). Thanks to Igor Petrik forthe diagnosis and fix.
unique=TrueonDeviceGrant.device_code, which duplicated theunique_device_codeUniqueConstraintand created two identical unique indexes on the samecolumn. Fresh installs failed on Oracle (
ORA-02261) and on MySQL backends that raise databasewarnings as errors (
ER_DUP_INDEX, 1831). Migration0013is fixed in place because theduplicate was created inside
CREATE TABLE, so a follow-up migration could never fix freshinstalls. Databases that already applied the old
0013keep one harmless extra unique index;it can optionally be dropped by hand. Thanks to Febin Micheal Antony (#1659) and
moscowmule2240 (#1718) for the fixes.
Security
user_codevalues with the cryptographically securesecretsmoduleinstead of the predictable
randommodule (Mersenne Twister). Theuser_codeis a deviceauthorization credential and must be unguessable per
RFC 8628 sections 5.1 and 5.2.
OAuth2Validatorlogged the submitted
client_secret(and, for Basic auth, the base64client_id:client_secretcredential string) at
DEBUGlevel. These messages now log at most theclient_id(when it isavailable; the base64/unicode decode-failure paths log a generic message with no credential), so
password-equivalent client secrets and raw credential strings no longer leak into log files or
aggregators.
admin. The default
AccessTokenAdmin,RefreshTokenAdmin, andGrantAdminclasses listed theraw
token/codeinlist_displayand included them insearch_fields. Because these valuesare stored in cleartext, any staff user with view access saw replayable credentials, and
searching placed them in the
?q=query string (captured by access logs and browser history).The columns are now masked (last characters only) and are no longer searchable (search is
available by application and user instead). The raw
token/codefield is also excluded fromthe admin change/view form, which showed the editable cleartext field to any staff user with view
access; a masked read-only value is shown instead. Adding tokens/codes through the admin is now
disabled (
has_add_permissionreturnsFalseon theAccessToken,RefreshToken,Grant, andIDTokenadmins) — these are issued by the OAuth flows and are not meant to be hand-created, andthe add form would otherwise present an editable cleartext field. Relatedly, the
AccessToken,RefreshToken, andGrantmodel__str__methods no longer return the raw token/code (which theadmin renders in a row's change-page title and breadcrumbs, and which also appears in
repr()andlogs); they now return a
"<Model> #<pk>"identifier.used the
HS256algorithm withhash_client_secret=True(the default), the ID token was signedwith the stored password-hash string as the HMAC key instead of the shared client secret, so a
relying party holding the real (plaintext) secret could never verify the signature — and a
password hash was misused as a MAC key.
HS256now requireshash_client_secret=False:Application.clean()rejects the combination, andjwk_keyraisesImproperlyConfiguredrather than emit an unverifiable token.
HS256with an empty client secret is likewise rejected(an empty HMAC key would make ID tokens trivially forgeable). See the breaking-changes note above.
prompt=nonerequest froman unauthenticated user was redirected to the supplied
redirect_uriwith alogin_requirederrorbefore the client and
redirect_uriwere validated, allowing an attacker to redirect a victim'sbrowser to an arbitrary origin. The request is now validated against a registered client before any
redirect, per OpenID Connect Core 1.0 section 3.1.2.6.
Reported by Brian Lee (SSLab, Georgia Tech).
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR has been generated by Mend Renovate.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.Merge
Merge the changes and update on Forgejo.Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.