"Nothing was deployed on our side" is the important sentence, because it points the investigation at the provider's console rather than your code — and provider-side changes are where these come from.
Getting a real error first. The generic failure is your auth layer swallowing the provider's response. Two places to look:
- The redirect URL your users land on. OAuth failures come back as query parameters — an
error code and often an error_description that is genuinely informative. Log the whole query string on your callback route, including the failure case. Most implementations only parse the success case and drop everything else, which is why you are flying blind.
- Your auth layer's own debug logging. Every serious library has a verbose mode that logs the token exchange, including the provider's raw response body. Turn it on in a non-production environment and reproduce.
Do that before theorising. The error code usually names the problem.
Now the things that break on a previously-working provider. In descending order of how often it turns out to be them:
1. Redirect URI mismatch. The single most common cause. The URI registered in the provider's console must match what you send exactly — scheme, host, port, path, trailing slash. It breaks without a deploy when: you added a domain or moved to a new one, a preview environment generated a new hostname, someone tidied the console, or you went from www to bare or back. The error for this is usually explicit once you are logging it.
2. An expired or rotated client secret. Several providers now issue secrets with an expiry date. It works for a year, then stops on a Tuesday with nobody having touched anything. Check the credential's expiry in the console — this fits your symptoms perfectly and is invisible from your side.
3. App access level or permissions changed. The provider changed what your tier is allowed to do, or a permission that used to be implicit now needs to be requested explicitly. The classic is email address: many providers require you to separately request email access, and only grant it once you have a privacy policy and terms of service URL on file. If your account-linking is keyed on email and the provider stops returning one, you get exactly this — the OAuth dance succeeds, your code cannot find an email, and it fails at account creation. Check whether the failure is in the token exchange or after it; that distinction splits the causes in half.
4. A required review or compliance step lapsed. The app was fine, then the provider required a form to be completed, a policy to be re-accepted or a review to be renewed, and put the app into a restricted state. There will be a notice in the console; there was almost certainly an email to whichever address is on the developer account, which is frequently somebody who has left.
5. Protocol version. A provider deprecating an older OAuth version in favour of a newer flow. Rare to happen overnight, but the deadline arrives eventually and it does not care that your integration was working.
Structural advice while you are in here. Build account-linking so that a provider can be removed without orphaning accounts: store the provider identity as one of potentially several linked identities on a user record, keyed by your own user id, and always give users a way to set a password or link a second method. Providers change their terms, their pricing and their access tiers, and the ones that have done it once will do it again. Being able to drop one without stranding those users is worth designing for on day one.