Skip to content

Support HttpOnly session cookie in XUI#1036

Open
vharseko wants to merge 7 commits into
OpenIdentityPlatform:masterfrom
vharseko:issues/xui
Open

Support HttpOnly session cookie in XUI#1036
vharseko wants to merge 7 commits into
OpenIdentityPlatform:masterfrom
vharseko:issues/xui

Conversation

@vharseko

@vharseko vharseko commented Jun 4, 2026

Copy link
Copy Markdown
Member

Summary

Make the OpenAM XUI (login / admin console, openam-ui-ria) work correctly when
the session cookie (e.g. iPlanetDirectoryPro) is issued with the HttpOnly
flag.

With HttpOnly=true the browser does not expose the cookie to JavaScript
(document.cookie), so the XUI could no longer read the session token. Because
the XUI relied on reading the token client‑side (SessionToken.get()) for session
detection, upgrade, realm‑change checks and logout, it treated authenticated users
as logged out. This PR removes that hard dependency: the token is kept in memory
for the page lifetime, and where a token is not client‑readable the server resolves
the session from the automatically‑sent HttpOnly cookie instead.

The change is backwards compatible — when HttpOnly is disabled the behaviour is
unchanged.

Motivation

HttpOnly session cookies are a baseline security hardening measure (mitigates
token theft via XSS). Previously enabling com.sun.identity.cookie.httponly=true
broke the XUI, forcing operators to choose between the XUI and this protection.

Changes

Server

  • ServerInfoResource — expose a new cookieHttpOnly flag in
    /json/serverinfo/* so the XUI can detect the mode at runtime.

XUI (openam-ui-ria)

  • SessionToken.jsm
    • Detect HttpOnly mode via Configuration.globalData.cookieHttpOnly.
    • In HttpOnly mode keep the token in memory for the page lifetime instead of
      reading/writing the cookie; get() returns the in‑memory token or an
      HTTP_ONLY_SESSION_TOKEN sentinel so callers know a session may exist.
    • set() / remove() become no‑ops on the cookie (it is managed server‑side).
    • New helpers: isHttpOnly() and isResolvable(token).
  • SessionService.jsm — for getSessionInfo / logout, omit the tokenId
    query parameter when the token is not client‑readable so the server resolves the
    session from the request cookie; suppress the expected 400 / 401 for the
    no‑token case.
  • AuthNService.js — only send sessionUpgradeSSOTokenId when the token is
    resolvable.
  • SiteConfigurationService.js — tolerate a missing/invalid session in
    checkForDifferences() and continue rendering instead of stalling.

Tests / CI

  • e2e/xui/xui-httponly.spec.mjs — mode‑agnostic Playwright spec. It reads
    the server mode from /json/serverinfo/* and asserts that the cookie HttpOnly
    attribute, document.cookie visibility, login + idFromSession detection, and
    server‑side logout invalidation all match that mode.
  • .github/workflows/build.yml — run the UI smoke tests in both modes:
    the existing default run (HttpOnly disabled) and a second run after restarting
    the IDP container with -Dcom.sun.identity.cookie.httponly=true
    (HttpOnly enabled).

Docs

  • dev-guide/chap-client-dev.adoc — document the new cookieHttpOnly field
    in the serverinfo example.

How to enable

Set the system property and restart OpenAM (it is read once at startup):

-Dcom.sun.identity.cookie.httponly=true

Or via Configuration → Servers and Sites → Server → Advanced:
com.sun.identity.cookie.httponly = true (restart required).

Verify:

curl -s "http://openam.example.org:8080/openam/json/serverinfo/*" | jq .cookieHttpOnly

Testing

Automated (Playwright), runs in both modes:

cd e2e && npm i @playwright/test && npx playwright install chromium
# default (HttpOnly disabled)
EXPECT_COOKIE_HTTPONLY=false npx playwright test xui
# against a server started with -Dcom.sun.identity.cookie.httponly=true
EXPECT_COOKIE_HTTPONLY=true  npx playwright test xui

The spec verifies, for the active mode:

  1. serverinfo.cookieHttpOnly matches the expected mode.
  2. XUI form login succeeds.
  3. The session cookie carries the expected HttpOnly attribute.
  4. document.cookie visibility is the inverse of HttpOnly.
  5. The session is detected via idFromSession (no client‑side cookie read).
  6. Logout invalidates the session server‑side (validated in both modes).

Manual:

  1. Enable HttpOnly, log in to the XUI.
  2. DevTools → Application → Cookies: iPlanetDirectoryPro shows the HttpOnly
    flag; document.cookie does not contain the token.
  3. The console / profile loads and logout works.

Backwards compatibility

  • Default behaviour (HttpOnly disabled) is unchanged.
  • No configuration migration required; the new serverinfo field is additive.

Affected modules

  • openam-core-rest
  • openam-ui/openam-ui-ria
  • openam-documentation
  • e2e, CI (.github/workflows/build.yml)

vharseko added 4 commits June 4, 2026 14:21
Allow the XUI to work correctly when the OpenAM session cookie
(iPlanetDirectoryPro) is issued with the HttpOnly flag, where the token
cannot be read from JavaScript via document.cookie.

Server
- ServerInfoResource: expose the new "cookieHttpOnly" flag in
  /json/serverinfo/* so the XUI can detect the mode at runtime.

XUI
- SessionToken: detect HttpOnly mode via Configuration.globalData.cookieHttpOnly.
  When enabled, keep the token in memory for the page lifetime instead of
  reading/writing the cookie, return an HTTP_ONLY_SESSION_TOKEN sentinel from
  get(), and add isHttpOnly()/isResolvable() helpers. set()/remove() become
  no-ops on the cookie since it is managed server-side.
- SessionService: omit the tokenId query param for getSessionInfo/logout when
  the token is not client-readable, so the server resolves the session from the
  automatically-sent HttpOnly cookie; suppress expected 400/401 for the
  no-token case.
- AuthNService: only send sessionUpgradeSSOTokenId when the token is resolvable.
- SiteConfigurationService: tolerate a missing/invalid session in
  checkForDifferences() and continue rendering instead of stalling.

Tests / CI
- e2e/xui/xui-httponly.spec.mjs: mode-agnostic Playwright spec that asserts the
  cookie HttpOnly attribute, JS visibility, login/idFromSession detection and
  logout match the server mode.
- build.yml: run the UI smoke tests in both modes — default (HttpOnly disabled)
  and after restarting the IDP with
  -Dcom.sun.identity.cookie.httponly=true (HttpOnly enabled).

Docs
- dev-guide: document the new cookieHttpOnly field in the serverinfo example.
The HttpOnly spec waited for "#loginButton", which does not exist in this
XUI build, so the login click hung until the global test timeout.

- Match the submit button by type, like the working SAML spec, and keep the
  id as a fallback: "#loginButton, input[type=submit], button[type=submit]".
- Click the first visible match to avoid a strict-mode violation when several
  elements match.
Stabilize the XUI HttpOnly Playwright spec so it passes against the real
XUI build with the session cookie both with and without the HttpOnly flag.

- Login: match the submit button by type with the id as a fallback
  ("#loginButton, input[type=submit], button[type=submit]") and click the
  first visible match, since "#loginButton" is absent in this build.
- Logout: XUI redirects to "#loggedOut/" (not "#login/"), so accept either
  route when waiting for the session to end.
- Logout assertion: verify the session is invalidated server-side via
  idFromSession instead of checking the browser cookie. In HttpOnly mode JS
  cannot clear the cookie and the REST logout may not emit a Set-Cookie, so a
  stale-but-dead cookie can linger; server-side invalidation holds in both modes.
@vharseko vharseko requested a review from maximthomas June 4, 2026 18:18
@maximthomas

Copy link
Copy Markdown
Contributor

I suggest this commit 3913a59 can be reverted for now.
Please also add e2e test to ensure a user stays logged in into admin console after reloading page in a browser.

vharseko added 2 commits June 4, 2026 22:36
Reverts the workaround from 3913a59 that disabled the XUI entirely when the session cookie is HttpOnly. The XUI now supports HttpOnly session cookies, so it must stay enabled in that mode.
Add an e2e check that an admin remains authenticated in the console after a full browser page reload (the reload drops any in-memory token, so the session must be re-detected from the auto-sent cookie). Extract shared loginViaXui()/idFromSession() helpers and update the PR description.
@vharseko vharseko requested a review from tsujiguchitky June 5, 2026 05:26
@tsujiguchitky

Copy link
Copy Markdown
Contributor

Nice work on the HttpOnly support - login/reload/logout look solid.

One thing I wanted to check with you: does agent-driven session upgrade (step-up) still work in HttpOnly mode?

The way I read it, the step-up redirect is a fresh page load, so inMemoryToken is empty and XUI can't send sessionUpgradeSSOTokenId. And server-side that param looks like the only source for the session to upgrade - LoginAuthenticator resolves it via getExistingValidSSOToken(new SessionID(getSSOTokenId())) and never reads the cookie. So wouldn't it fall through to a brand-new login instead of an upgrade (old session orphaned, properties/sessionHandle lost, composite-advice cases possibly looping)?

If that's right, would it make sense to fall back to the session cookie as the upgrade target in the REST authenticate flow when sessionUpgradeSSOTokenId is absent?

…arget

When the session cookie is configured as HttpOnly, the XUI cannot read the
tokenId from JavaScript and therefore cannot send the sessionUpgradeSSOTokenId
query parameter on an agent-driven session upgrade (step-up), which is performed
via a fresh page load with an empty in-memory token. Server-side that parameter
was the only source for the session to upgrade, so the request fell through to a
brand-new login: the existing session was orphaned, its properties/sessionHandle
were lost, and composite-advice step-up could loop.

RestAuthenticationHandler now resolves the upgrade target from the auto-sent
HttpOnly session cookie when sessionUpgradeSSOTokenId is absent. The fallback is
limited to the HttpOnly deployment mode (CookieUtils.isCookieHttpOnly()), so the
behaviour of all other token-readable deployments is unchanged.

Also clean up leftover merge-conflict markers in the file's license header.

Changes:
- RestAuthenticationHandler: add resolveSessionUpgradeTarget() and apply it in
  the authenticate flow before resolving the auth index.
- RestAuthenticationHandlerTest: cover the cookie fallback in HttpOnly mode and
  the unchanged behaviour when a token is supplied / HttpOnly is off.
- e2e/xui/xui-httponly.spec.mjs: add a step-up scenario asserting the existing
  session is recognised as the upgrade target (no fresh authId/callbacks) in
  HttpOnly mode; consolidated from the separate session-upgrade spec.
@vharseko vharseko requested a review from maximthomas June 9, 2026 11:11
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.

3 participants