Supplemental examples to publicly available scripting resources, including:
- Official Documentation
- Knowledge Base
- ForgeRock Community
- Notes on Scripting in ForgeRock Access Management (AM)
- ForgeRock Access Management (AM)
- General
- Scripted Decisions
- Set a Theme with a Method of the
Action Interface
- Get User Location from Their Postal or Physical Address and Save the Location Information in
sharedState
- Get User Location from Their IP Address and Save the Location Information in
sharedState
- Set a Session Property Based on a Value Saved in
sharedState
- Set Multiple Session Properties
- Enable 2-Step Verification in Identity Cloud with Pre-existing Oath Devices Data Saved in a Custom Attribute
- Resend HTTP Request
- Set a Theme with a Method of the
- OIDC Claims
- Scripts to Run in a Browser
- ForgeRock Identity Management (IDM)
- ForgeRock Identity Platform and Identity Cloud
-
Write all your functionality in a function; you may start with an Immediately Invoked Function Expression (IIFE).
Do NOT use the top-level Rhino scope in AM as it might produce unexpected behavior, particularly when assigning variables.
In addition, closing your variables in a function will separate them from bindings provided by AM scripting engine.
-
You can inspect the variables in the top-level Rhino scope in AM and, thus, see what bindings are provided in the current scripting context.
-
-
Using the org.forgerock.util.encode.Base64 Class
Example script: org.forgerock.util.encode.Base64.js
-
Example script: webtoolkit.base64.js
-
-
-
String.prototype.matchAll() Polyfill used in the example
Currently, a
String.prototype.matchAll()
polyfill is required in AM.
-
Set a Theme with a Method of the Action Interface
In Platform UI, you can apply a theme dynamically from a scripted decision node by sending callbacks and using withStage method of the Action Interface:
action = javaImports.Action.send( javaImports.ScriptTextOutputCallback(script) ).withStage('themeId=' + themeNameOrId).build();
Note, however, that setting a theme this way will be overridden by specifying a theme for journey, as described in Apply a custom theme to a Journey.
You can find, update, or create a theme in the Platform Administrator under Hosted Pages. The theme name can serve as the theme ID in your scripted decision.
-
You can get the client IP address from the X-Forwarded-For request header in deployments with AM behind a balance loader or a reverse proxy.
Otherwise, in an "on premise" installation, you'll need to obtain the client IP address with a script running in the browser. Accessing the request object in the scripted decision context is currently not an option.
-
You can set the future session properties in a scripted decision dynamically by using methods of an ActionBuilder instance returned by static methods of the Action interface.
-
In a scripted decision, you can use the httpClient binding for making an outbound HTTP request. The binding's .send(Request request) method returns a Promise. You can make the script wait for the promise to be resolved by using one of its
.get
methods. Alternatively, you can leave the promise to be resolved independently of the script execution with a.then
method.If you use the .get() method, the scripted decision will wait for the promise to be resolved with the default timeout of 10 seconds and then continue to execute.
Using the .get(long timeout, TimeUnit unit) allows to specify a custom timeout. This will require the
java.util.concurrent.TimeUnit
class to be added to the allowed Java in AM, as described in this KB article. Doing so is an option in self-managed environments, such as "on-premises" one or ForgeOps, but the class is not currently a part of the allowed Java in ForgeRock Identity Cloud.Whether the default or a custom timeout is used in the promise, it is possible that the request will intermittently fail. The example scripts referenced below allow for making a specified number of subsequent requests in case the previous request times out.
-
Saving AM User Session Information in a Custom Claim, and Resetting the Session Idle Timeout by Using the SSO Token
-
A third-party Relying Party (RP) might not be able to include the OpenID Provider (OP) cookies in requests made via the back channel. For example, including cookies in XHR requests from a different domain might be affected by the third-party cookies restrictions implemented in the browser; and for requests made from the server side, a third-party application won't have access to the cookies set by the authorization server.
Therefore, maintaining a user session at the OP via the back channel, silently, may need to be implemented without reliance on the OP's session cookies.
In AM, the session information could be accessible in the context of the OIDC Claims script, which runs when ID token claims are being processed. Thus, the session information could be included in a custom ID token claim.
The session context in the script is provided via the
session
binding, which represents an instance of the SSOToken class.The
session
binding is currently available in the OIDC Claims script only when the session cookie is included in the OIDC authentication request; for example, during an interactive authorization code grant initiated in the front channel. -
In these conditions, a trusted RP application running in a different domain could maintain a (CTS-Based) user session at AM by providing the SSO token in a REST call to the /json/sessions/ endpoint.
To make this call silently from a browser, via an XHR request, you will need to add the session token header name (typically, iPlanetDirectoryPro) to the accepted headers in your CORS service configuration, in the AM console > CONFIGURE > Global Services > CORS Service > Secondary Configurations > CORS configuration name > Accepted Headers.
The SSO token could be obtained from the
session
object with itsgetTokenID()
method.For this to work, you will need the
com.iplanet.sso.providers.dpro.SSOTokenIDImpl
Java class to be allowed in the scripting engine configuration for the OIDC Claims Script type.Then, the session token could be included as a custom claim in the ID token sent to the RP.
With an RP that is a Single Page Application (SPA), storing the almighty SSO token in a browser should be well considered.
-
An untrusted third-party application, belonging to a business entity different from the one maintaining the authorization server, should not have access to the user's SSO token.
If/when the
session
binding were defined every time the ID token is issued, and the user session is not terminated, presence of a claim based on the session information could serve for the RP as an indication of an active user session, and its absence could be used by the RP to effect the single sign out functionality.In addition, the user session could be renewed from the script by sending a request to the
/json/sessions/
endpoint; thus, relieving a trusted RP from the responsibility to maintain the SSO token, and providing a safe option to renew the user session for an untrusted RP.To make an HTTP request from the OIDC Claims script, you will need the following Java classes to be allowed in the scripting engine configuration for this script type:
org.forgerock.http.protocol.*
org.forgerock.http.Client
org.forgerock.util.promise.PromiseImpl
-
{ "at_hash": "7y7kdvI4P97EOrng0kn7jw", "sub": "85f8cdd0-59f5-4d4c-b6f9-5fe0857c0f8c", "auditTrackingId": "e6716ee2-32ee-4b32-ade6-19c7f3760c87-80295", "subname": "85f8cdd0-59f5-4d4c-b6f9-5fe0857c0f8c", "iss": "https://default.iam.example.com/am/oauth2", "tokenName": "id_token", "session": { "timeLeft": 7199, "tokenId": "VxYoSm207yxijytEXTW2HUkNCmM.*AAJTSQACMDIAAlNLABxsQTFuU3NtMW00L3QxWTFiZmUrU2l4aitDSEU9AAR0eXBlAANDVFMAAlMxAAIwMQ..*" }, "given_name": "user", "nonce": "Y2u8L7AjPOknYx7ESDJwNaAi2-3xloI79UWiS4vp6BQ", "sid": "E90taYovhKEMRrTu0+mTc64/1KbrHnG0z1QLtlSqV9o=", "aud": "node-openid-client", "c_hash": "8kF3xWVmnEzh51oqDD2Vvw", "acr": "0", "org.forgerock.openidconnect.ops": "GQgVZAO0mKmrPBru9WYymF2nJwI", "s_hash": "EdwNOuHRohV2HCUbUYONbg", "azp": "node-openid-client", "auth_time": 1625071581, "name": "user 0", "realm": "/", "exp": 1625075181, "tokenType": "JWTToken", "iat": 1625071581, "family_name": "0" }
-
Run this script in any administrative browser console—AM, IDM, or Platform.
-
Continue reading for additional information and examples.
-
Allow for uploading existing hash into a custom field, and use it for validating users' answers to security questions.
For example:
"frIndexedMultivalued3": [ "{\"questionId\":\"3\",\"answer\":\"$2a$10$QYZRykd.7Is1DQx96hfUeOunRACQKPKCR21jpPND60eCmC6WObhSK\"}", "{\"questionId\":\"1\",\"answer\":\"$2a$10$U6kH.1ghtrtzJhpNQXretuQR4psfO6zC5ANnwfjsGiLAdp0ob1xeG\"}", "{\"questionId\":\"2\",\"answer\":\"$2a$10$noDIZAkLxArssuDOw8PJhOseQ5QrwXSAckq/U2s1Gbj6G8hvlPLES\"}" ]
-
Allow for use of hashing algorithms that are currently unsupported in a control environment, such as ForgeRock Identity Cloud (Identity Cloud).
For example:
const javaImports = JavaImporter( java.security.SecureRandom, org.bouncycastle.crypto.generators.OpenBSDBCrypt, java.lang.String ); // . . . /** * Create hash. */ function hashAnswer(answer) { const secureRandom = new javaImports.SecureRandom(); const salt = secureRandom.generateSeed(16); const answerJava = new javaImports.String(answer); return javaImports.OpenBSDBCrypt.generate('2a', answerJava.toCharArray(), salt, 10); } // . . . /** * Validate answers. */ const hasIncorrectAnswer = requestQuestions.some(function (requestQuestion) { const requestAnswerJava = new javaImports.String(requestQuestion.answer); const correctAnswer = profileQuestions.find(function (profileQuestion) { return profileQuestion.questionId === requestQuestion.questionId; }).answer; return !javaImports.OpenBSDBCrypt.checkPassword(correctAnswer, requestAnswerJava.toCharArray()); });
-
Configure the Security Questions
For example, in the browser console during an active IDM Administrator session, update the KBA configuration over REST:
-
Create a Custom Endpoint to Launch the Script
Upload the following script at the custom endpoint:
This ^^^ script is using OpenBSDBCrypt with the following defaults:
const bcryptVersion = '2a'; const bcryptCost = 10;
For example, you can upload the script over REST using the following example as a template in the browser console during an active IDM Administrator session:
-
Optionally, load the existing hashed answers into the custom KBA field as an array of stringified JSON.
For example:
"frIndexedMultivalued3": [ "{\"questionId\":\"3\",\"answer\":\"$2a$10$QYZRykd.7Is1DQx96hfUeOunRACQKPKCR21jpPND60eCmC6WObhSK\"}", "{\"questionId\":\"1\",\"answer\":\"$2a$10$U6kH.1ghtrtzJhpNQXretuQR4psfO6zC5ANnwfjsGiLAdp0ob1xeG\"}", "{\"questionId\":\"2\",\"answer\":\"$2a$10$noDIZAkLxArssuDOw8PJhOseQ5QrwXSAckq/U2s1Gbj6G8hvlPLES\"}" ]
-
During registration, use PATCH request to the custom endpoint to hash plain-text answers with the custom algorithm, save them in the custom KBA field, and update the standard KBA property accordingly.
See an example of doing so in the browser console during an active IDM Administrator session:
Hash and Save Answers in a Custom KBA Field - Example Script
-
During authentication, use POST request to the custom endpoint to verify plain-text answers against hashes saved in the custom KBA field, and to update the standard KBA property accordingly if the correct answers were provided.
See an example of doing so in the browser console during an active IDM Administrator session:
Verify Answers Against Hashes Saved in the Custom KBA Field - Example Script
-
During registration and authentication, optionally, pass in the custom KBA field name in the requests sent to the custom endpoint as the
field
parameter.For example (to be run in the browser authenticated as IDM Administrator):
var customEndpointName = '{custom-endpoint-name}'; var userId = '{user-id}'; var data = JSON.stringify([ { operation: 'replace', field: '{custom-kba-field-name}', value: [ { questionId: '{question-id}', answer: '{answer}' } ] } ]); await $.ajax({ method: 'PATCH', url: '/openidm/endpoint/' + customEndpointName + '/' + userId, data: data, headers: { 'x-requested-with': 'XMLHttpRequest', 'Content-Type': 'application/json' } });
If the
field
parameter is not provided, the custom endpoint script will substitute it with a default:const defaultKbaCustomField = 'frIndexedMultivalued3';
You can also change the default in the Custom Endpoint Script.
-
Optionally, when the standard KBA property has been populated for all users, use the out of the box KBA nodes to validate user's answers.
-
-
Backup Realm Configuration with FRODO CLI
An example shell script that saves major realm configuration components:
Example call:
$ ENVIRONMENT=kl03; REALM=alpha; /path/to/frodo-backup-realm-configuration.sh $ENVIRONMENT $REALM;