Upgrading
This page collects the upgrade steps for every release of Django OAuth Toolkit that needs one, so you don’t have to reconstruct them from the Changelog. If a release required something of you — a breaking change to accommodate, a behavior change that can surface in a running deployment, an action to take on the way through — it has a section here. Releases that need nothing beyond installing them are deliberately absent, so an empty gap between two versions is an answer, not an omission. The changelog remains the authoritative, complete record — always read the entries between your current version and your target version before upgrading.
General upgrade procedure
Read the changelog for every release between your current and target version, not only the target — breaking changes are sometimes introduced in one release and then have follow-ups in later ones.
Pin the exact version you are upgrading to and test in a staging environment before production.
Run migrations. After upgrading the package, run
python manage.py migrate.Regenerate migrations for swapped models. If you have swapped any of the toolkit’s models (application or token models), run
python manage.py makemigrationsfor your app and thenmigrate, so your custom models pick up the same schema changes.Check for removed deprecations. Major releases remove things that earlier releases warned about with a
DeprecationWarning. Run your test suite with warnings enabled (python -W all) on the previous version first to surface anything you still rely on.
Upgrading to 2.0
2.0.0 is a major release with breaking changes. The two most likely to surface in a running
deployment are the client-secret hashing change — which shows up as an {"error":
"invalid_client"} response at the token endpoint — and PKCE now being required, which instead
fails with an invalid_request / invalid_grant error for clients that don’t send a PKCE
challenge.
Client secrets are now hashed on save (#1093). Existing cleartext
application.client_secretvalues are migrated to Django password-style hashes on upgrade, and the hashing cannot be reversed. When you create or edit an application (in the admin or via the API), copy the generated/entered secret before saving — afterwards only the hash is stored. Clients configured with the old cleartext value keep working; only reading back a secret is no longer possible. If you have automation that readsclient_secretout of the database, it must be updated.PKCE is now required by default (#1129).
PKCE_REQUIREDdefaults toTrue, so authorization-code clients that do not send a PKCEcode_challenge/code_verifierwill fail. Either add PKCE to those clients (recommended) or setPKCE_REQUIREDtoFalseto retain the pre-2.x behavior. Note that it is namespaced under theOAUTH2_PROVIDERsetting, not a top-level Django setting:OAUTH2_PROVIDER = { # ... "PKCE_REQUIRED": False, }
OIDC standard scopes now gate claims (#1108). Default OIDC scopes now determine which claims are returned. If you customized OIDC responses and want the pre-2.x behavior, set
oidc_claim_scope = Nonein yourOAuth2Validatorsubclass.The ``oob`` redirect URIs were removed (#1124). Support for the insecure
urn:ietf:wg:oauth:2.0:oobandurn:ietf:wg:oauth:2.0:oob:autoredirect URIs is gone, replaced by RFC 8252 “OAuth 2.0 for Native Apps”. If you still rely onoob, migrate those native clients to a loopback or custom-scheme redirect before upgrading.
Upgrading to 3.0
3.0.0 requires a schema migration and drops support for older Django versions.
Run ``migrate`` — the ``AccessToken`` model changed (#1447). The
tokencolumn became aTextField(removing the 255-character limit so JWT access tokens with extra claims fit), and a newtoken_checksum(SHA-256) field is used to look tokens up. Runpython manage.py migrateafter upgrading; if you use swapped models, runmakemigrationsfor your app first.Warning
Swapped access token models need a manual ``token_checksum`` backfill. The built-in migration backfills
token_checksumfor the defaultAccessTokenonly — it deliberately skips a swapped access token model (and logs a warning to that effect). Until you backfill the checksum for your existing rows, those access tokens will fail validation. Backfill it in a data migration on your app, computinghashlib.sha256(token.encode("utf-8")).hexdigest()for each existing row (mirroring whatoauth2_provider’s0012_add_token_checksummigration does for the default model).Models now use ``pk`` instead of ``id`` (#1446). This lets swapped models use a different primary-key field. If any of your code assumed an
idattribute on the toolkit’s models, usepkinstead.Django < 4.2 is no longer supported (#1455). Upgrade Django to 4.2 or newer first.
Deprecations from 2.4.0 were removed (#1425).
RedirectURIValidatorandWildcardSet(deprecated in #1345) are gone — replace any imports of them. The deprecated importablevalidate_logout_requesthelper was also removed (#1274); note that this is distinct from theRPInitiatedLogoutView.validate_logout_requestmethod, which still exists — so if you grep the codebase and still findvalidate_logout_request, that method is expected to be there.Token cleanup writes now honor database routers (#1450). If you run a multi-database setup, ensure your routers direct the token models to the correct database (see the multiple-databases note).
Upgrading to 3.4.1
3.4.1 tightens redirect URI matching and refresh token handling. Most deployments need to do
nothing beyond installing it, running migrate and running collectstatic, but several
behaviors that were previously accepted are now rejected, so work through this list before rolling
it out.
Redirect URIs are now matched exactly (RFC 9700 §2.1). A request may no longer carry query parameters, path parameters (
;key=value), credentials (https://user@host/cb) or a fragment that the registered URI does not have. If any of your clients pass per-request data through theredirect_uriquery string, they will start failing withredirect_uri_mismatch: either register the full URI including its query (matched in the same order), or move that data into thestateparameter, which is what it is for. Applications whose registeredredirect_uriscarry no query component are unaffected.Some registered redirect URIs are no longer valid and must be re-registered. A URI ending in a bare
#is now rejected at registration (#1801); previously it was stored and would then never match anything. A rootless private-use scheme URI (com.example.app:oauth2redirect) is also rejected (#1796) — it used to be silently rewritten tocom.example.app://oauth2redirect, registeringoauth2redirectas a hostname, which no client matches. Re-register those in the RFC 8252 §7.1 single-slash form,com.example.app:/oauth2redirect.Run ``collectstatic``. The shipped templates no longer load Bootstrap from a third-party CDN (#730);
oauth2_provider/base.htmllinks a stylesheet distributed with the package instead, served throughstaticfiles. Until you collect static files, the built-in authorization and application pages render unstyled. Substituting your own styles through thecssblock ofbase.htmlworks exactly as before.``REFRESH_TOKEN_EXPIRE_SECONDS`` is now enforced when a refresh token is presented (#746), not only by the
cleartokenssweep. Expiry is idle-based — a refresh token is rejected that many seconds after its access token expires, and the deadline slides forward on every refresh — so actively-used tokens are unaffected. If you set this, expect idle tokens that are already past their lifetime to be rejected on upgrade, forcing those clients to re-authenticate. The default (None) still never expires refresh tokens.Revoking an access token now revokes the refresh token bound to it — through the RFC 7009
/revoke/endpoint (#746), the authorized-tokens page (#1510) and the admin. Previously the refresh token survived and could immediately mint a new access token, defeating the revocation. If you depend on the old behavior, note that whether a refresh token may survive access-token revocation becomes a configurable policy in 4.0.The revocation endpoint only revokes tokens issued to the authenticated client (#727). If you have automation that revokes another application’s tokens through
/o/revoke_token/, it will silently stop having an effect — the endpoint still returns200(RFC 7009 §2.2) without disclosing whether the token exists.A revoked refresh token is no longer honored inside the grace window (#1816). This only affects deployments that set a non-zero
REFRESH_TOKEN_GRACE_PERIOD_SECONDS; the default of0was never exposed. Genuine rotation retries inside the window still work.If you swap in your own refresh token model, run
makemigrationsfor your app to pick up the new index ontoken_family(#1809), and if you overriderevoke(), overriderevoke_family()to match — reuse detection now revokes a compromised family as a set rather than row by row, so anything extra yourrevoke()does has to happen inrevoke_family()too. See Extending the token models.If you wrapped or patched ``redirect_to_uri_allowed()`` to influence
AbstractApplication.redirect_uri_allowed()orpost_logout_redirect_uri_allowed(), target the newcheck_redirect_to_uri_allowed()instead (#681) — those methods now call it rather than the old helper.``Application.clean()`` now reports validation errors per field (#1343) instead of as non-field errors, and reports all of them at once.
ValidationError.message_dictis keyed by field name, so callers ofApplication.full_clean()see the field alongside the message. A customModelFormthat omits one of those fields still receives the message as a non-field error, provided it subclassesoauth2_provider.forms.ApplicationForm.``JSONOAuthLibCore`` is deprecated (#1773) and now emits a
DeprecationWarning. If you setOAUTH2_BACKEND_CLASSto it, plan to move off it: the JSON request-body mode is non-standard (RFC 6749, RFC 7662 and RFC 7009 define those endpoints asapplication/x-www-form-urlencoded), and it is scheduled for removal in 4.0.
Note
For the full, authoritative list of changes in every release — including the releases that asked nothing of you and so have no section here — see the Changelog.