isFailedRefresh returns true if the error returned by the TokenSource.Token() is due to a failed refresh. The failure being the refresh token itself. If this returns true, no amount of retries will fix the issue. Notes: Provider responses are not uniform. Here are some examples: Github - Returns a
(existingToken *oauth2.Token, err error)
| 1434 | // Gitlab |
| 1435 | // - Returns 400 with Code "invalid_grant" and Description "The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client." |
| 1436 | func isFailedRefresh(existingToken *oauth2.Token, err error) bool { |
| 1437 | if existingToken.RefreshToken == "" { |
| 1438 | return false // No refresh token, so this cannot be refreshed |
| 1439 | } |
| 1440 | |
| 1441 | if existingToken.Valid() { |
| 1442 | return false // Valid tokens are not refreshed |
| 1443 | } |
| 1444 | |
| 1445 | var oauthErr *oauth2.RetrieveError |
| 1446 | if xerrors.As(err, &oauthErr) { |
| 1447 | switch oauthErr.ErrorCode { |
| 1448 | // Known error codes that indicate a failed refresh. |
| 1449 | // 'Spec' means the code is defined in the spec. |
| 1450 | case "bad_refresh_token", // Github |
| 1451 | "invalid_grant", // Gitlab & Spec |
| 1452 | "unauthorized_client", // Gitea & Spec |
| 1453 | "unsupported_grant_type", // Spec, refresh not supported |
| 1454 | "incorrect_client_credentials", // GitHub, wrong client_id/secret (HTTP 200) |
| 1455 | "invalid_client": // RFC 6749 Section 5.2, client auth failed |
| 1456 | return true |
| 1457 | } |
| 1458 | |
| 1459 | switch oauthErr.Response.StatusCode { |
| 1460 | case http.StatusBadRequest, http.StatusUnauthorized, http.StatusOK: |
| 1461 | // Status codes that indicate the request was processed |
| 1462 | // and rejected. 403 is intentionally excluded: no known |
| 1463 | // provider returns 403 from the token endpoint, and the |
| 1464 | // previous 403 case caused token destruction on |
| 1465 | // rate-limited refresh attempts. |
| 1466 | return true |
| 1467 | case http.StatusInternalServerError, http.StatusTooManyRequests: |
| 1468 | // These do not indicate a failed refresh, but could be a temporary issue. |
| 1469 | return false |
| 1470 | } |
| 1471 | } |
| 1472 | |
| 1473 | return false |
| 1474 | } |