Ever tried to figure out which version of a script is running in production? Or spent 20 minutes recreating a script you accidentally overwrote in the admin console?
Yeah, me too.
ForgeRock’s browser-based script editor is fine for quick edits, but it falls apart when you need version history, code review, or deployments across multiple environments. That’s where Frodo CLI comes in—it lets you treat scripts like actual code.
The Script Management Problem
ForgeRock AM scripts power critical authentication logic:
| Script Type | Use Case |
|---|---|
| Scripted Decision | Custom authentication logic in journeys |
| OAuth2 Access Token Modification | Custom claims in tokens |
| OIDC Claims | Customizing OpenID Connect claims |
| Social Identity Provider Profile | Transform social provider data |
| Policy Condition | Custom authorization conditions |
| Configuration Provider | Dynamic configuration scripts |
Without proper management, scripts become:
- Scattered across environments with no single source of truth
- Unversioned with no history of changes
- Error-prone when manually copying between tenants
Frodo Script Commands
List All Scripts
# List scripts in a tenant
frodo script list -h https://openam-dev.forgeblocks.com/am
# Output:
# ┌─────────────────────────────────────────┬──────────────────────────────┬───────────┐
# │ Name │ Type │ Language │
# ├─────────────────────────────────────────┼──────────────────────────────┼───────────┤
# │ AUTHENTICATION_TREE_DECISION │ Scripted Decision │ JAVASCRIPT│
# │ CustomRiskEvaluation │ Scripted Decision │ JAVASCRIPT│
# │ OAuth2AccessTokenModification │ Access Token Modification │ GROOVY │
# └─────────────────────────────────────────┴──────────────────────────────┴───────────┘
Export Scripts
# Export all scripts to a directory
frodo script export -a -D ./scripts -h https://openam-dev.forgeblocks.com/am
# Export a specific script by name
frodo script export -n "CustomRiskEvaluation" -h https://openam-dev.forgeblocks.com/am
# Export by script ID
frodo script export -i "12345678-1234-1234-1234-123456789012" -h https://openam-dev.forgeblocks.com/am
# Export with metadata (includes description, context, etc.)
frodo script export -a -D ./scripts --include-meta -h https://openam-dev.forgeblocks.com/am
Import Scripts
# Import all scripts from a directory
frodo script import -a -D ./scripts -h https://openam-prod.forgeblocks.com/am
# Import a specific script file
frodo script import -f ./scripts/CustomRiskEvaluation.script.json -h https://openam-prod.forgeblocks.com/am
# Import and overwrite existing scripts
frodo script import -a -D ./scripts --force -h https://openam-prod.forgeblocks.com/am
Script File Format
Exported scripts are JSON files with this structure:
{
"script": {
"12345678-1234-1234-1234-123456789012": {
"_id": "12345678-1234-1234-1234-123456789012",
"name": "CustomRiskEvaluation",
"description": "Evaluates risk based on user behavior",
"script": "dmFyIHJpc2tTY29yZSA9IDA7...",
"language": "JAVASCRIPT",
"context": "AUTHENTICATION_TREE_DECISION",
"createdBy": "id=admin,ou=user,dc=openam,dc=forgerock,dc=org",
"creationDate": 1702000000000,
"lastModifiedBy": "id=admin,ou=user,dc=openam,dc=forgerock,dc=org",
"lastModifiedDate": 1702000000000
}
}
}
Note: The script field is Base64-encoded. Frodo handles encoding/decoding automatically.
Version Control Workflow
Repository Structure
forgerock-scripts/
├── .gitignore
├── README.md
├── scripts/
│ ├── authentication/
│ │ ├── CustomRiskEvaluation.script.json
│ │ ├── DeviceFingerprintValidation.script.json
│ │ └── MFAStepUp.script.json
│ ├── oauth/
│ │ ├── AccessTokenModification.script.json
│ │ └── CustomClaims.script.json
│ └── policy/
│ └── GeolocationCondition.script.json
└── scripts-decoded/
├── CustomRiskEvaluation.js
├── DeviceFingerprintValidation.js
└── MFAStepUp.js
Decoding Scripts for Review
Since scripts are Base64-encoded in the JSON, create decoded versions for easier review:
#!/bin/bash
# decode-scripts.sh - Extract script content for review
mkdir -p scripts-decoded
for file in scripts/**/*.script.json; do
name=$(basename "$file" .script.json)
# Extract and decode the script content
jq -r '.script | to_entries | .[0].value.script' "$file" | base64 -d > "scripts-decoded/${name}.js"
echo "Decoded: $name"
done
Pre-commit Hook for Script Validation
#!/bin/bash
# .git/hooks/pre-commit
echo "Validating ForgeRock scripts..."
for file in scripts/**/*.script.json; do
# Validate JSON syntax
if ! jq empty "$file" 2>/dev/null; then
echo "Invalid JSON: $file"
exit 1
fi
# Check script is properly encoded
script_content=$(jq -r '.script | to_entries | .[0].value.script // empty' "$file")
if [ -z "$script_content" ]; then
echo "Missing script content: $file"
exit 1
fi
done
echo "All scripts valid!"
Automated Script Deployment
GitHub Actions Workflow
# .github/workflows/deploy-scripts.yml
name: Deploy AM Scripts
on:
push:
branches: [main]
paths:
- 'scripts/**'
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'staging' }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install Frodo CLI
run: npm install -g @rockcarver/frodo-cli
- name: Validate Scripts
run: |
echo "Validating script files..."
for file in scripts/**/*.script.json; do
jq empty "$file" || exit 1
done
echo "All scripts valid!"
- name: Deploy Scripts
env:
FRODO_HOST: ${{ secrets.FRODO_HOST }}
FRODO_USER: ${{ secrets.FRODO_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_PASSWORD }}
run: |
echo "Deploying scripts to ${{ github.event.inputs.environment || 'staging' }}..."
frodo script import -a -D ./scripts
- name: Verify Deployment
env:
FRODO_HOST: ${{ secrets.FRODO_HOST }}
FRODO_USER: ${{ secrets.FRODO_USER }}
FRODO_PASSWORD: ${{ secrets.FRODO_PASSWORD }}
run: |
echo "Verifying deployed scripts..."
frodo script list
Script Development Best Practices
1. Use Descriptive Names
// Bad: script1, test, new
// Good: CustomRiskEvaluationForLoginJourney, DeviceFingerprintValidator
2. Add Script Headers
Every script should start with metadata:
/**
* Script: CustomRiskEvaluation
* Purpose: Evaluate user risk based on device, location, and behavior
* Journey: Login, Registration
* Author: IAM Team
* Last Modified: 2024-12-20
* Dependencies: Risk API service
*/
var riskScore = 0;
// ... script logic
3. Environment-Specific Configuration
Use ESVs (Environment Secrets and Variables) for environment-specific values:
// Instead of hardcoding:
// var apiUrl = "https://risk-api-prod.example.com";
// Use ESV reference:
var apiUrl = systemEnv.getProperty("esv.risk.api.url");
// Or use shared state from journey configuration:
var apiUrl = sharedState.get("riskApiUrl");
4. Script Testing Workflow
#!/bin/bash
# test-script.sh - Test a script in dev before promoting
SCRIPT_NAME="$1"
DEV_TENANT="https://openam-dev.forgeblocks.com/am"
STAGING_TENANT="https://openam-staging.forgeblocks.com/am"
# Export from dev
echo "Exporting $SCRIPT_NAME from dev..."
frodo script export -n "$SCRIPT_NAME" -D ./test-scripts -h "$DEV_TENANT"
# Import to staging for testing
echo "Importing to staging for testing..."
frodo script import -f "./test-scripts/${SCRIPT_NAME}.script.json" -h "$STAGING_TENANT"
echo "Script promoted to staging. Test in staging environment."
Script Comparison Across Environments
Compare scripts between environments to identify drift:
#!/bin/bash
# compare-scripts.sh - Compare scripts between two tenants
DEV_TENANT="https://openam-dev.forgeblocks.com/am"
PROD_TENANT="https://openam-prod.forgeblocks.com/am"
mkdir -p compare/dev compare/prod
# Export from both environments
frodo script export -a -D ./compare/dev -h "$DEV_TENANT"
frodo script export -a -D ./compare/prod -h "$PROD_TENANT"
# Compare script content (excluding timestamps)
for dev_file in compare/dev/*.script.json; do
name=$(basename "$dev_file")
prod_file="compare/prod/$name"
if [ -f "$prod_file" ]; then
# Compare just the script content
dev_script=$(jq -r '.script | to_entries | .[0].value.script' "$dev_file")
prod_script=$(jq -r '.script | to_entries | .[0].value.script' "$prod_file")
if [ "$dev_script" != "$prod_script" ]; then
echo "DIFFERENT: $name"
else
echo "IDENTICAL: $name"
fi
else
echo "MISSING IN PROD: $name"
fi
done
# Check for scripts only in prod
for prod_file in compare/prod/*.script.json; do
name=$(basename "$prod_file")
dev_file="compare/dev/$name"
if [ ! -f "$dev_file" ]; then
echo "ONLY IN PROD: $name"
fi
done
Troubleshooting
Script Import Failures
# Check script exists before import
frodo script list -h $TENANT | grep "ScriptName"
# Use verbose mode
frodo script import -f script.json -h $TENANT --verbose
# Force overwrite existing script
frodo script import -f script.json -h $TENANT --force
Encoding Issues
If scripts don’t work after import, check encoding:
# Decode and verify script content
jq -r '.script | to_entries | .[0].value.script' script.json | base64 -d
# Check for special characters
jq -r '.script | to_entries | .[0].value.script' script.json | base64 -d | file -
Permission Errors
Ensure your service account has script permissions:
fr:am:scripts:*- Full script accessfr:am:scripts:read- Read-only access
Related Resources
Frodo CLI Series
Related Topics
Final Thoughts
The shift from browser-based script editing to Git-based workflows was a game changer for our team. Here’s what made the difference:
-
Code reviews actually happen now - When scripts live in Git, PRs become natural. We’ve caught bugs in code review that would have made it to production otherwise.
-
“Who changed this?” has an answer -
git blameis your friend. No more guessing who modified the MFA script last week. -
Environment drift is visible - Running the comparison script monthly caught several cases where prod had manual changes that weren’t in our repo.
The initial export takes 10 minutes. Setting up the Git workflow takes another hour. But after that, you’ll never go back to the old way.