diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94c8e0eb10..9d17f5ca86 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,14 +188,15 @@ jobs: echo "Test IDP authentication" - ADMIN_TOKEN=$(docker exec openam-idp bash -c \ - 'curl -sf \ + ADMIN_TOKEN=$(docker exec openam-idp bash -o pipefail -c \ + 'curl -sf -D - -o /dev/null \ --request POST \ --header "Content-Type: application/json" \ --header "X-OpenAM-Username: amadmin" \ --header "X-OpenAM-Password: ampassword" \ --data "{}" \ - http://openam.example.org:8080/openam/json/authenticate' | jq -r .tokenId) + http://openam.example.org:8080/openam/json/authenticate \ + | tr -d "\r" | sed -n "s/^[Ss]et-[Cc]ookie: *iPlanetDirectoryPro=\([^;]*\).*/\1/p" | grep . | tail -n1') docker inspect --format="{{json .State.Health.Status}}" openam-idp | grep -q \"healthy\" @@ -224,7 +225,7 @@ jobs: --header "X-OpenAM-Username: demo" \ --header "X-OpenAM-Password: changeit" \ --data "{}" \ - http://openam.example.org:8080/openam/json/authenticate' + http://openam.example.org:8080/openam/json/authenticate | grep -q successUrl' - name: Docker start with a dedicated OpenDJ container (SP) shell: bash @@ -281,13 +282,13 @@ jobs: echo "Test SP authentication" docker exec openam-sp bash -c \ - 'curl \ + 'curl -sf \ --request POST \ --header "Content-Type: application/json" \ --header "X-OpenAM-Username: amadmin" \ --header "X-OpenAM-Password: ampassword" \ --data "{}" \ - http://sp.mycompany.org:8080/openam/json/authenticate | grep tokenId' + http://sp.mycompany.org:8080/openam/json/authenticate | grep -q successUrl' docker inspect --format="{{json .State.Health.Status}}" openam-sp | grep -q \"healthy\" @@ -302,38 +303,46 @@ jobs: with: sparse-checkout: e2e - - name: UI Smoke Tests (Playwright) - HttpOnly disabled + - name: UI Smoke Tests (Playwright) - HttpOnly enabled (default) + # OpenAM now ships HttpOnly session cookies by default, so a freshly + # configured server already reports cookieHttpOnly=true. This stage runs + # the (mode-agnostic) XUI specs against that default. env: - EXPECT_COOKIE_HTTPONLY: "false" + EXPECT_COOKIE_HTTPONLY: "true" run: | cd e2e npm init -y npm install @playwright/test npx playwright install chromium --with-deps - npx playwright test --reporter=list + echo "verifying the freshly configured server reports cookieHttpOnly=true (the new default)" + curl -sf "http://openam.example.org:8080/openam/json/serverinfo/*" | jq -e '.cookieHttpOnly == true' + npx playwright test xui --reporter=list - - name: Enable HttpOnly session cookie on OpenAM IDP and restart + - name: Disable HttpOnly session cookie on OpenAM IDP and restart shell: bash run: | # com.sun.identity.cookie.httponly is read once at startup (static field # in CookieUtils) and SystemProperties gives JVM -D properties priority, - # so we inject it via Tomcat setenv.sh and restart the same container - # (its configured data dir is preserved across a restart). + # so we inject the non-default value via Tomcat setenv.sh and restart the + # same container (its configured data dir is preserved across a restart). docker exec openam-idp bash -c ' - echo "export CATALINA_OPTS=\"\$CATALINA_OPTS -Dcom.sun.identity.cookie.httponly=true\"" > "$CATALINA_HOME/bin/setenv.sh" + echo "export CATALINA_OPTS=\"\$CATALINA_OPTS -Dcom.sun.identity.cookie.httponly=false\"" > "$CATALINA_HOME/bin/setenv.sh" chmod +x "$CATALINA_HOME/bin/setenv.sh"' docker restart openam-idp echo "waiting for OpenAM IDP to be alive again..." timeout 3m bash -c 'until docker inspect --format="{{json .State.Health.Status}}" openam-idp | grep -q \"healthy\"; do sleep 10; done' - echo "verifying the server now reports cookieHttpOnly=true" - curl -sf "http://openam.example.org:8080/openam/json/serverinfo/*" | jq -e '.cookieHttpOnly == true' + echo "verifying the server now reports cookieHttpOnly=false" + curl -sf "http://openam.example.org:8080/openam/json/serverinfo/*" | jq -e '.cookieHttpOnly == false' - - name: UI Smoke Tests (Playwright) - HttpOnly enabled + - name: UI Smoke Tests (Playwright) - HttpOnly disabled + # The full suite (oauth2/saml) runs in the non-HttpOnly mode because those + # specs read the SSO tokenId from the /json/authenticate response body, + # which is suppressed in the default HttpOnly mode. env: - EXPECT_COOKIE_HTTPONLY: "true" + EXPECT_COOKIE_HTTPONLY: "false" run: | cd e2e - npx playwright test xui --reporter=list + npx playwright test --reporter=list - name: Upload failure artifacts uses: actions/upload-artifact@v7 @@ -389,13 +398,13 @@ jobs: " > conf.file && java -jar openam-configurator-tool*.jar --file conf.file' docker exec test-openam1 bash -c \ - 'curl \ + 'curl -sf \ --request POST \ --header "Content-Type: application/json" \ --header "X-OpenAM-Username: amadmin" \ --header "X-OpenAM-Password: ampassword" \ --data "{}" \ - http://openam1.example.org:8080/openam/json/authenticate | grep tokenId' + http://openam1.example.org:8080/openam/json/authenticate | grep -q successUrl' docker inspect --format="{{json .State.Health.Status}}" test-openam1 | grep -q \"healthy\" @@ -439,13 +448,13 @@ jobs: " > conf.file && java -jar openam-configurator-tool*.jar --file conf.file' docker exec test-openam2 bash -c \ - 'curl \ + 'curl -sf \ --request POST \ --header "Content-Type: application/json" \ --header "X-OpenAM-Username: amadmin" \ --header "X-OpenAM-Password: ampassword" \ --data "{}" \ - http://openam2.example.org:8080/openam/json/authenticate | grep tokenId' + http://openam2.example.org:8080/openam/json/authenticate | grep -q successUrl' docker inspect --format="{{json .State.Health.Status}}" test-openam2 | grep -q \"healthy\" @@ -487,13 +496,13 @@ jobs: " > conf.file && java -jar openam-configurator-tool*.jar --file conf.file' docker exec test-openam3 bash -c \ - 'curl \ + 'curl -sf \ --request POST \ --header "Content-Type: application/json" \ --header "X-OpenAM-Username: amadmin" \ --header "X-OpenAM-Password: ampassword" \ --data "{}" \ - http://openam3.example.org:8080/openam/json/authenticate | grep tokenId' + http://openam3.example.org:8080/openam/json/authenticate | grep -q successUrl' docker inspect --format="{{json .State.Health.Status}}" test-openam3 | grep -q \"healthy\" diff --git a/e2e/common/openam-commons.mjs b/e2e/common/openam-commons.mjs index 6f84272277..eb90570408 100644 --- a/e2e/common/openam-commons.mjs +++ b/e2e/common/openam-commons.mjs @@ -25,6 +25,11 @@ export async function getAdminToken(request) { return getAuthToken(request, ADMIN_USER, ADMIN_PASS) } +// Resolves the SSO tokenId from the /json/authenticate response body. Note this only works when the +// session cookie is NOT HttpOnly, or when org.openidentityplatform.openam.httponly.allowTokenInBody +// is enabled: in the default HttpOnly deployment the token is delivered solely via Set-Cookie and is +// not echoed in the body, so this helper returns undefined. Specs that rely on it must run against a +// server with HttpOnly disabled (see the CI matrix in .github/workflows/build.yml). export async function getAuthToken(request, username, password) { const resp = await request.post(`${OPENAM_BASE}/json/authenticate`, { headers: { diff --git a/e2e/saml/saml-test.spec.mjs b/e2e/saml/saml-test.spec.mjs index ddac492c8e..d3f9cd3bb8 100644 --- a/e2e/saml/saml-test.spec.mjs +++ b/e2e/saml/saml-test.spec.mjs @@ -115,9 +115,10 @@ test.describe("OpenAM XUI - Login flow", () => { // ── 7. Assert the SSO session cookie carries a SameSite attribute ─────── // GHSA-fpmh-vx4h-xc33: the iPlanetDirectoryPro SSO cookie ships with a SameSite attribute by - // default so it is not sent on cross-site requests. It is intentionally NOT HttpOnly: the XUI - // reads it from document.cookie (SessionToken.jsm / AMConfig.js / AuthNService.js) to track the - // session and set REST headers, so enabling HttpOnly by default would break XUI console login. + // default so it is not sent on cross-site requests. The check below only asserts the SameSite + // attribute; whether the cookie is HttpOnly is governed by com.sun.identity.cookie.httponly (on + // by default, and fully supported by the XUI). HttpOnly behaviour is covered by the xui-httponly + // spec. const cookies = await page.context().cookies(); const ssoCookie = cookies.find((c) => c.name === "iPlanetDirectoryPro"); expect(ssoCookie, "iPlanetDirectoryPro SSO cookie should be set").toBeTruthy(); diff --git a/e2e/xui/xui-self-registration.spec.mjs b/e2e/xui/xui-self-registration.spec.mjs new file mode 100644 index 0000000000..cae73d9e9f --- /dev/null +++ b/e2e/xui/xui-self-registration.spec.mjs @@ -0,0 +1,190 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions copyright [year] [name of copyright owner]". + * + * Copyright 2026 3A Systems, LLC. + */ + +/** + * OpenAM XUI - self-registration with auto-login + * + * A user who registers through the XUI with "auto-login" as the registration destination must end up + * logged in, and stay logged in across a page reload, in both session cookie modes: + * - HttpOnly off: the registration response carries the tokenId and the XUI writes the cookie; + * - HttpOnly on (the default): JavaScript cannot write the cookie, so the server sets it on the + * registration response and keeps the tokenId out of the body. + * + * The spec enables self-registration in the top level realm (no captcha, no email verification, + * no security questions) and deletes the user it registers. + */ + +import { test, expect } from "@playwright/test"; +import { OPENAM_BASE, ADMIN_USER, ADMIN_PASS } from "../common/openam-commons.mjs"; + +const SERVICE_URL = `${OPENAM_BASE}/json/realms/root/realm-config/services/selfService`; +const SERVICE_API = "protocol=1.0,resource=1.0"; +const REGISTRATION = { + userRegistrationEnabled: true, + userRegisteredDestination: "auto-login", + userRegistrationCaptchaEnabled: false, + userRegistrationEmailVerificationEnabled: false, + userRegistrationKbaEnabled: false, +}; + +async function getServerInfo(request) { + const resp = await request.get(`${OPENAM_BASE}/json/serverinfo/*`, { + headers: { "Accept-API-Version": "protocol=1.0,resource=1.0" }, + }); + expect(resp.ok(), "GET /json/serverinfo/* should succeed").toBeTruthy(); + return resp.json(); +} + +/** + * Logs in the administrator and returns the SSO token: from the body when the server echoes it, + * otherwise from the last non-empty session cookie (HttpOnly mode). + */ +async function getAdminSession(request, cookieName) { + const resp = await request.post(`${OPENAM_BASE}/json/authenticate`, { + headers: { + "Content-Type": "application/json", + "X-OpenAM-Username": ADMIN_USER, + "X-OpenAM-Password": ADMIN_PASS, + "Accept-API-Version": "resource=2.0, protocol=1.0", + }, + }); + expect(resp.ok(), "administrator authentication should succeed").toBeTruthy(); + const body = await resp.json(); + if (body.tokenId) { + return body.tokenId; + } + const token = resp.headersArray() + .filter((h) => h.name.toLowerCase() === "set-cookie") + .map((h) => h.value.split(";", 1)[0]) + .filter((pair) => pair.startsWith(`${cookieName}=`)) + .map((pair) => pair.substring(cookieName.length + 1)) + .filter((value) => value.length > 0) + .pop(); + expect(token, "administrator session must be in the body or the session cookie").toBeTruthy(); + return token; +} + +/** + * Enables self-registration with auto-login, creating the realm's self-service config if needed. The + * config comes in sections (generalConfig, userRegistration, ...); the service needs its key aliases, + * which a fresh config leaves empty, so the default keystore's self-service keys are filled in. + */ +async function enableAutoLoginRegistration(request, adminHeaders) { + const current = await request.get(SERVICE_URL, { headers: adminHeaders }); + const exists = current.status() !== 404; + const source = exists ? current : await request.post(`${SERVICE_URL}?_action=template`, { headers: adminHeaders }); + expect(source.ok(), `reading the self-service config: ${await source.text()}`).toBeTruthy(); + const config = await source.json(); + delete config._id; + delete config._rev; + delete config._type; + config.generalConfig = { + ...config.generalConfig, + encryptionKeyPairAlias: config.generalConfig?.encryptionKeyPairAlias || "selfserviceenctest", + signingSecretKeyAlias: config.generalConfig?.signingSecretKeyAlias || "selfservicesigntest", + }; + config.userRegistration = { ...config.userRegistration, ...REGISTRATION }; + const saved = exists + ? await request.put(SERVICE_URL, { + headers: { ...adminHeaders, "Content-Type": "application/json" }, data: config }) + : await request.post(`${SERVICE_URL}?_action=create`, { + headers: { ...adminHeaders, "Content-Type": "application/json" }, data: config }); + expect(saved.ok(), `saving the self-service config: ${await saved.text()}`).toBeTruthy(); +} + +/** Resolves the username of the session the browser's (auto-sent) cookie carries. */ +async function idFromSession(request) { + const resp = await request.post(`${OPENAM_BASE}/json/users?_action=idFromSession`, { + headers: { "Accept-API-Version": "protocol=1.0,resource=2.0" }, + }); + return resp.ok() ? (await resp.json()).id : null; +} + +test.describe("OpenAM XUI - self-registration", () => { + test("a user registered with auto-login is logged in and stays logged in after a reload", + async ({ page, context, request }) => { + const info = await getServerInfo(request); + const cookieName = info.cookieName ?? "iPlanetDirectoryPro"; + const httpOnly = info.cookieHttpOnly === true; + console.log(`Server reports cookieName=${cookieName}, cookieHttpOnly=${httpOnly}`); + + const adminHeaders = { + [cookieName]: await getAdminSession(request, cookieName), + "Accept-API-Version": SERVICE_API, + }; + await enableAutoLoginRegistration(request, adminHeaders); + + const username = `selfreg${Date.now()}`; + const password = "Selfreg-Passw0rd"; + try { + // ── 1. Register through the XUI ───────────────────────────────────── + await page.goto(`${OPENAM_BASE}/XUI/#register/`); + await expect(page.locator("#input-username")).toBeVisible({ timeout: 30_000 }); + await page.fill("#input-username", username); + await page.fill("#input-givenName", "Self"); + await page.fill("#input-sn", "Registered"); + // The password validators run asynchronously on keyup, which fill() does not fire, and a + // stale result for a shorter prefix can land last; a final keyup revalidates the whole value + for (const field of ["#input-password", "#input-confirmPassword"]) { + await page.locator(field).pressSequentially(password, { delay: 20 }); + await page.waitForTimeout(500); + await page.locator(field).press("End"); + } + const submit = page.locator("input[type=\"submit\"]"); + await expect(submit, "the form must validate before it can be submitted").toBeEnabled(); + + const registered = page.waitForResponse((resp) => + resp.url().includes("selfservice/userRegistration") + && resp.url().includes("_action=submitRequirements") + && resp.status() === 200, { timeout: 30_000 }); + await submit.click(); + const registration = await (await registered).json(); + + // ── 2. The session travels as the server intends in this mode ─────── + expect(registration.type, "registration must end in the auto-login stage").toBe("autoLoginStage"); + expect(registration.tag).toBe("end"); + if (httpOnly) { + expect(registration.additions?.tokenId, + "the registration response must not expose the session in HttpOnly mode").toBeFalsy(); + } else { + expect(registration.additions?.tokenId, "the XUI needs the tokenId to write the cookie") + .toBeTruthy(); + } + + // ── 3. The browser holds the session cookie with the server's HttpOnly flag ── + await page.waitForURL((url) => !url.hash.startsWith("#register"), { timeout: 30_000 }); + await expect.poll(async () => (await context.cookies()).find((c) => c.name === cookieName), + { message: `session cookie "${cookieName}" must be set`, timeout: 15_000 }).toBeTruthy(); + const session = (await context.cookies()).find((c) => c.name === cookieName); + expect(session.httpOnly, "cookie HttpOnly attribute must match the server mode").toBe(httpOnly); + + // ── 4. The new user is logged in, before and after a reload ───────── + expect(String(await idFromSession(page.request)).toLowerCase()).toBe(username.toLowerCase()); + await page.reload({ waitUntil: "networkidle" }); + expect(page.url(), "reload must not redirect to the login page").not.toContain("#login"); + expect(String(await idFromSession(page.request)).toLowerCase()).toBe(username.toLowerCase()); + } finally { + try { + const deleted = await request.delete(`${OPENAM_BASE}/json/realms/root/users/${username}`, { + headers: { ...adminHeaders, "Accept-API-Version": "protocol=1.0,resource=2.0" }, + }); + console.log(`Deleting ${username}: HTTP ${deleted.status()}`); + } catch (e) { + console.log(`Could not delete ${username}: ${e.message}`); + } + } + }); +}); diff --git a/openam-core-rest/src/test/java/org/forgerock/openam/core/rest/authn/RestAuthenticationHandlerTest.java b/openam-core-rest/src/test/java/org/forgerock/openam/core/rest/authn/RestAuthenticationHandlerTest.java index c3c344bbed..be5801c694 100644 --- a/openam-core-rest/src/test/java/org/forgerock/openam/core/rest/authn/RestAuthenticationHandlerTest.java +++ b/openam-core-rest/src/test/java/org/forgerock/openam/core/rest/authn/RestAuthenticationHandlerTest.java @@ -74,7 +74,12 @@ public class RestAuthenticationHandlerTest { private CoreServicesWrapper coreServicesWrapper; @BeforeMethod - public void setUp() { + public void setUp() throws Exception { + + // Establish the token-readable baseline (HttpOnly off) for every test, independent of the + // production default of com.sun.identity.cookie.httponly. Tests that exercise HttpOnly mode + // opt in explicitly via setCookieHttpOnly(true) and reset it afterwards. + setCookieHttpOnly(false); loginAuthenticator = mock(LoginAuthenticator.class); restAuthCallbackHandlerManager = mock(RestAuthCallbackHandlerManager.class); diff --git a/openam-documentation/openam-doc-source/src/main/asciidoc/admin-guide/chap-securing.adoc b/openam-documentation/openam-doc-source/src/main/asciidoc/admin-guide/chap-securing.adoc index 8b4fd5ad69..7b071245f4 100644 --- a/openam-documentation/openam-doc-source/src/main/asciidoc/admin-guide/chap-securing.adoc +++ b/openam-documentation/openam-doc-source/src/main/asciidoc/admin-guide/chap-securing.adoc @@ -110,11 +110,11 @@ To configure OpenAM server to use secure cookies, in the OpenAM console, navigat + HttpOnly cookies are meant to be transmitted only over HTTP and HTTPS, and not through non-HTTP methods, such as JavaScript functions. + -You can configure the OpenAM server to use HttpOnly cookies by navigating to Configure > Server Defaults > Advanced, and setting the `com.sun.identity.cookie.httponly` property's value to `true`. Save your changes. Both the classic UI and the XUI support HttpOnly session cookies: when HttpOnly is enabled, the XUI relies on the automatically sent cookie instead of reading the token from JavaScript, and the `/json/authenticate` response delivers the token only through the `Set-Cookie` header rather than echoing `tokenId` in the response body. To keep returning `tokenId` in the body as well (for example, for non-browser or raw-REST integrations), set `org.openidentityplatform.openam.httponly.allowTokenInBody` to `true`. Note that doing so re-exposes the token to scripts on the OpenAM origin, so leave it at its default of `false` unless an integration requires it. For both properties, see `com.sun.identity.cookie.httponly` and `org.openidentityplatform.openam.httponly.allowTokenInBody` in xref:../reference/chap-config-ref.adoc#chap-config-ref["Configuration Reference"] in the __Reference__. +OpenAM marks its cookies `HttpOnly` by default (`com.sun.identity.cookie.httponly=true`, under Configure > Server Defaults > Advanced). Both the classic UI and the XUI support HttpOnly session cookies: the XUI relies on the automatically sent cookie instead of reading the token from JavaScript, and the `/json/authenticate` response delivers the token only through the `Set-Cookie` header rather than echoing `tokenId` in the response body. To keep returning `tokenId` in the body as well (for example, for non-browser or raw-REST integrations), set `org.openidentityplatform.openam.httponly.allowTokenInBody` to `true`. Note that doing so re-exposes the token to scripts on the OpenAM origin, so leave it at its default of `false` unless an integration requires it. Set `com.sun.identity.cookie.httponly` to `false` to disable HttpOnly cookies entirely. For both properties, see `com.sun.identity.cookie.httponly` and `org.openidentityplatform.openam.httponly.allowTokenInBody` in xref:../reference/chap-config-ref.adoc#chap-config-ref["Configuration Reference"] in the __Reference__. + Both properties are read once when the server starts, so you must restart the OpenAM server for a change to either of them to take effect. + -`com.sun.identity.cookie.httponly` defaults to `false` to preserve the behaviour of existing integrations that read the session cookie, or the `tokenId` from the authentication response body, from script. Enabling HttpOnly is recommended for browser-facing deployments: an HttpOnly session cookie prevents a cross-site scripting flaw on the OpenAM origin from reading a replayable session token. +An HttpOnly session cookie prevents a cross-site scripting flaw on the OpenAM origin from reading a replayable session token, which is why it is enabled by default. + One known limitation applies: the OpenID Connect session management OP iframe (`/oauth2/connect/checkSession`) computes the browser state by reading the session cookie from JavaScript, so it cannot observe the session while `HttpOnly` is enabled. + diff --git a/openam-documentation/openam-doc-source/src/main/asciidoc/deployment-planning/chap-deployments.adoc b/openam-documentation/openam-doc-source/src/main/asciidoc/deployment-planning/chap-deployments.adoc index 060258029e..5e932b5532 100644 --- a/openam-documentation/openam-doc-source/src/main/asciidoc/deployment-planning/chap-deployments.adoc +++ b/openam-documentation/openam-doc-source/src/main/asciidoc/deployment-planning/chap-deployments.adoc @@ -226,7 +226,7 @@ When you first configure OpenAM, there are many options to evaluate, plus a numb * On a server that includes OpenAM Console, all the endpoints defined in the Web application descriptor, `WEB-INF/web.xml`, are available for use. -* To prevent cross-site scripting attacks, you can configure session cookies as HTTP Only by setting the property `com.sun.identity.cookie.httponly=true`. This property prevents third-party scripts from accessing the session cookie. Both the classic UI and the XUI support HttpOnly session cookies, so enabling it is recommended for browser-facing deployments; it defaults to `false` only to preserve the behaviour of existing integrations that read the session cookie, or the `tokenId` from the authentication response body, from script. Two consequences to plan for: the OpenID Connect session management OP iframe (`/oauth2/connect/checkSession`) reads the session cookie from JavaScript and so cannot observe the session while HttpOnly is enabled, and the property is read only at server startup. See xref:../admin-guide/chap-securing.adoc#secure-communications["Securing Communications"] in the __Administration Guide__. By default, OpenAM also sets `org.openidentityplatform.openam.cookie.samesite=Lax` to reduce cross-site request forgery (CSRF) exposure. +* To prevent cross-site scripting attacks, OpenAM marks session cookies as HTTP Only by default (`com.sun.identity.cookie.httponly=true`). This property prevents third-party scripts from accessing the session cookie. Both the classic UI and the XUI support HttpOnly session cookies out of the box; set the property to `false` only if you need the SSO token, or the `tokenId` from the authentication response body, to be readable from script. Two consequences to plan for: the OpenID Connect session management OP iframe (`/oauth2/connect/checkSession`) reads the session cookie from JavaScript and so cannot observe the session while HttpOnly is enabled, and the property is read only at server startup. See xref:../admin-guide/chap-securing.adoc#secure-communications["Securing Communications"] in the __Administration Guide__. By default, OpenAM also sets `org.openidentityplatform.openam.cookie.samesite=Lax` to reduce cross-site request forgery (CSRF) exposure. * You can deploy a reverse proxy within delimitarized zone (DMZ) firewalls to limit exposure of service URLs to the end user as well as block access to back end configuration and user data stores to unauthorized users. diff --git a/openam-documentation/openam-doc-source/src/main/asciidoc/dev-guide/chap-client-dev.adoc b/openam-documentation/openam-doc-source/src/main/asciidoc/dev-guide/chap-client-dev.adoc index f858f073fa..fe77a9abb7 100644 --- a/openam-documentation/openam-doc-source/src/main/asciidoc/dev-guide/chap-client-dev.adoc +++ b/openam-documentation/openam-doc-source/src/main/asciidoc/dev-guide/chap-client-dev.adoc @@ -944,7 +944,7 @@ $ curl https://openam.example.com:8443/openam/json/serverinfo/* "protectedUserAttributes": [], "cookieName": "iPlanetDirectoryPro", "secureCookie": false, - "cookieHttpOnly": false, + "cookieHttpOnly": true, "forgotPassword": "false", "forgotUsername": "false", "kbaEnabled": "false", diff --git a/openam-documentation/openam-doc-source/src/main/asciidoc/reference/chap-config-ref.adoc b/openam-documentation/openam-doc-source/src/main/asciidoc/reference/chap-config-ref.adoc index 7270569126..521cfe71a5 100644 --- a/openam-documentation/openam-doc-source/src/main/asciidoc/reference/chap-config-ref.adoc +++ b/openam-documentation/openam-doc-source/src/main/asciidoc/reference/chap-config-ref.adoc @@ -5639,11 +5639,14 @@ When set to `true`, mark cookies as HttpOnly to prevent scripts and third-party + Both the classic UI and the XUI support HttpOnly session cookies. When HttpOnly is enabled, the XUI relies on the automatically sent cookie instead of reading the token from JavaScript, and a successful `/json/authenticate` response delivers the token only through the `Set-Cookie` header (the `tokenId` is, by default, no longer returned in the response body). See `org.openidentityplatform.openam.httponly.allowTokenInBody` to control that behaviour. The OpenID Connect session management OP iframe (`/oauth2/connect/checkSession`) is a known exception: it reads the session cookie from JavaScript to compute the browser state, and so cannot observe the session while HttpOnly is enabled. ++ +Only the value `false` (in any letter case) disables HttpOnly cookies; an empty or any other value leaves them enabled. + + Changes to this property do not take effect until you restart the OpenAM server. + -Default: `false` +Default: `true` `com.sun.identity.enableUniqueSSOTokenCookie`:: If `true`, then OpenAM is using protection against cookie hijacking. diff --git a/openam-federation/openam-idpdiscovery-war/src/main/webapp/Configurator.jsp b/openam-federation/openam-idpdiscovery-war/src/main/webapp/Configurator.jsp index 97d8304e6a..a042e32441 100644 --- a/openam-federation/openam-idpdiscovery-war/src/main/webapp/Configurator.jsp +++ b/openam-federation/openam-idpdiscovery-war/src/main/webapp/Configurator.jsp @@ -28,6 +28,7 @@ <%-- Portions Copyrighted 2012-2013 ForgeRock Inc Portions Copyrighted 2012 Open Source Solution Technology Corporation + Portions Copyrighted 2026 3A Systems, LLC --%> @@ -207,8 +208,8 @@ java.util.Properties"