8000 Device authorization grant: return invalid_grant instead of HTTP 500 when the client session has expired by Inf1mum · Pull Request #54662 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
  • Device authorization grant: return invalid_grant instead of HTTP 500 when the client session has expired - #54662

    Open
    Inf1mum wants to merge 1 commit into
    keycloak:mainfrom
    Inf1mum:fix/54606-device-grant-null-session
    Open

    Inf1mum wants to merge 1 commit into
    keycloak:mainfrom
    Inf1mum:fix/54606-device-grant-null-session

    Conversation

    @Inf1mum
    @Inf1mum Inf1mum commented Nov 4, 2026
    Copy link
    Copy Markdown
    Contributor

    Fixes #54606

    Problem

    When a client uses the device authorization grant and its own client session idle timeout is shorter than the user's SSO session, the token request that follows an approved device code fails with HTTP 500 and an uncaught NullPointerException:

    java.lang.NullPointerException: Cannot invoke "org.keycloak.models.AuthenticatedClientSessionModel.getClient()" because "clientSession" is null
    	at org.keycloak.protocol.oidc.grants.device.DeviceGrantType.process(DeviceGrantType.java:330)
    

    In DeviceGrantType.process, the user session is first looked up with getUserSessionIfClientExists(...), which returns null when the client session is gone (for example because the client session idle timeout has elapsed), so the code falls back to getUserSession(...), which still finds the user session. The client session is then read with getAuthenticatedClientSessionByClient(client.getId()) without a null check and dereferenced.

    Fix

    Add a null check for the client session in DeviceGrantType.process and, when it is missing, return the standard invalid_grant / "Session not active" error (HTTP 400), consistent with the existing check a few lines below that handles an invalid user session (AuthenticationManager.isSessionValid). The event is recorded with the existing SESSION_EXPIRED error and a Session not active reason detail, mirroring the user-session-expired path.

    The order of operations is unchanged, in particular the device code is still removed before the session checks, so the device restarts the whole flow (as described in the issue).

    Test

    Added OAuth2DeviceAuthorizationGrantTest.testExpiredClientSession, modeled on the neighboring cases of the same class: the public device client is given a client session idle timeout of 5 seconds, the device code is approved in the browser, then the token request is polled after the client session has expired but while the user session is still alive. The test expects HTTP 400 with invalid_grant / "Session not active" (previously this hit the 500 NPE).

    Notes

    • The integration test has not been run locally (requires building the Keycloak distribution); CI will run it.

    …ired
    
    Signed-off-by: Inf1mum <vadim.lazuk@mail.ru>
    @Inf1mum
    Inf1mum requested review from a team as code owners November 4, 2026 22:54
    Copilot AI balanced review requested due to automatic review settings November 4, 2026 22:54
    Copilot AI left a comment
    Copy link
    Copy Markdown
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    🟡 Changes recommended

    Failures before the try block can skip cleanup, leaving the testing time service installed and potentially the client timeout altered, which can break later time-based tests.

    1 open issue
    What changed in this PR

    This PR addresses #54606 by returning a standard OAuth error when a device authorization request encounters an expired client session, instead of failing with HTTP 500.

    Changes:

    • Guard against a missing client session and return HTTP 400 with invalid_grant / “Session not active.”
    • Record a session-expired event while preserving device-code consumption order.
    • Add regression coverage for a client session expiring before the user’s SSO session.
    File Description
    testsuite/​integration-arquillian/​tests/​base/​src/​test/​java/​org/​keycloak/​testsuite/​oauth/​OAuth2DeviceAuthorizationGrantTest.java Adds the expired-client-session regression test.
    services/​src/​main/​java/​org/​keycloak/​protocol/​oidc/​grants/​device/​DeviceGrantType.java Handles a missing client session with an OAuth error.

    Review effort: Balanced

    @mposolda mposolda self-assigned this Nov 5, 2026
    @Inf1mum
    Inf1mum commented Nov 5, 2026
    Copy link
    Copy Markdown
    Contributor Author

    The two failed checks are unrelated to this PR:

    • Admin UI E2E (chromium): flaky UI test (masthead/realm.spec.ts:124) that passed on retry initial #1 within the same run; the PR touches only Java code. The same check failed for other PRs in the same window (e.g. run 42043110412, kerberos.spec.ts).
    • Test OLM installation: infrastructure failure — the k8s cluster never came up (localhost:8080 refused, oc: command not found); the identical failure hit run 42045169954 from another PR at the same time.

    The Java suites are fully green, including OAuth2DeviceAuthorizationGrantTest (30 tests, 0 failures) which contains the new testExpiredClientSession. Could someone re-run the two failed jobs?

    @Inf1mum
    Inf1mum commented Nov 6, 2026
    Copy link
    Copy Markdown
    Contributor Author

    Friendly follow-up — the two failed checks (Admin UI E2E (chromium) flaky test, Test OLM installation k8s infra failure) are still red and a re-run hasn't happened since my comment yesterday. Both are unrelated to this PR (details and evidence for parallel failures on other PRs are in my previous comment). Could someone re-run the failed jobs?

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Labels

    None yet

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    Device authorization grant returns HTTP 500 when the client session has expired but the user session is still alive

    3 participants

    0