Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 32 additions & 23 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,14 @@ jobs:
echo "Test IDP authentication"

ADMIN_TOKEN=$(docker exec openam-idp bash -c \
'curl -sf \
'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\"

Expand Down Expand Up @@ -210,7 +211,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
Expand Down Expand Up @@ -267,13 +268,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\"

Expand All @@ -288,38 +289,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
Expand Down Expand Up @@ -375,13 +384,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\"

Expand Down Expand Up @@ -425,13 +434,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\"

Expand Down Expand Up @@ -473,12 +482,12 @@ 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\"
5 changes: 5 additions & 0 deletions e2e/common/openam-commons.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 4 additions & 3 deletions e2e/saml/saml-test.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ 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. Note that the XUI reads the `iPlanetDirectoryPro` SSO cookie from the browser to track the session, so enabling `HttpOnly` breaks XUI console login; the property therefore defaults to `false`. Set it to `true` only in deployments that do not rely on the XUI.
OpenAM marks its cookies `HttpOnly` by default (`com.sun.identity.cookie.httponly=true`, under Configure > Server Defaults > Advanced). The XUI fully supports this mode: it relies on the automatically sent cookie instead of reading the SSO token from JavaScript. In HttpOnly mode the token is delivered only through the `Set-Cookie` header and is, by default, no longer returned in the `/json/authenticate` response body; non-browser or raw-REST integrations that need the token in the body can opt back in with `org.openidentityplatform.openam.httponly.allowTokenInBody=true`. Set `com.sun.identity.cookie.httponly` to `false` to disable HttpOnly cookies entirely.
+
To reduce cross-site request forgery (CSRF) exposure, OpenAM also sets a `SameSite` attribute on its cookies. A standard installation ships with `org.openidentityplatform.openam.cookie.samesite=Lax`. You can change this to `Strict` or `None` (the latter requires secure cookies) under Configure > Server Defaults > Security > Cookie.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. Because the XUI reads the `iPlanetDirectoryPro` SSO cookie from the browser, this property defaults to `false`; enable it only in deployments that do not use the XUI. 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`), which prevents third-party scripts from accessing the session cookie. The XUI supports this mode out of the box; set the property to `false` only if you need the SSO token to be readable from `document.cookie`. 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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5624,7 +5624,7 @@ 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.

+
Default: `false`
Default: `true`

`com.sun.identity.enableUniqueSSOTokenCookie`::
If `true`, then OpenAM is using protection against cookie hijacking.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<%--
Portions Copyrighted 2012-2013 ForgeRock Inc
Portions Copyrighted 2012 Open Source Solution Technology Corporation
Portions Copyrighted 2026 3A Systems, LLC
--%>

<html>
Expand Down Expand Up @@ -207,8 +208,8 @@ java.util.Properties"
<tr>
<td>HTTP-Only Cookie:</td>
<td>
<input type="radio" name="httpOnlyCookie" value="true" >True
<input type="radio" name="httpOnlyCookie" value="false" CHECKED>False
<input type="radio" name="httpOnlyCookie" value="true" CHECKED>True
<input type="radio" name="httpOnlyCookie" value="false" >False
</td>
</tr>
<tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

/**
* Portions Copyrighted 2013 ForgeRock, Inc.
* Portions Copyrighted 2025 3A Systems LLC.
* Portions Copyrighted 2021-2026 3A Systems LLC.
*/
package com.sun.identity.saml2.idpdiscovery;

Expand Down Expand Up @@ -59,11 +59,8 @@ public class CookieUtils {
SystemProperties.get(IDPDiscoveryConstants.AM_COOKIE_SECURE).
equalsIgnoreCase("true"));

static boolean cookieHttpOnly =
(SystemProperties.get(IDPDiscoveryConstants.AM_COOKIE_HTTPONLY)
!= null) &&
(SystemProperties.get(IDPDiscoveryConstants.AM_COOKIE_HTTPONLY).
equalsIgnoreCase("true"));
static boolean cookieHttpOnly = !"false".equalsIgnoreCase(
SystemProperties.get(IDPDiscoveryConstants.AM_COOKIE_HTTPONLY));

static String cookieSameSite = SystemPropertiesManager.get(
Constants.AM_COOKIE_SAMESITE);
Expand Down
2 changes: 1 addition & 1 deletion openam-mcp-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<parent>
<groupId>org.openidentityplatform.openam</groupId>
<artifactId>openam</artifactId>
<version>16.0.7-SNAPSHOT</version>
<version>16.1.1</version>
</parent>
<artifactId>openam-mcp-server</artifactId>
<name>OpenAM MCP Server</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ com.iplanet.am.profile.host=%SERVER_HOST%
com.iplanet.am.profile.port=%SERVER_PORT%
com.sun.identity.client.notification.url=%SERVER_PROTO%://%SERVER_HOST%:%SERVER_PORT%/%SERVER_URI%/notificationservice
com.iplanet.am.daemons=securid
# NOTE: HttpOnly cannot be enabled by default because the XUI reads the
# iPlanetDirectoryPro SSO cookie from document.cookie (see SessionToken.jsm,
# AMConfig.js, AuthNService.js) to track the session and set REST headers.
# Shipping HttpOnly=true here breaks XUI console login. Operators that do not
# use the XUI can safely override this to true.
com.sun.identity.cookie.httponly=false
# The session/SSO cookies are marked HttpOnly by default so that scripts cannot
# read the SSO token from document.cookie. The XUI fully supports this mode: it
# relies on the auto-sent cookie instead of reading the token in JavaScript, and
# in HttpOnly mode the token is delivered only via the Set-Cookie header (it is
# not echoed in the /json/authenticate body). Non-browser/raw-REST integrations
# that need the token in the body can opt back in with
# org.openidentityplatform.openam.httponly.allowTokenInBody=true, or set this
# property to false to disable HttpOnly entirely.
com.sun.identity.cookie.httponly=true
com.iplanet.am.cookie.name=iPlanetDirectoryPro
com.iplanet.am.cookie.secure=@SECURE_COOKIE@
org.openidentityplatform.openam.cookie.samesite=Lax
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,8 @@ public class CookieUtils {
(SystemPropertiesManager.get(Constants.AM_COOKIE_SECURE).
equalsIgnoreCase("true"));

static boolean cookieHttpOnly =
(SystemPropertiesManager.get(Constants.AM_COOKIE_HTTPONLY) != null) &&
(SystemPropertiesManager.get(Constants.AM_COOKIE_HTTPONLY).
equalsIgnoreCase("true"));
static boolean cookieHttpOnly =
SystemPropertiesManager.getAsBoolean(Constants.AM_COOKIE_HTTPONLY, true);

static boolean httpOnlyAllowTokenInBody =
SystemPropertiesManager.getAsBoolean(Constants.AM_COOKIE_HTTPONLY_ALLOW_TOKEN_IN_BODY, false);
Expand Down Expand Up @@ -169,8 +167,12 @@ public static boolean isCookieSecure() {
}

/**
* Returns property value of "com.sun.identity.cookie.httponly"
*
* Returns property value of "com.sun.identity.cookie.httponly".
* <p>
* Defaults to {@code true} when the property is not set: OpenAM marks its cookies
* {@code HttpOnly} out of the box. Set the property to {@code false} to opt out (for example for
* integrations that read the SSO token from {@code document.cookie} in the browser).
*
* @return the property value of "com.sun.identity.cookie.httponly"
*/
public static boolean isCookieHttpOnly() {
Expand Down