In this guide 6 sections
Most OAuth login failures trace to a short list. A redirect URI mismatch means the callback URL differs from the one registered. An invalid_grant means the code or refresh token is expired, reused or revoked. Check URLs, clocks, consent screen status and which client ID each environment uses.
What does redirect_uri_mismatch mean?
The provider compares the redirect URI in your request with the list registered for your client, character by character. A trailing slash, http instead of https, a different port or a www prefix all count as different. Google reports it as Error 400 redirect_uri_mismatch, Microsoft as AADSTS50011.
Open the failing request in the browser address bar and copy the redirect_uri value. Paste it into the provider console exactly. Behind a proxy or load balancer, the app may build the URL as http, so check the forwarded protocol header.
- Scheme: http versus https
- Host: www versus bare domain
- Port and trailing slash
- Path casing
Why does the token call return invalid_grant?
This error is deliberately vague. An authorisation code can be exchanged once, within a minute or so. Reuse it, retry on a double click, or exchange it with a different redirect URI than the one in the first step, and the answer is invalid_grant.
Refresh tokens fail for other reasons: a revoked app, a changed user password, long disuse, or a provider cap on tokens per user. Send the user through sign-in again. Store the newest refresh token.
Could clock skew be the problem?
Signed tokens carry iat, nbf and exp timestamps. If your server clock drifts by a few minutes, a freshly issued ID token looks not yet valid or already expired, and verification fails with messages about time.
Run NTP on every server and container host. Most JWT libraries accept a small clock tolerance setting, which helps, but fixing the clock is the real answer. Compare the Date header from the provider with your own system time.
Why does login work for you but not for users?
An app on a Google or Microsoft consent screen left in testing mode only lets listed test users sign in. Everyone else sees an access blocked page. Sensitive scopes also need the app verified before outside users are allowed.
Ask for the narrowest scopes your app needs. Each extra scope such as Gmail or Drive access lengthens the review and frightens users. Publish the privacy policy URL and the authorised domains on the consent screen before you submit.
Staging and production use different client IDs
A common slip is deploying the staging client ID and secret to production, or the reverse. The redirect list then matches one environment and not the other. Keep each environment’s client ID, secret and redirect URIs in its own configuration.
Write the list down once: environment, client ID name, redirect URIs, scopes and secret expiry date. Many providers expire client secrets, so a login that worked for a year can stop on a known date.
- Separate client per environment
- Secret expiry date in the team calendar
- Redirect URIs listed per client
- Test with a non-admin account after each deploy
OAuth login failure checklist
Table on a single page, giving each error message its own row: redirect_uri_mismatch, invalid_grant, invalid_client, access_denied and token time errors. Columns show the first thing to check, where in the provider console to look, the owner and the date fixed. A side box lists client ID, redirect URIs and secret expiry per environment.
Open a blank worksheet to printQuestions owners ask
Does localhost need its own redirect URI?
Yes. Register the exact local address and port used in development, such as http://localhost:3000/callback, on the development client. Do not add it to the production client.
Why does invalid_grant appear only sometimes?
Intermittent cases usually come from a double submit, a retry that reuses an authorisation code, or a refresh token rotated by another process. Make the exchange run once per code.
How long do refresh tokens last?
It depends on the provider and the app type. Check its documentation. Plan for expiry by catching the error and sending the user through sign-in again.
Do I need to verify my app with Google or Microsoft?
If outside users sign in and you request sensitive scopes, yes. Basic sign-in with only profile and email scopes usually needs less review than access to mail or files.
