{"record":{"id":"58ee5d67f2fe2178","repo":"justauth/JustAuth","slug":"5009","errorCode":"5009","errorMessage":"Illegal state","messagePattern":"Illegal state","errorType":"validation","errorClass":"AuthException","httpStatus":null,"severity":"error","filePath":"src/main/java/me/zhyd/oauth/utils/AuthChecker.java","lineNumber":108,"sourceCode":"\n    /**\n     * 校验回调传回的{@code state}，为空或者不存在\n     * <p>\n     * {@code state}不存在的情况只有两种：\n     * 1. {@code state}已使用，被正常清除\n     * 2. {@code state}为前端伪造，本身就不存在\n     *\n     * @param state          {@code state}一定不为空\n     * @param source         {@code source}当前授权平台\n     * @param authStateCache {@code authStateCache} state缓存实现\n     */\n    public static void checkState(String state, AuthSource source, AuthStateCache authStateCache) {\n        // 推特平台不支持回调 code 和 state\n        if (source == AuthDefaultSource.TWITTER) {\n            return;\n        }\n        if (StringUtils.isEmpty(state) || !authStateCache.containsKey(state)) {\n            throw new AuthException(AuthResponseStatus.ILLEGAL_STATUS, source);\n        }\n    }\n}\n","sourceCodeStart":90,"sourceCodeEnd":112,"githubUrl":"https://github.com/justauth/JustAuth/blob/694bbf1b010d93404e3bfb4824d90e9ddfaebebb/src/main/java/me/zhyd/oauth/utils/AuthChecker.java#L90-L112","documentation":"Thrown by AuthChecker.checkState: the state echoed back by the provider is empty, or it is not present in the configured AuthStateCache. The state parameter is JustAuth's CSRF protection — checkState sees exactly two failure modes: the state was already consumed and evicted, or it was never issued by this server (forged). Code 5009 (ILLEGAL_STATUS). Twitter is exempt.","triggerScenarios":"Calling request.login(callback)/getAccessToken after the user's authorize redirect lands, when: the state query param is missing/mangled; the cached state expired between authorize and callback; the app restarted and the default in-memory AuthDefaultStateCache lost everything; the app runs multiple instances behind a load balancer and the callback hit an instance that never stored the state.","commonSituations":"Local dev with frequent restarts (every pending OAuth round-trip breaks); horizontal scaling without a shared cache implementation; browser back-button or double-click re-using an already-consumed state; authStateCache implementations with aggressive TTL; long user dwell time on the provider consent page exceeding cache expiry.","solutions":["If you run more than one instance or restart often, supply a shared/durable AuthStateCache (Redis-backed) instead of the default in-memory one, and set the timeout comfortably longer than a realistic authorize->callback round trip.","On failure, do not retry with the same state — re-issue request.authorize(state) to start a fresh round trip.","Ensure state survives the full redirect: check it is not stripped by your frontend router, proxy, or callback URL normalization.","Guard the callback handler: if state is absent from the request, redirect the user to a new authorize URL rather than calling into the SDK."],"exampleFix":"// before\nAuthRequest request = new AuthGithubRequest(config);\n// default in-memory AuthStateCache is used; restarts/scaling break state\n\n// after\nAuthStateCache redisCache = new AuthDefaultStateCache() {\n    // replace with Redis/DB-backed implementation for multi-instance deploys\n};\nAuthRequest request = new AuthGithubRequest(config, redisCache);","handlingStrategy":"validation","validationCode":"String state = callback.getState();\nif (StringUtils.isEmpty(state) || !stateCache.containsKey(state)) {\n    // expired, consumed, or forged — restart the flow instead of calling login()\n    return redirect(authRequest.authorize(StateUtils.createState()));\n}","typeGuard":null,"tryCatchPattern":"catch (AuthException e) { if (e.getErrorCode() == AuthResponseStatus.ILLEGAL_STATUS.getCode()) { /* re-issue authorize URL; never reuse the state */ } }","preventionTips":["Use a shared Redis-backed AuthStateCache in any multi-instance or restart-prone deployment.","Set state TTL well above the worst-case user dwell time on the consent page.","Treat a failed state as a security event (possible CSRF/forgery), log it, then restart the flow."],"tags":["java","justauth","oauth","csrf","state-cache","session"],"backgroundTag":null,"analyzedSha":"694bbf1b010d93404e3bfb4824d90e9ddfaebebb","analyzedAt":"2026-08-14T15:16:59.945Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}