Password replay with AM
Password replay brings SSO to legacy web applications without the need to edit, upgrade, or recode them.
Use PingGateway with an appropriate AM authentication tree to capture and replay username password credentials. PingGateway and AM share a secret key to encrypt and decrypt the password and keep it safe.
When running PingOne Advanced Identity Cloud, read Password replay instead.
Request flow
The following figure illustrates the flow of requests when an unauthenticated user accesses a protected application. After authenticating with AM, the user is logged into the application with the username and password from the AM login session.
-
PingGateway intercepts the browser’s HTTP GET request.
-
PingGateway redirects the user to AM for authentication.
-
AM authenticates the user and stores the encrypted password in a JWT.
-
AM redirects the browser back to the protected application with the JWT.
-
PingGateway intercepts the browser’s HTTP GET request again:
-
The user is authenticated.
-
PingGateway gets the password from the JWT and decrypts it.
-
PingGateway gets the username from AM based on the user
_id
.
-
-
PingGateway replaces the request with an HTTP POST to the application, taking the credentials from the context.
-
The sample application validates the credentials from the HTTP POST request.
-
The sample application responds with the user’s profile page.
-
PingGateway passes the response from the sample application to the browser.
Tasks
Before you begin
Before you begin, prepare AM, PingGateway, and the sample application. Learn more in the example installation for this guide.
Task 1: Prepare a shared secret
PingGateway and AM share a secret symmetric key to encrypt and decrypt the password.
The following tasks use the AM default Don’t use the test key in production. In a production deployment, generate a random AES 256-bit key to use as a shared secret. How you generate the secret key is up to you. For example:
Keep the secret safe. |
You’ll set the AM secret label scripted.node.aes.key
to reference it
in a script to encrypt the user’s credentials.
You’ll also share the secret with PingGateway to decrypt the user’s credentials before replaying them.
Task 2: Configure AM
This example uses the default-passwords-store
secret store for the shared secret.
In a production deployment, use your own secret store and shared secret:
-
Go to http://am.example.com:8088/openam/encode.jsp and use the page to encode the base64-encoded AES key.
In this example, the base64-encoded AES key is
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd5eHowMTIzNDU=
. -
Write the result to a
default-passwords-store
file namedscripted.node.aes.key
:$ echo -n <encoded-aes-key> > /path/to/openam/security/secrets/encrypted/scripted.node.aes.key
Only secret labels that begin with the string
scripted.node.
are accessible to scripts. -
Select Realms > Top Level Realm > Services > Add a Service and add a Validation Service with the following Valid goto URL Resources:
-
https://ig.example.com:8443/*
-
https://ig.example.com:8443/*?*
-
-
In the agent profile for PingGateway, add a redirect URI for CDSSO that PingGateway can use in the route for password replay.
Go to Applications > Agents > Identity Gateway > ig_agent, set Redirect URLs for CDSSO to
https://ig.example.com:8443/replay/redirect
, and click Save Changes. -
Go to Scripts, click + New Script and create a Decision node script for authentication trees with the Legacy engine named
Password replay
and the following JavaScript content.The script adds a JWT encrypted with the shared secret key to the session:
Show script
var fr = JavaImporter( org.forgerock.openam.auth.node.api.Action, org.forgerock.openam.auth.node.api, javax.security.auth.callback.TextOutputCallback, org.forgerock.json.jose.builders.JwtBuilderFactory, org.forgerock.json.jose.jwt.JwtClaimsSet, org.forgerock.json.jose.jwe.JweAlgorithm, org.forgerock.json.jose.jwe.EncryptionMethod, javax.crypto.spec.SecretKeySpec, org.forgerock.secrets.SecretBuilder, org.forgerock.util.encode.Base64 ); var NodeOutcome = { ERROR: 'false', SUCCESS: 'true' }; var config = { encryptionKey: secrets.getGenericSecret("scripted.node.aes.key").getAsUtf8() }; function getKey () { logger.message("encKey: " + config.encryptionKey) return new fr.SecretKeySpec(fr.Base64.decode(config.encryptionKey), 'AES'); } function buildJwt (claims) { logger.message('Building response JWT'); var encryptionKey = getKey(); var jwtClaims = new fr.JwtClaimsSet; jwtClaims.setClaims(claims); var jwt = new fr.JwtBuilderFactory() .jwe(encryptionKey) .headers() .alg(fr.JweAlgorithm.DIRECT) .enc(fr.EncryptionMethod.A128CBC_HS256) .done() .claims(jwtClaims) .build(); return jwt; } try { password=nodeState.get("password").asString() var registrationClaims = { password: password }; passwordJwt = buildJwt(registrationClaims); action = fr.Action.goTo(NodeOutcome.SUCCESS).putSessionProperty("sunIdentityUserPassword", passwordJwt).build(); } catch (e) { logger.error('ERROR ' + e); action = fr.Action.send(new fr.TextOutputCallback(fr.TextOutputCallback.ERROR, e.toString())).build(); }
You can download the script as password-replay-am.js.
-
Go to Authentication > Trees, click + Create Tree, and create an authentication tree named
Password replay
configured with the nodes shown in this screenshot:-
The Page node presents a page with input fields to prompt for the username and password.
-
The Username collector node collects and injects the
userName
into the shared node state. -
The Password collector node collects and injects the
password
into the shared node state.
-
-
The Data Store Decision node uses the username and password to determine whether authentication is successful.
-
The Scripted Decision node references your script and has the same outcomes:
true
andfalse
.
-
-
Make sure the allowlist for scripted decision nodes includes all the classes the script requires.
Go to Configure > Global Services > Scripting > Secondary Configurations > AUTHENTICATION_TREE_DECISION_NODE > Secondary Configurations > engineConfiguration, add these Java classes to the allowlist, then click Save Changes:
-
javax.crypto.spec.SecretKeySpec
-
org.forgerock.json.jose.builders.EncryptedJwtBuilder
-
org.forgerock.json.jose.builders.JweHeaderBuilder
-
org.forgerock.json.jose.builders.JwtBuilderFactory
-
org.forgerock.json.jose.jwe.EncryptionMethod
-
org.forgerock.json.jose.jwe.JweAlgorithm
-
org.forgerock.json.jose.jwt.JwtClaimsSet
-
org.forgerock.secrets.SecretBuilder
-
org.forgerock.util.encode.Base64
-
-
Make sure the test user can log in with the tree before continuing.
To verify the tree works as expected, in your browser’s privacy or incognito mode, go to http://am.example.com:8088/openam/XUI/?service=Password%20replay#login and log in as test user
demo
with passwordCh4ng31t
.
Task 3: Configure PingGateway
The password replay configuration includes the PingGateway password to connect to AM, the shared secret, and the route for password replay.
-
Set up PingGateway for HTTPS.
Learn more in Configure PingGateway for TLS (server-side).
-
Set an environment variable locally on the computer running PingGateway to access the agent password:
# The base64-encoded PingGateway agent "password": $ export AGENT_SECRET_ID='cGFzc3dvcmQ='
PingGateway retrieves the password with a SystemAndEnvSecretStore, which requires it to be base64-encoded.
The password must match the agent profile password you set in AM. PingGateway uses the password to connect to AM.
-
Set an environment variable locally on the computer running PingGateway to access the shared secret key:
# The base64-encoded shared secret key: $ export AES_KEY='YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd5eHowMTIzNDU='
PingGateway retrieves the shared secret with a SystemAndEnvSecretStore, which requires it to be base64-encoded.
The shared secret key must match the secret key the AM script uses to encrypt the password to replay. PingGateway uses the secret key to decrypt the password to replay.
-
Restart PingGateway to read the secrets from the environment.
-
Add a route to PingGateway to serve the sample application CSS and other static resources:
-
Linux
-
Windows
$HOME/.openig/config/routes/00-static-resources.json
%appdata%\OpenIG\config\routes\00-static-resources.json
{ "name" : "00-static-resources", "baseURI" : "http://app.example.com:8081", "condition": "${find(request.uri.path,'^/css') or matchesWithRegex(request.uri.path, '^/.*\\\\.ico$') or matchesWithRegex(request.uri.path, '^/.*\\\\.gif$')}", "handler": "ReverseProxyHandler" }
-
-
Add a route for password replay:
-
Linux
-
Windows
$HOME/.openig/config/routes/04-replay.json
%appdata%\OpenIG\config\routes\04-replay.json
{ "name": "04-replay", "condition": "${find(request.uri.path, '^/replay')}", "properties": { "amInstanceUrl": "http://am.example.com:8088/openam/" }, "heap": [ { "name": "SystemAndEnvSecretStore-1", "type": "SystemAndEnvSecretStore", "config": { "mappings": [ { "secretId": "aes.key", "format": { "type": "SecretKeyPropertyFormat", "config": { "format": "BASE64", "algorithm": "AES" } } } ] } }, { "name": "AmService-1", "type": "AmService", "config": { "agent": { "username": "ig_agent", "passwordSecretId": "agent.secret.id" }, "secretsProvider": "SystemAndEnvSecretStore-1", "url": "&{amInstanceUrl}" } }, { "name": "CapturedUserPasswordFilter-1", "type": "CapturedUserPasswordFilter", "config": { "ssoToken": "${contexts.ssoToken.value}", "keySecretId": "aes.key", "keyType": "AES", "secretsProvider": "SystemAndEnvSecretStore-1", "amService": "AmService-1" } } ], "handler": { "type": "Chain", "config": { "filters": [ { "name": "CrossDomainSingleSignOnFilter-1", "type": "CrossDomainSingleSignOnFilter", "config": { "redirectEndpoint": "/replay/redirect", "authCookie": { "path": "/replay", "name": "ig-token-cookie" }, "amService": "AmService-1", "authenticationService": "Password replay" } }, { "name": "UserProfileFilter-1", "type": "UserProfileFilter", "config": { "username": "${contexts.ssoToken.info.uid}", "userProfileService": { "type": "UserProfileService", "config": { "amService": "AmService-1", "profileAttributes": [ "username" ] } } } }, { "name": "PasswordReplayFilter-1", "type": "PasswordReplayFilter", "config": { "loginPage": "${true}", "credentials": "CapturedUserPasswordFilter-1", "request": { "method": "POST", "uri": "http://app.example.com:8081/login", "form": { "username": [ "${contexts.userProfile.username}" ], "password": [ "${contexts.capturedPassword.value}" ] } } }, "capture": [ "all" ] } ], "handler": "ReverseProxyHandler" } } }
The route:
-
Matches requests whose path starts with
/replay
. -
Sets an
amInstanceUrl
property to the URL for AM. -
Loads the
aes.key
secret key from the localAES_KEY
environment variable. -
Connects to AM as
ig_agent
with theagent.secret.id
password from the localAGENT_SECRET_ID
environment variable. -
Extracts the captured password from the SSO token context and decrypts it with the
aes.key
secret key. -
Uses a CrossDomainSingleSignOnFilter to redirect to the AM
Password replay
tree for authentication, getting the authentication information from theig-token-cookie
. -
Queries AM to retrieve the username for logging in.
You can retrieve other profile attributes with the UserProfileFilter, such as the email address or first and last names. The sample application expects the username in this example, so the route gets the username.
-
Logs in to the sample application with the username and password.
-
Returns the result to the browser.
In production, remove
"capture": ["all"]
from the PasswordReplayFilter to avoid recording credentials in the logs. -
Validation
-
In your browser’s privacy or incognito mode, go to https://ig.example.com:8443/replay/.
PingGateway redirects to the AM authentication tree.
-
Log in as test user
demo
with passwordCh4ng31t
.PingGateway successfully replays the credentials against the sample application. The sample application displays its user profile page:
-
Review the PingGateway output.
On success, the output displays the credentials and the profile page:
...INFO o.f.o.d.c.C.c.PasswordReplayFilter-1 @04-replay - [CONTINUED]--- (filtered-request) exchangeId:<id> - transactionId:<id> ---> [CONTINUED]POST http://app.example.com:8081/login HTTP/1.1 [CONTINUED]Content-Length: 31 [CONTINUED]Content-Type: application/x-www-form-urlencoded [CONTINUED]password=Ch4ng31t&username=demo ...INFO o.f.o.d.c.C.c.PasswordReplayFilter-1 @04-replay - [CONTINUED]<--- (response) exchangeId:<id> - transactionId:<id> --- [CONTINUED]HTTP/1.1 200 OK ... [CONTINUED]<!DOCTYPE html> ...
You have successfully demonstrated password replay with PingGateway and AM.
If password replay fails, consider the outcome of the HTTP POST from PingGateway to the sample application:
- HTTP 401 Unauthorized
-
PingGateway is not replaying the credentials.
Review the PingGateway output to determine whether the username or password is missing when PingGateway replays the credentials.
If the password is missing, make sure PingGateway and AM share the same AES secret key.
- HTTP 403 Forbidden
-
PingGateway is not replaying the right credentials.
Make sure you’re using a username-password combination known to the sample application.