11. Debug OAuth failures

OAuth failures feel cryptic until you map each error to a step in the flow. Most problems are not mysterious: the registered callback does not match, the state was not preserved, the verifier is wrong, the code expired, the client secret is missing, or the requested scope does not cover the API call.

Error map

Symptom Likely cause First check
redirect_uri_mismatch The authorization request and GitHub app callback configuration do not line up. Compare scheme, host, port, path, and whether the request is relying on GitHub's loopback exception.
invalid_client Wrong client ID or client secret. Reload values from .env and check for extra spaces.
bad_verification_code or invalid_grant The code expired, was already used, or does not belong to this flow. Start a fresh authorization request.
State mismatch The callback did not return the state your app generated. Abort before token exchange.
PKCE verification failure The token exchange used the wrong code_verifier. Make sure the verifier from the original flow was preserved.
401 Unauthorized The access token is missing, invalid, expired, or revoked. For the split-script flow, check that GITHUB_ACCESS_TOKEN exists in .env. In either flow, confirm the token is loaded and sent as Authorization: Bearer ....
403 or insufficient scope The token does not have permission for that endpoint. Check which scope the feature actually needs.
Browser says it cannot connect after approval The local callback server was not running or was listening on the wrong port. Start the script before approving and confirm the host and port in GITHUB_REDIRECT_URI. The learning default is localhost:8000.

Debug in flow order

  1. Confirm the GitHub OAuth App exists and the callback URL is correct.
  2. Confirm .env loads the expected client ID, client secret, and redirect URI.
  3. Generate a fresh authorization URL.
  4. Keep the generated state and code_verifier tied to that one flow.
  5. Start the callback server before clicking Authorize.
  6. Check returned state before token exchange.
  7. Exchange the code once, immediately.
  8. For the split-script flow, confirm the access token was saved to .env as GITHUB_ACCESS_TOKEN.
  9. Call a small endpoint such as /user before adding repository features.

Redirect URI tripwires

Redirect URI rules are provider-specific. For GitHub OAuth Apps, keep the learning path simple: register http://localhost:8000/callback, use the same value in .env, and have the local server listen on the same host, port, and path.

The split scripts assume the learning default. The complete Dev GitHub Tool parses GITHUB_REDIRECT_URI to choose the listener host and port. In either version, if you change one piece, change all of them together. Common mistakes are a trailing slash, a different port, using 127.0.0.1 in one place and localhost in another, or approving the browser prompt before the local callback server is running.

When scopes are the problem

If /user works but a repository endpoint fails, the OAuth flow itself may be fine. The token may simply lack the permission required for that endpoint. Do not solve that by blindly adding repo. Decide whether the feature needs public repositories, private repositories, organization resources, or a different GitHub app model.

Debugging OAuth is mostly discipline: identify the failed step, fix that step, and rerun from the step that creates a fresh authorization URL. The review page pulls the full chapter together.