justauth/JustAuth · error · AuthException

JSONObject.toJSONString(response)

Error message

JSONObject.toJSONString(response)

What it means

The DingTalk v2 adapter posts grantType=authorization_code and expects an 'accessToken' field in the JSON response; when that key is absent it throws AuthException whose message is the entire raw response body. So the exception text is DingTalk's own error payload (errcode/errmsg style), which tells you why the exchange failed.

Source

Thrown at src/main/java/me/zhyd/oauth/request/AuthDingTalkV2Request.java:62

            .queryParam("org_type", config.getDingTalkOrgType())
            .queryParam("corpId", config.getDingTalkCorpId())
            .queryParam("exclusiveLogin", config.isDingTalkExclusiveLogin())
            .queryParam("exclusiveCorpId", config.getDingTalkExclusiveCorpId())
            .queryParam("state", getRealState(state))
            .build();
    }

    @Override
    public AuthToken getAccessToken(AuthCallback authCallback) {
        Map<String, String> params = new HashMap<>();
        params.put("grantType", "authorization_code");
        params.put("clientId", config.getClientId());
        params.put("clientSecret", config.getClientSecret());
        params.put("code", authCallback.getCode());
        String response = new HttpUtils(config.getHttpConfig()).post(this.source.accessToken(), JSONObject.toJSONString(params)).getBody();
        JSONObject accessTokenObject = JSONObject.parseObject(response);
        if (!accessTokenObject.containsKey("accessToken")) {
            throw new AuthException(JSONObject.toJSONString(response), source);
        }
        return AuthToken.builder()
            .accessToken(accessTokenObject.getString("accessToken"))
            .refreshToken(accessTokenObject.getString("refreshToken"))
            .expireIn(accessTokenObject.getIntValue("expireIn"))
            .corpId(accessTokenObject.getString("corpId"))
            .build();
    }

    @Override
    public AuthUser getUserInfo(AuthToken authToken) {
        HttpHeader header = new HttpHeader();
        header.add("x-acs-dingtalk-access-token", authToken.getAccessToken());

        String response = new HttpUtils(config.getHttpConfig()).get(this.source.userInfo(), null, header, false).getBody();
        JSONObject object = JSONObject.parseObject(response);

        authToken.setOpenId(object.getString("openId"));

View on GitHub (pinned to 694bbf1b01)

Solutions

  1. Read the thrown message - it is DingTalk's raw JSON, e.g. {"errcode":40035,"errmsg":"invalid code"} - and act on the errmsg.
  2. Ensure the code from authCallback.getCode() is exchanged exactly once, immediately after redirect (store state to dedupe).
  3. Verify the DingTalk app's clientId (AppKey)/clientSecret (AppSecret) match the current credentials.
  4. If errmsg mentions IP restrictions, add your server's egress IP to the DingTalk app's server whitelist.
Defensive patterns

Strategy: try-catch

Try / catch

try {
    return dingTalkRequest.getAccessToken(callback);
} catch (AuthException e) {
    // message is DingTalk's raw JSON body, e.g. {"errcode":40035,"errmsg":"invalid code"}
    log.warn("DingTalk token error: {}", e.getErrorMsg());
    if (e.getErrorMsg() != null && e.getErrorMsg().contains("invalid code")) {
        return reauthorizeResponse();
    }
    throw e;
}

Prevention

When it happens

Trigger: getAccessToken(AuthCallback) with a wrong clientId/clientSecret pair, an authorization code that was already redeemed or expired (DingTalk codes are single-use and short-lived), or a server-side error where DingTalk returns errcode without accessToken.

Common situations: User refreshes the callback page causing the code to be exchanged twice; DingTalk app credentials rotated; system clock skew; or dev environment reaching DingTalk through a proxy that mangles the POST body.

Related errors


AI-assisted analysis of justauth/JustAuth@694bbf1b01 (2026-08-14). Data as JSON: /api/errors/0387cbe18624a9ca. Report an issue: GitHub.