theonedev/onedev · error · ExplicitException

OAuth token refresh error: ${getErrorMessage(errorResponse.g

Error message

OAuth token refresh error: ${getErrorMessage(errorResponse.getErrorObject())}

What it means

DefaultOAuthTokenService.requestTokens performs the OAuth token exchange/refresh against the authorization server. When the token endpoint returns a non-success response, the error object is converted to a message via getErrorMessage and thrown as ExplicitException (surfaced as 'OAuth token refresh error: ...'). This indicates the OAuth provider rejected the token request or refresh — e.g. invalid grant, bad client credentials, or expired refresh token.

Source

Thrown at server-core/src/main/java/io/onedev/server/util/oauth/DefaultOAuthTokenService.java:76

                new com.nimbusds.oauth2.sdk.token.RefreshToken(refreshTokenValue);
        AuthorizationGrant refreshTokenGrant = new RefreshTokenGrant(refreshToken);

        ClientAuthentication clientAuth = new ClientSecretBasic(
                new ClientID(clientId), new Secret(clientSecret));

        TokenResponse response;
        try {
            TokenRequest request = new TokenRequest(new URI(tokenEndpoint), clientAuth, refreshTokenGrant, null);
            response = TokenResponse.parse(request.toHTTPRequest().send());
        } catch (ParseException | URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }

        if (response.indicatesSuccess()) {
            return response.toSuccessResponse().getTokens();
        } else {
            TokenErrorResponse errorResponse = response.toErrorResponse();
            throw new ExplicitException(getErrorMessage(errorResponse.getErrorObject()));
        }

    }

    @Listen
    public void on(SystemStarted event) {
        taskId = taskScheduler.schedule(this);
    }

    @Listen
    public void on(SystemStopping event) {
        if (taskId != null)
            taskScheduler.unschedule(taskId);
    }

    @Override
    public void execute() {
        accessTokenCache.entrySet().removeIf(it -> it.getValue().isExpired());

View on GitHub (pinned to d44925c47c)

Solutions

  1. Re-authorize the OAuth connection to obtain a fresh access/refresh token (re-run the login/link flow).
  2. Read the provider error in the message (e.g. invalid_grant, invalid_client) and fix accordingly — usually re-consent or credential update.
  3. Verify the OAuth app's client_id/client_secret and redirect URI configured on the server match the provider's settings.
  4. Check provider status/docs for changed token policies and update server config, then retry.

Example fix

// before: refresh token revoked -> invalid_grant on every refresh
// after: re-authorize the account so a new refresh token is stored
// Server admin: OAuth connection settings -> 'Authorize' again with the provider.
Defensive patterns

Strategy: try-catch

Validate before calling

// Before refreshing, check stored token presence and expiry:
boolean refreshPossible(OAuthToken token) {
    return token != null && token.getRefreshToken() != null;
}

Try / catch

try {
    tokens = oAuthTokenService.tokens(...);
} catch (ExplicitException e) {
    if (e.getMessage().startsWith("OAuth token refresh error")) {
        // trigger re-authorization flow for the connection
    } else throw e;
}

Prevention

When it happens

Trigger: Calling requestTokens (via the tokens path) where the TokenResponse from the OAuth provider indicates failure: expired/revoked refresh token, wrong client_id/client_secret, redirect_uri mismatch, or the provider returning error codes like invalid_grant or invalid_client.

Common situations: Long-lived OAuth connections where the refresh token expired or was revoked; rotating the OAuth app's client secret without updating OneDev's server configuration; provider-side policy changes (token lifetimes, IP restrictions); clock skew invalidating tokens.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/72c18957bf3cc5b5. Report an issue: GitHub.