Migrating older integrations
The photo service has decided to retire the implicit and password grants and to require PKCE from every client. Its authorization server has been running for more than a decade. Some clients were registered before PKCE existed, some by developers who have since left their companies, and a few by the photo service's own teams.
Switching the old behavior off one morning would be simple. It would also break integrations that people rely on, including some that nobody remembers are there. A migration takes longer, and it follows a predictable shape.
Taking inventory
The first step is to find out what actually happens, not what registrations allow. The authorization server's logs show, for each client, which grant types and response types it has used recently, which redirect URIs appeared in real requests, whether its authorization requests carried a PKCE challenge, how it authenticated at the token endpoint, and when it last obtained a token. All identifiers in these examples are fictional. Joined with the client registry, the logs produce a table like this:
| Client | Type | Seen in the last 90 days |
|---|---|---|
slideshow-widget | Public | Implicit grant, from a partner's browser application built in 2013 |
photos-cli | Public | Password grant, from older installed versions of the photo service's own tool |
greeting-cards | Confidential | Authorization code without PKCE, a wildcard redirect URI pattern, and a shared client secret |
photo-printer | Confidential | Authorization code with PKCE, refresh tokens, and a client secret in the HTTP Basic header |
old-uploader | Confidential | No requests since 2024 |
Inventories usually hold surprises. A client registered with five redirect URIs uses one. A client allowed the password grant has never used it, so removing it from the registration costs nothing. A client nobody can name turns out to power a feature a partner still sells. Each row becomes a migration item with an owner, a contact at the client's developer, and a decision. photo-printer needs nothing for this migration. Current versions of photos-cli already sign in through the browser, as Command-line tools showed, so the remaining work there is retiring the old versions. old-uploader is disabled, with a message to its registered contacts, and removed later if nobody objects.
Choosing each replacement
Each old pattern has a well-understood destination:
| Old pattern | Replacement |
|---|---|
| Implicit grant in a browser application | Authorization code flow with PKCE in the browser, or a backend for frontend that holds the tokens |
| Password grant in an installed application or tool | Authorization code flow with PKCE through the system browser, or device authorization where no browser is available |
| Password grant in automated tests | Test accounts with a test client, or client credentials when the test acts as a service |
| Authorization code without PKCE | The same flow with an S256 challenge and verifier |
| Redirect URI patterns, or many unused entries | An exact list of the addresses actually in use |
| A shared client secret, where the client can manage keys | private_key_jwt or mutual TLS, the asymmetric methods current guidance recommends |
Most of these changes happen in the clients, so the authorization server's owners can make them possible and ask for them, but cannot make them. They can make sure each destination works before asking anyone to move. The photo service's token endpoint accepts cross-origin requests from each browser client's registered origin, as it already does for the photo editor, so the slideshow can call it from its page. Its developer console lets a partner register a public key next to its existing secret. A request to migrate is far easier to accept when the new path already exists and is documented with exact requests and responses.
Tightening what still works
Some changes keep the same grant and remove a weakness. Adding PKCE to greeting-cards, a confidential client, protects its codes against injection even though it already authenticates. The server's part is to enforce PKCE completely once a client uses it: a challenge in the authorization request means the token request must carry the matching verifier, and a verifier presented for a code issued without a challenge is refused, the downgrade check that Request correlation and CSRF described.
Redirect URIs tighten in a similar way. The inventory shows which exact addresses greeting-cards really used, and those replace its wildcard pattern, which Redirect URI validation showed to be dangerous. Moving the partner from a shared secret to private_key_jwt follows the overlap from Rotating client credentials: register the public key, let the client start signing assertions, confirm that no request still uses the secret, then remove it. For the length of that overlap the registration accepts two methods, the arrangement Client authentication methods warned against as a standing setting, so the overlap gets an end date. When it ends, the partner holds no shared secret at all.
Enforcing gradually
Each change gets a per-client setting with three states: off, report-only, and enforced. In report-only mode, the server accepts a request as before but records that it would have been refused. The plan for one client might read:
Client: greeting-cards
PKCE (S256): report-only from 2026-10-15
enforced from 2027-01-15
Redirect URIs: https://*.cards.example/*
replaced by https://cards.example/oauth/callback
on 2027-01-15
Last 7 days: 1,204 requests would have been refused
The report-only count shows whether a client has really moved before anything is enforced. When it reaches zero, enforcement changes nothing for that client's users. When it does not, the server's owners know whom to contact, and they have the evidence to show them.
Third-party developers need time and specifics. The photo service announces each change with a date several months ahead, publishes a migration guide, and writes directly to every affected client's registered contacts with that client's own numbers. Reminders follow as the date approaches. Some services also schedule short brownouts beforehand, an hour or a day when the old behavior is refused, so that integrations nobody is watching fail while their developers still have time to respond.
New clients get the strict settings from their first day, so the list of exceptions only shrinks. When the last client has moved, the old grant types come out of the server's configuration altogether. Code that no client can reach can still contain a vulnerability, and removing it is the final step of the migration.