karatelabs/karate · error · OAuth2Exception
Token request failed: " + error + (desc != null ? " - " +…
Error message
Token request failed: " + error + (desc != null ? " - " + desc : "")
What it means
Thrown when the token endpoint returned a well-formed JSON object containing an "error" field — i.e. the OAuth provider explicitly rejected the token exchange (RFC 6749 section 5.2). The message includes the provider's error code and, when present, the error_description.
Solutions
- Read the error code in the message: 'invalid_grant' usually means the code expired or was already redeemed — restart the authorization flow.
- Verify client_id and client_secret match the provider's registered app credentials.
- Ensure the redirect_uri sent in the token exchange is byte-identical to the one used in the authorization request.
- Check clock skew on the machine — large drift can invalidate codes/tokens with the provider.
Example fix
// before: mismatched redirect_uri
.redirectUri("http://localhost:8080/callback") // auth request used 8081
// after: same redirect_uri in both steps
.redirectUri("http://localhost:8081/callback") Defensive patterns
Strategy: try-catch
Validate before calling
// Before the flow: assert credentials and redirect_uri are configured and consistent
assert cfg.getClientId() != null && cfg.getClientSecret() != null;
assert cfg.getRedirectUri() != null && cfg.getRedirectUri().startsWith("http://localhost:"); Try / catch
try {
handler.token();
} catch (OAuth2Exception e) {
if (e.getMessage().contains("invalid_grant")) {
restartAuthorizationFlow(); // code expired/reused — start fresh
} else if (e.getMessage().contains("invalid_client")) {
rotateCredentials();
}
} Prevention
- Never reuse an authorization code; codes are single-use and short-lived.
- Keep redirect_uri byte-identical between the authorize and token requests.
- Rotate client secrets promptly and keep them synced with the provider app.
- Monitor NTP/clock sync on CI machines.
When it happens
Trigger: POST to the token endpoint succeeded at the HTTP/JSON level but the body contains {"error": ...}, e.g. invalid_grant, invalid_client, unauthorized_client, or unsupported_grant_type.
Common situations: Authorization code already used or expired (invalid_grant); wrong client_id/client_secret (invalid_client); redirect_uri in the token request not matching the one used in the authorization request; provider account/app misconfiguration.
Related errors
- Authorization flow failed: " + e.getMessage()
- Missing 'authorizationUrl' in OAuth config
- Missing 'client_id' in OAuth config
- Token endpoint returned invalid response: " +…
- Token endpoint returned unexpected response: " +…
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/54442dec7cd11565.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/http/AuthorizationCodeAuthHandler.java:209
builder.header("Accept", "application/json");
try {
HttpResponse response = builder.invoke("post");
String bodyString = response.getBodyString();
Json json;
try {
json = Json.of(bodyString);
} catch (Exception e) {
throw new OAuth2Exception("Token endpoint returned invalid response: " + truncate(bodyString));
}
if (!json.isObject()) {
throw new OAuth2Exception("Token endpoint returned unexpected response: " + truncate(bodyString));
}
Map<String, Object> data = json.asMap();
if (data.containsKey("error")) {
String error = String.valueOf(data.get("error"));
String desc = data.containsKey("error_description") ? String.valueOf(data.get("error_description")) : null;
throw new OAuth2Exception("Token request failed: " + error + (desc != null ? " - " + desc : ""));
}
logger.debug("Token exchange successful");
return OAuth2Token.fromMap(data);
} catch (OAuth2Exception e) {
logger.error("Token exchange failed: {}", e.getMessage());
throw new OAuth2Exception("Token exchange failed: " + e.getMessage(), e);
} catch (Exception e) {
logger.error("Token exchange failed: {}", e.getMessage());
throw new OAuth2Exception("Token exchange failed: " + e.getMessage(), e);
}
}
/**
* Start callback server on configured or default ports
*/
private String startCallbackServer(LocalCallbackServer server) {View on GitHub (pinned to a22eb90246)