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

// Consent
grantPage.accept();

try {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No failures of the kind described have been observed on this PR's CI: the red checks are the two infra flakes documented at PR level (comment) — Test OLM installation failed with a 504 gateway timeout during the install step (run 41929270839), and Admin UI E2E (chromium) hit a flaky step reported by Playwright as 1 flaky (run 41929270979) — neither is related to this test or its cleanup ordering.

@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?

@Inf1mum

Inf1mum commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Both failures look like infra flakes unrelated to the change: (1) Test OLM installation failed with HTTPError: Unexpected HTTP response: 504 during the install step (run 41929270839) — a gateway timeout with no test output; (2) Admin UI E2E (chromium) failed in realm.spec.ts › should disable preview if json very long, with Playwright itself reporting 1 flaky (run 41929270979). I can't rerun the jobs without write access — happy to push an empty commit to re-trigger CI if that's easier.

@Inf1mum
Inf1mum force-pushed the fix/54606-device-grant-null-session branch from d0d3336 to 22255ce Compare November 13, 2026 11:49
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