Ping Identity Hardening Guide
Identity federation security for PingFederate, PingOne, and OAuth configurations
Overview
Ping Identity serves 50%+ of Fortune 100 with federation trust relationships connecting enterprise identity to hundreds of downstream applications. OAuth and SAML tokens, if compromised, provide persistent access across the enterprise. The PingOne DaVinci orchestration platform creates automated identity workflows that attackers can exploit for privilege escalation and persistent access.
Intended Audience
- Security engineers managing identity infrastructure
- IT administrators configuring Ping Identity products
- GRC professionals assessing IAM compliance
- Third-party risk managers evaluating federation security
How to Use This Guide
- L1 (Baseline): Essential controls for all organizations
- L2 (Hardened): Enhanced controls for security-sensitive environments
- L3 (Maximum Security): Strictest controls for regulated industries
Scope
This guide covers Ping Identity security configurations including federation hardening, OAuth security, DaVinci orchestration controls, and token lifecycle management.
Table of Contents
- Authentication & Access Controls
- Federation Security
- OAuth & Token Security
- DaVinci Orchestration Security
- Monitoring & Detection
- Third-Party Integration Security
- Compliance Quick Reference
1. Authentication & Access Controls
1.1 Enforce Phishing-Resistant MFA
Profile Level: L1 (Baseline) CIS Controls: 6.3, 6.5 NIST 800-53: IA-2(1), IA-2(6)
Description
Require FIDO2/WebAuthn authenticators for administrator and high-privilege user authentication.
Rationale
Why This Matters:
- Federation trust means Ping Identity compromise affects all connected apps
- TOTP/SMS MFA can be bypassed via real-time phishing
- FIDO2 provides origin-bound authentication resistant to phishing
Attack Scenario: Attacker phishes admin credentials, generates valid tokens for any connected application via federation trust exploitation.
ClickOps Implementation (PingOne)
Step 1: Enable FIDO2 Authentication
- Navigate to: Authentication → Policies → MFA Policies
- Create policy:
- Name: “Phishing-Resistant MFA”
- Methods: FIDO2 Security Key (required)
- Fallback: None for admins
- Assign to administrator groups
Step 2: Configure Authentication Policy
- Navigate to: Authentication → Policies → Sign-On Policies
- Create rule:
- Condition: User group = “Administrators”
- Action: Require FIDO2 MFA
- Session duration: 2 hours maximum
Step 3: Disable Legacy Methods for Admins
- Navigate to: Authentication → MFA
- For admin accounts:
- Disable: SMS, Voice, Email OTP
- Enable only: FIDO2, Mobile app (push with number matching)
Code Implementation (PingOne API)
See the API pack below for MFA policy creation and assignment commands.
Compliance Mappings
| Framework | Control ID | Control Description |
|---|---|---|
| SOC 2 | CC6.1 | Logical access controls |
| NIST 800-53 | IA-2(6) | MFA for privileged accounts |
| PCI DSS | 8.3.1 | MFA for administrative access |
Code Pack: Terraform
# MFA device policy requiring FIDO2 for admin authentication
resource "pingone_mfa_device_policy" "phishing_resistant" {
environment_id = var.pingone_environment_id
name = "HTH Phishing-Resistant MFA"
authentication = {
device_selection = "DEFAULT_TO_FIRST"
}
fido2 = {
enabled = true
}
mobile = {
enabled = false
}
totp = {
enabled = false
}
sms = {
enabled = false
}
voice = {
enabled = false
}
email = {
enabled = false
}
}
# FIDO2 policy with platform and cross-platform authenticators
resource "pingone_mfa_fido2_policy" "webauthn" {
environment_id = var.pingone_environment_id
name = "HTH FIDO2 WebAuthn Policy"
attestation_requirements = "DIRECT"
authenticator_attachment = "BOTH"
backup_eligibility = { allow = true, enforce_during_authentication = false }
device_display_name = "FIDO2 Security Key"
discoverable_credentials = "PREFERRED"
mds_authenticators_requirements = { enforce_during_authentication = true, option = "SPECIFIC" }
relying_party_id = ""
user_display_name_attributes = { attributes = [{ name = "username" }] }
user_verification = { enforce_during_authentication = true, option = "REQUIRED" }
}
# Sign-on policy enforcing FIDO2 for administrators
resource "pingone_sign_on_policy" "phishing_resistant_admin" {
environment_id = var.pingone_environment_id
name = "HTH Phishing-Resistant Admin Sign-On"
description = "Requires FIDO2 MFA for all administrator access"
}
# Sign-on policy action: require MFA with FIDO2
resource "pingone_sign_on_policy_action" "admin_mfa" {
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.phishing_resistant_admin.id
priority = 1
mfa {
device_sign_on_policy_id = pingone_mfa_device_policy.phishing_resistant.id
no_device_mode = "BLOCK"
}
conditions {
user_attribute_equals {
attribute_reference = "$${user.memberOfGroups[?(id == '${var.admin_group_id}')]}"
value_boolean = true
}
}
}
# L2+: Reduce admin session duration to 2 hours
resource "pingone_sign_on_policy_action" "admin_session_limit" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.phishing_resistant_admin.id
priority = 2
login {
recovery_enabled = false
}
}
Code Pack: API Script
# Create MFA policy requiring FIDO2
curl -X POST "https://api.pingone.com/v1/environments/${ENV_ID}/mfaPolicies" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Phishing-Resistant MFA",
"enabled": true,
"configuration": {
"fido2": {
"enabled": true,
"required": true
},
"sms": {
"enabled": false
},
"totp": {
"enabled": false
}
}
}'
# Assign to admin group
curl -X PUT "https://api.pingone.com/v1/environments/${ENV_ID}/groups/${ADMIN_GROUP_ID}/mfaPolicy" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"mfaPolicyId": "${MFA_POLICY_ID}"
}'
1.2 Implement Least-Privilege Admin Roles
Profile Level: L1 (Baseline) NIST 800-53: AC-6, AC-6(1)
Description
Create granular administrative roles instead of using organization-wide admin access.
ClickOps Implementation (PingOne)
Step 1: Create Custom Admin Roles
- Navigate to: Settings → Roles
- Create roles:
Identity Administrator:
- Manage users and groups
- Reset passwords
- Assign MFA
- NO: Configure applications, manage policies
Application Administrator:
- Configure SAML/OIDC applications
- Manage application policies
- NO: Manage users, access audit logs
Security Administrator:
- Configure MFA policies
- Manage authentication policies
- Access audit logs
- NO: Manage applications directly
Read-Only Auditor:
- View all configurations
- Access reports and logs
- NO: Make any changes
Step 2: Assign Roles to Groups
- Navigate to: Identities → Groups
- Create admin groups (e.g., “Identity-Admins”, “App-Admins”)
- Assign appropriate roles to each group
- Add users to groups (not direct role assignment)
Code Pack: Terraform
# Groups for role-based admin access
resource "pingone_group" "identity_admins" {
environment_id = var.pingone_environment_id
name = "HTH Identity Administrators"
description = "Manage users, groups, reset passwords, assign MFA. No application or policy config."
}
resource "pingone_group" "app_admins" {
environment_id = var.pingone_environment_id
name = "HTH Application Administrators"
description = "Configure SAML/OIDC applications and application policies. No user management."
}
resource "pingone_group" "security_admins" {
environment_id = var.pingone_environment_id
name = "HTH Security Administrators"
description = "Configure MFA and authentication policies, access audit logs. No direct app management."
}
resource "pingone_group" "auditors" {
environment_id = var.pingone_environment_id
name = "HTH Read-Only Auditors"
description = "View all configurations, access reports and logs. No write access."
}
# Role assignments -- Identity Admin
resource "pingone_group_role_assignment" "identity_admin_role" {
count = var.identity_admin_group_id != "" ? 1 : 0
environment_id = var.pingone_environment_id
group_id = pingone_group.identity_admins.id
role_id = data.pingone_role.identity_data_admin.id
scope_environment_id = var.pingone_environment_id
}
# Role assignments -- Application Admin
resource "pingone_group_role_assignment" "app_admin_role" {
count = var.app_admin_group_id != "" ? 1 : 0
environment_id = var.pingone_environment_id
group_id = pingone_group.app_admins.id
role_id = data.pingone_role.application_owner.id
scope_environment_id = var.pingone_environment_id
}
# Role assignments -- Security Admin
resource "pingone_group_role_assignment" "security_admin_role" {
count = var.security_admin_group_id != "" ? 1 : 0
environment_id = var.pingone_environment_id
group_id = pingone_group.security_admins.id
role_id = data.pingone_role.environment_admin.id
scope_environment_id = var.pingone_environment_id
}
# Data sources for built-in PingOne roles
data "pingone_role" "identity_data_admin" {
name = "Identity Data Admin"
}
data "pingone_role" "application_owner" {
name = "Application Owner"
}
data "pingone_role" "environment_admin" {
name = "Environment Admin"
}
data "pingone_role" "identity_data_read_only" {
name = "Identity Data Read Only"
}
# L2+: Enforce group-only role assignments (no direct user roles)
# This is enforced via policy; direct user role assignments should be audited
resource "pingone_group_role_assignment" "auditor_role" {
environment_id = var.pingone_environment_id
group_id = pingone_group.auditors.id
role_id = data.pingone_role.identity_data_read_only.id
scope_environment_id = var.pingone_environment_id
}
1.3 Configure IP-Based Access Restrictions
Profile Level: L2 (Hardened) NIST 800-53: AC-3(7), SC-7
Description
Restrict administrative console and API access to known IP ranges.
ClickOps Implementation
Step 1: Configure IP Restrictions (PingOne)
- Navigate to: Settings → IP Restrictions
- Add allowed IP ranges:
- Corporate network CIDRs
- VPN egress IPs
- Set default: Deny all not in list
Step 2: Configure in Sign-On Policy
- Navigate to: Authentication → Policies → Sign-On Policies
- Create rule:
- Condition: IP not in trusted ranges
- Action: Deny access OR require additional verification
Code Pack: Terraform
# IP-based access restriction for admin console and API access (L2+)
resource "pingone_sign_on_policy" "ip_restricted" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH IP-Restricted Access"
description = "Restricts admin and API access to known corporate IP ranges"
}
# Allow access from corporate network CIDRs
resource "pingone_sign_on_policy_action" "allow_corporate_ips" {
count = var.profile_level >= 2 && length(var.corporate_gateway_cidrs) > 0 ? 1 : 0
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.ip_restricted[0].id
priority = 1
login {
recovery_enabled = true
}
conditions {
ip_out_of_range_cidr = var.corporate_gateway_cidrs
}
}
# Allow access from VPN egress IPs
resource "pingone_sign_on_policy_action" "allow_vpn_ips" {
count = var.profile_level >= 2 && length(var.vpn_egress_cidrs) > 0 ? 1 : 0
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.ip_restricted[0].id
priority = 2
login {
recovery_enabled = true
}
conditions {
ip_out_of_range_cidr = var.vpn_egress_cidrs
}
}
# L3: Deny all traffic outside corporate and VPN ranges
resource "pingone_sign_on_policy_action" "deny_unknown_ips" {
count = var.profile_level >= 3 ? 1 : 0
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.ip_restricted[0].id
priority = 99
login {
recovery_enabled = false
}
}
2. Federation Security
2.1 Harden SAML Federation Trust
Profile Level: L1 (Baseline) NIST 800-53: IA-5, SC-23
Description
Configure secure SAML settings to prevent assertion manipulation and replay attacks.
Rationale
Why This Matters:
- SAML assertions can be manipulated if not properly validated
- Weak signature algorithms enable forgery
- Long assertion validity enables replay attacks
Attack Scenario: Federation trust exploitation enables attackers to generate valid tokens for any connected application.
ClickOps Implementation (PingFederate)
Step 1: Configure Secure Signature Settings
- Navigate to: System → Server Configuration → Signing & Encryption
- Configure:
- Signature Algorithm: RSA-SHA256 (minimum)
- Digest Algorithm: SHA-256 (minimum)
- Key Size: 2048+ bits RSA or P-256 ECDSA
- Disable: SHA-1 algorithms
Step 2: Configure Assertion Validation
- Navigate to: Identity Provider → Connection → SAML Settings
- Enable:
- Verify Signature: Required
- Require Encrypted Assertions: Yes (L2)
- Audience Restriction: Enforce
- Set:
- Assertion Valid Period: 5 minutes (maximum)
- Session Timeout: 8 hours
Step 3: Configure Certificate Validation
- Navigate to: Security → Certificate Management
- Enable:
- Certificate revocation checking: CRL or OCSP
- Key usage validation: Enabled
- Configure: Certificate expiration alerts (30 days)
Code Implementation
See the CLI pack below for SAML configuration examples.
Code Pack: Terraform
# Signing key for SAML assertions -- RSA 2048+ with SHA-256
resource "pingone_key" "saml_signing" {
environment_id = var.pingone_environment_id
name = "HTH SAML Signing Key"
algorithm = "RSA"
key_length = 2048
signature_algorithm = "SHA256withRSA"
subject_dn = "CN=PingOne SAML Signing, O=HTH Hardened"
usage_type = "SIGNING"
validity_period = 365
}
# SAML application with hardened assertion settings
resource "pingone_application" "hardened_saml_sp" {
environment_id = var.pingone_environment_id
name = "HTH Hardened SAML SP Template"
enabled = true
saml_options {
acs_urls = ["https://sp.example.com/saml/acs"]
assertion_duration = var.saml_assertion_validity_seconds
sp_entity_id = "https://sp.example.com"
response_is_signed = true
assertion_signed = true
slo_binding = "HTTP_REDIRECT"
idp_signing_key {
key_id = pingone_key.saml_signing.id
algorithm = "SHA256withRSA"
}
sp_verification {
authn_request_signed = true
}
}
}
# L2+: Require encrypted SAML assertions
resource "pingone_key" "saml_encryption" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH SAML Encryption Key"
algorithm = "RSA"
key_length = 2048
signature_algorithm = "SHA256withRSA"
subject_dn = "CN=PingOne SAML Encryption, O=HTH Hardened"
usage_type = "ENCRYPTION"
validity_period = 365
}
# L3+: Enforce ECDSA P-256 signing for maximum security
resource "pingone_key" "saml_signing_ecdsa" {
count = var.profile_level >= 3 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH SAML Signing Key (ECDSA)"
algorithm = "EC"
key_length = 256
signature_algorithm = "SHA256withECDSA"
subject_dn = "CN=PingOne SAML Signing ECDSA, O=HTH Hardened"
usage_type = "SIGNING"
validity_period = 365
}
Code Pack: CLI Script
# PingFederate SAML Configuration Example
# XML assertion with short validity window and audience restriction:
#
# <saml:Assertion>
# <saml:Conditions
# NotBefore="2025-01-15T10:00:00Z"
# NotOnOrAfter="2025-01-15T10:05:00Z">
# <saml:AudienceRestriction>
# <saml:Audience>https://sp.company.com</saml:Audience>
# </saml:AudienceRestriction>
# </saml:Conditions>
# </saml:Assertion>
2.2 Implement Federation Monitoring
Profile Level: L1 (Baseline) NIST 800-53: AU-6, SI-4
Description
Monitor federation activity for anomalous patterns indicating compromise.
Detection Use Cases
See the DB pack below for federation monitoring queries.
2.3 Certificate Lifecycle Management
Profile Level: L1 (Baseline) NIST 800-53: SC-12
Description
Implement proactive certificate management to prevent federation disruption.
ClickOps Implementation
Step 1: Configure Certificate Rotation
- Navigate to: Security → Certificate Management
- Enable: Automatic certificate renewal alerts
- Set thresholds:
- 90 days: Warning
- 30 days: Critical alert
- 14 days: Emergency procedures
Step 2: Implement Dual Certificate
- Add new certificate before old expires
- Configure SP connections to accept both
- Coordinate rotation with SPs
- Remove old certificate after validation
Code Pack: Terraform
# Notification policy for certificate expiry warnings
resource "pingone_notification_policy" "cert_expiry_warning" {
environment_id = var.pingone_environment_id
name = "HTH Certificate Expiry Warning"
}
# Alert condition: certificate approaching expiry (configurable threshold)
resource "pingone_alert_channel" "cert_expiry" {
environment_id = var.pingone_environment_id
alert_name = "HTH Certificate Expiry Alert"
addresses = var.siem_webhook_url != "" ? [var.siem_webhook_url] : []
channel_type = "EMAIL"
include_severity = ["WARNING", "ERROR"]
}
# Dual certificate rotation support -- secondary signing key
# Deploy the new key before the old one expires, coordinate with SPs
resource "pingone_key" "saml_signing_rotation" {
environment_id = var.pingone_environment_id
name = "HTH SAML Signing Key (Rotation)"
algorithm = "RSA"
key_length = 2048
signature_algorithm = "SHA256withRSA"
subject_dn = "CN=PingOne SAML Signing Rotation, O=HTH Hardened"
usage_type = "SIGNING"
validity_period = 365
# This key exists for planned rotation -- activate when primary nears expiry
default = false
}
# L2+: Shorter certificate validity for tighter rotation cycles
resource "pingone_key" "short_lived_signing" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Short-Lived Signing Key"
algorithm = "RSA"
key_length = 2048
signature_algorithm = "SHA256withRSA"
subject_dn = "CN=PingOne Short-Lived Signing, O=HTH Hardened"
usage_type = "SIGNING"
validity_period = 180
}
3. OAuth & Token Security
3.1 Configure Secure OAuth Settings
Profile Level: L1 (Baseline) NIST 800-53: IA-5(13), SC-23
Description
Harden OAuth authorization server configuration with short token lifetimes and restricted scopes.
ClickOps Implementation (PingOne)
Step 1: Configure Token Lifetimes
- Navigate to: Applications → OAuth Settings
- Configure:
- Access Token Lifetime: 1 hour (maximum)
- Refresh Token Lifetime: 7 days (L1) / 24 hours (L2)
- ID Token Lifetime: 1 hour
- Authorization Code Lifetime: 60 seconds
Step 2: Enable Token Binding
- Navigate to: Applications → [App] → OAuth Settings
- Enable:
- Require PKCE: For public clients
- Token binding: Certificate-bound tokens (L2)
Step 3: Restrict Grant Types
- Disable unnecessary grant types:
- Implicit grant: Disabled (deprecated)
- Resource Owner Password: Disabled unless required
- Enable only: Authorization Code with PKCE
Code Implementation
See the API pack below for OAuth application configuration.
Code Pack: Terraform
# Custom resource server (API) with restricted scopes
resource "pingone_resource" "hardened_api" {
environment_id = var.pingone_environment_id
name = "HTH Hardened API"
description = "API resource with scoped access and short token lifetimes"
audience = "https://api.example.com"
access_token_validity_seconds = var.access_token_lifetime_seconds
}
# Define granular scopes for the API resource
resource "pingone_resource_scope" "read" {
environment_id = var.pingone_environment_id
resource_id = pingone_resource.hardened_api.id
name = "read"
description = "Read-only access to API resources"
}
resource "pingone_resource_scope" "write" {
environment_id = var.pingone_environment_id
resource_id = pingone_resource.hardened_api.id
name = "write"
description = "Write access to API resources"
}
# OIDC application with hardened OAuth settings
resource "pingone_application" "hardened_oidc" {
environment_id = var.pingone_environment_id
name = "HTH Hardened OIDC Application"
enabled = true
oidc_options {
type = "WEB_APP"
grant_types = ["AUTHORIZATION_CODE", "REFRESH_TOKEN"]
response_types = ["CODE"]
token_endpoint_auth_method = "CLIENT_SECRET_POST"
redirect_uris = ["https://app.example.com/callback"]
post_logout_redirect_uris = ["https://app.example.com/logout"]
# Enforce PKCE with S256
pkce_enforcement = "S256_REQUIRED"
# Token lifetimes
refresh_token_duration = var.refresh_token_lifetime_seconds
refresh_token_rolling_duration = var.refresh_token_lifetime_seconds
refresh_token_rolling_grace_period_duration = 0
}
}
# L2+: Tighten refresh token lifetime to 24 hours
resource "pingone_application" "hardened_oidc_l2" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Hardened OIDC Application (L2)"
enabled = true
oidc_options {
type = "WEB_APP"
grant_types = ["AUTHORIZATION_CODE", "REFRESH_TOKEN"]
response_types = ["CODE"]
token_endpoint_auth_method = "CLIENT_SECRET_POST"
redirect_uris = ["https://app.example.com/callback"]
post_logout_redirect_uris = ["https://app.example.com/logout"]
pkce_enforcement = "S256_REQUIRED"
# L2: Reduced refresh token lifetime (24 hours)
refresh_token_duration = 86400
refresh_token_rolling_duration = 86400
refresh_token_rolling_grace_period_duration = 0
}
}
# L3+: Disable refresh tokens entirely for maximum security
resource "pingone_application" "hardened_oidc_l3" {
count = var.profile_level >= 3 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Hardened OIDC Application (L3)"
enabled = true
oidc_options {
type = "WEB_APP"
grant_types = ["AUTHORIZATION_CODE"]
response_types = ["CODE"]
token_endpoint_auth_method = "CLIENT_SECRET_POST"
redirect_uris = ["https://app.example.com/callback"]
post_logout_redirect_uris = ["https://app.example.com/logout"]
pkce_enforcement = "S256_REQUIRED"
# L3: No refresh tokens -- force re-authentication
refresh_token_duration = 0
refresh_token_rolling_duration = 0
}
}
Code Pack: API Script
# PingOne - Configure OAuth application
curl -X PUT "https://api.pingone.com/v1/environments/${ENV_ID}/applications/${APP_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Secure App",
"protocol": "OPENID_CONNECT",
"tokenEndpointAuthMethod": "CLIENT_SECRET_POST",
"grantTypes": ["AUTHORIZATION_CODE", "REFRESH_TOKEN"],
"pkceEnforcement": "S256_REQUIRED",
"accessTokenValiditySeconds": 3600,
"refreshTokenValiditySeconds": 86400,
"refreshTokenRollingEnabled": true
}'
3.2 Implement Token Revocation
Profile Level: L1 (Baseline) NIST 800-53: AC-2(6)
Description
Enable token revocation for user sessions and compromised tokens.
ClickOps Implementation
Step 1: Enable Session Revocation
- Navigate to: Authentication → Session Management
- Enable:
- Allow session revocation: Yes
- Propagate revocation: To all connected apps
Step 2: Configure Revocation on Risk
- Navigate to: Authentication → Risk Policies
- Create rule:
- Trigger: High-risk authentication detected
- Action: Revoke all user tokens
- Notify: Security team
Step 3: Admin Revocation Capability
- Verify admin can revoke user sessions
- Document incident response procedure
- Test revocation propagation
Code Pack: Terraform
# Sign-on policy with session revocation on risk detection
resource "pingone_sign_on_policy" "risk_based_revocation" {
environment_id = var.pingone_environment_id
name = "HTH Risk-Based Token Revocation"
description = "Revoke tokens and sessions on high-risk authentication events"
}
# Risk policy: medium risk triggers step-up, high risk triggers revocation
resource "pingone_risk_policy" "token_revocation" {
environment_id = var.pingone_environment_id
name = "HTH High-Risk Revocation Policy"
default_result = { level = "LOW" }
evaluated_predictors = [
{ predictor_id = data.pingone_risk_predictor.ip_reputation.id },
{ predictor_id = data.pingone_risk_predictor.anonymous_network.id },
{ predictor_id = data.pingone_risk_predictor.geovelocity.id },
]
policy_scores = {
medium = { min_score = 40, max_score = 69 }
high = { min_score = 70, max_score = 100 }
}
}
# Data sources for built-in risk predictors
data "pingone_risk_predictor" "ip_reputation" {
environment_id = var.pingone_environment_id
name = "IP Reputation"
}
data "pingone_risk_predictor" "anonymous_network" {
environment_id = var.pingone_environment_id
name = "Anonymous Network Detection"
}
data "pingone_risk_predictor" "geovelocity" {
environment_id = var.pingone_environment_id
name = "Geo-Velocity"
}
# Sign-on policy action: high risk triggers MFA + revocation
resource "pingone_sign_on_policy_action" "high_risk_mfa" {
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.risk_based_revocation.id
priority = 1
mfa {
device_sign_on_policy_id = pingone_mfa_device_policy.phishing_resistant.id
no_device_mode = "BLOCK"
}
conditions {
last_sign_on_older_than_seconds = 3600
}
}
# L2+: Custom risk predictor for unusual token issuance volume
resource "pingone_risk_predictor" "token_velocity" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Token Velocity"
compact_name = "hthTokenVelocity"
predictor_velocity {
measure = "DISTINCT_COUNT"
of = "$${event.ip}"
by = ["$${event.user.id}"]
every = {
min_sample = 5
quantity = 1
unit = "HOUR"
}
sliding_window = {
min_sample = 5
quantity = 7
unit = "DAY"
}
use = {
type = "POISSON_WITH_MAX"
medium = 2.0
high = 4.0
}
}
}
3.3 OAuth Consent Management
Profile Level: L2 (Hardened) NIST 800-53: AC-6
Description
Control OAuth consent to prevent unauthorized application access.
ClickOps Implementation
Step 1: Enable Admin Consent Requirement
- Navigate to: Applications → Settings
- Enable: Require admin consent for new applications
- Configure approval workflow
Step 2: Review Existing Consents
- Navigate to: Identities → User → Authorized Applications
- Audit granted permissions
- Revoke unnecessary or suspicious consents
Code Pack: Terraform
# Resource scope requiring admin consent for sensitive operations (L2+)
resource "pingone_resource_scope" "admin_consent_required" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
resource_id = pingone_resource.hardened_api.id
name = "admin"
description = "Administrative scope requiring admin consent"
}
# Application resource grant -- explicitly grant only approved scopes
resource "pingone_application_resource_grant" "limited_scopes" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
application_id = pingone_application.hardened_oidc.id
resource_id = pingone_resource.hardened_api.id
scopes = [
pingone_resource_scope.read.id,
]
}
# OpenID Connect scopes -- restrict to minimum required
resource "pingone_application_resource_grant" "oidc_scopes" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
application_id = pingone_application.hardened_oidc.id
resource_id = data.pingone_resource.openid.id
scopes = [
data.pingone_resource_scope.openid.id,
data.pingone_resource_scope.profile.id,
]
}
# Data sources for OpenID scopes
data "pingone_resource" "openid" {
environment_id = var.pingone_environment_id
name = "openid"
}
data "pingone_resource_scope" "openid" {
environment_id = var.pingone_environment_id
resource_id = data.pingone_resource.openid.id
name = "openid"
}
data "pingone_resource_scope" "profile" {
environment_id = var.pingone_environment_id
resource_id = data.pingone_resource.openid.id
name = "profile"
}
# L3+: Require admin consent for all non-standard scopes
resource "pingone_resource_scope" "elevated_consent" {
count = var.profile_level >= 3 ? 1 : 0
environment_id = var.pingone_environment_id
resource_id = pingone_resource.hardened_api.id
name = "elevated"
description = "Elevated scope requiring explicit admin approval and justification"
}
4. DaVinci Orchestration Security
4.1 Secure DaVinci Flows
Profile Level: L2 (Hardened) NIST 800-53: AC-3, CM-3
Description
Harden PingOne DaVinci orchestration flows to prevent abuse and unauthorized workflow execution.
Rationale
Why This Matters:
- DaVinci flows automate identity processes
- Misconfigured flows enable privilege escalation
- Compromised flows provide persistent backdoors
ClickOps Implementation
Step 1: Implement Flow Approval Workflow
- Navigate to: DaVinci → Settings
- Enable:
- Require approval for flow changes: Yes
- Approvers: Security team
Step 2: Audit Existing Flows
- Navigate to: DaVinci → Flows
- For each flow, verify:
- Business justification documented
- Minimal permissions required
- Error handling doesn’t leak information
- Logging enabled
Step 3: Restrict Sensitive Connectors
- Identify high-risk connectors:
- User provisioning
- Group management
- Password reset
- Limit to approved flows only
- Require additional authentication for sensitive actions
Step 4: Enable Flow Logging
- Navigate to: DaVinci → Settings → Logging
- Enable:
- Log all flow executions: Yes
- Include input/output: Masked sensitive data
- Retention: 90 days minimum
Code Pack: Terraform
# DaVinci flow policy -- restrict flow execution to approved applications (L2+)
resource "pingone_application" "davinci_restricted" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH DaVinci Restricted Application"
enabled = true
description = "Hardened DaVinci application with restricted connector access"
oidc_options {
type = "WEB_APP"
grant_types = ["AUTHORIZATION_CODE"]
response_types = ["CODE"]
token_endpoint_auth_method = "CLIENT_SECRET_POST"
redirect_uris = ["https://app.example.com/callback"]
pkce_enforcement = "S256_REQUIRED"
}
}
# Sign-on policy for DaVinci flows requiring elevated authentication
resource "pingone_sign_on_policy" "davinci_elevated" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH DaVinci Elevated Auth"
description = "Requires additional authentication for sensitive DaVinci flow actions"
}
# DaVinci flow action: require MFA for sensitive connector operations
resource "pingone_sign_on_policy_action" "davinci_mfa_required" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
sign_on_policy_id = pingone_sign_on_policy.davinci_elevated[0].id
priority = 1
mfa {
device_sign_on_policy_id = pingone_mfa_device_policy.phishing_resistant.id
no_device_mode = "BLOCK"
}
}
# L3+: Strict DaVinci controls -- all flow changes require approval
resource "pingone_sign_on_policy" "davinci_strict" {
count = var.profile_level >= 3 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH DaVinci Strict Governance"
description = "Maximum security: all DaVinci changes require security team approval"
}
4.2 Version Control for Flows
Profile Level: L2 (Hardened) NIST 800-53: CM-3
Description
Implement version control and change management for DaVinci flows.
Implementation
- Export flows regularly to git repository
- Require pull request for changes
- Implement staging environment for testing
- Document rollback procedures
Code Pack: Terraform
# Worker application for DaVinci flow export automation (L2+)
# Used by CI/CD pipeline to export flows to git for version control
resource "pingone_application" "davinci_export_worker" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH DaVinci Flow Export Worker"
enabled = true
description = "Service account for automated DaVinci flow export to version control"
oidc_options {
type = "WORKER"
grant_types = ["CLIENT_CREDENTIALS"]
token_endpoint_auth_method = "CLIENT_SECRET_BASIC"
}
}
# Grant the export worker read-only access to DaVinci resources
resource "pingone_application_role_assignment" "davinci_export_readonly" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
application_id = pingone_application.davinci_export_worker[0].id
role_id = data.pingone_role.identity_data_read_only.id
scope_environment_id = var.pingone_environment_id
}
# L3+: Dedicated staging environment for flow testing before production
resource "pingone_environment" "davinci_staging" {
count = var.profile_level >= 3 ? 1 : 0
name = "HTH DaVinci Staging"
description = "Staging environment for testing DaVinci flow changes before production deployment"
type = "SANDBOX"
region = var.pingone_region
service {
type = "SSO"
}
service {
type = "DaVinci"
}
}
5. Monitoring & Detection
5.1 Configure Comprehensive Audit Logging
Profile Level: L1 (Baseline) NIST 800-53: AU-2, AU-3, AU-6
Description
Enable comprehensive audit logging for all identity operations.
ClickOps Implementation (PingOne)
Step 1: Configure Audit Settings
- Navigate to: Settings → Audit
- Enable:
- Authentication events: All
- Administrative events: All
- API events: All
- DaVinci flow events: All
Step 2: Configure Log Export
- Navigate to: Settings → Audit → Export
- Configure SIEM integration:
- S3 bucket export
- Webhook to SIEM
- Splunk integration
Step 3: Configure Alerts
- Navigate to: Settings → Alerts
- Create alerts for:
- Failed admin authentication (>5 in 5 minutes)
- New application created
- MFA policy disabled
- High-privilege role assigned
Detection Queries
See the DB pack below for SIEM detection queries.
Code Pack: Terraform
# Webhook for SIEM integration -- forward audit events to external systems
resource "pingone_webhook" "siem_integration" {
count = var.siem_webhook_url != "" ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH SIEM Integration"
enabled = true
http_endpoint_url = var.siem_webhook_url
http_endpoint_headers = {
"Content-Type" = "application/json"
}
# Subscribe to all security-relevant event types
filter_options {
included_action_types = [
"USER.CREATED",
"USER.UPDATED",
"USER.DELETED",
"USER.ACCESS_ALLOWED",
"USER.ACCESS_DENIED",
"USER.MFA_ENROLLMENT",
"USER.PASSWORD_RESET",
"APPLICATION.CREATED",
"APPLICATION.UPDATED",
"APPLICATION.DELETED",
"ROLE_ASSIGNMENT.CREATED",
"ROLE_ASSIGNMENT.DELETED",
"POLICY.CREATED",
"POLICY.UPDATED",
"POLICY.DELETED",
"FLOW.EXECUTION_COMPLETED",
"FLOW.EXECUTION_FAILED",
]
}
format = "ACTIVITY"
tls_client_auth_key_pair = {}
verify_tls_certificates = true
}
# Alert rule: failed admin authentication (>5 in 5 minutes)
resource "pingone_notification_settings_email" "security_alerts" {
environment_id = var.pingone_environment_id
host = "smtp.example.com"
port = 587
username = "alerts@example.com"
password = ""
from = { address = "pingone-alerts@example.com", name = "PingOne Security" }
}
# L2+: Extended logging with input/output capture (masked)
resource "pingone_webhook" "extended_logging" {
count = var.profile_level >= 2 && var.siem_webhook_url != "" ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Extended Audit Logging"
enabled = true
http_endpoint_url = var.siem_webhook_url
http_endpoint_headers = {
"Content-Type" = "application/json"
"X-HTH-Level" = "extended"
}
# Extended: capture all event types for comprehensive audit trail
filter_options {
included_action_types = [
"USER.CREATED",
"USER.UPDATED",
"USER.DELETED",
"USER.ACCESS_ALLOWED",
"USER.ACCESS_DENIED",
"USER.MFA_ENROLLMENT",
"USER.PASSWORD_RESET",
"APPLICATION.CREATED",
"APPLICATION.UPDATED",
"APPLICATION.DELETED",
"ROLE_ASSIGNMENT.CREATED",
"ROLE_ASSIGNMENT.DELETED",
"POLICY.CREATED",
"POLICY.UPDATED",
"POLICY.DELETED",
"FLOW.CREATED",
"FLOW.UPDATED",
"FLOW.DELETED",
"FLOW.EXECUTION_COMPLETED",
"FLOW.EXECUTION_FAILED",
"KEY.CREATED",
"KEY.UPDATED",
"KEY.DELETED",
"GRANT.CREATED",
"GRANT.DELETED",
]
}
format = "ACTIVITY"
tls_client_auth_key_pair = {}
verify_tls_certificates = true
}
# L3+: Real-time alerting on critical security events
resource "pingone_webhook" "critical_alerts" {
count = var.profile_level >= 3 && var.siem_webhook_url != "" ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Critical Security Alerts"
enabled = true
http_endpoint_url = var.siem_webhook_url
http_endpoint_headers = {
"Content-Type" = "application/json"
"X-HTH-Level" = "critical"
"X-HTH-Priority" = "1"
}
filter_options {
included_action_types = [
"ROLE_ASSIGNMENT.CREATED",
"POLICY.DELETED",
"APPLICATION.DELETED",
"KEY.DELETED",
"FLOW.DELETED",
]
}
format = "ACTIVITY"
tls_client_auth_key_pair = {}
verify_tls_certificates = true
}
6. Third-Party Integration Security
6.1 SP Connection Hardening
Profile Level: L1 (Baseline)
Description
Harden Service Provider (SP) connections in federation.
For Each SP Connection:
- ✅ Verify SP certificate validity
- ✅ Configure audience restriction
- ✅ Set minimum assertion validity
- ✅ Enable encryption (L2)
- ✅ Document business owner
Code Pack: Terraform
# Hardened SAML SP connection template with all required validations
resource "pingone_application" "hardened_sp_connection" {
environment_id = var.pingone_environment_id
name = "HTH Hardened SP Connection"
enabled = true
description = "Template SP connection with audience restriction, signed assertions, minimum validity"
saml_options {
acs_urls = ["https://sp.example.com/saml/acs"]
assertion_duration = var.saml_assertion_validity_seconds
sp_entity_id = "https://sp.example.com"
# Require signed responses and assertions
response_is_signed = true
assertion_signed = true
# SLO configuration
slo_binding = "HTTP_REDIRECT"
slo_endpoint = "https://sp.example.com/saml/slo"
# Signing configuration using hardened key
idp_signing_key {
key_id = pingone_key.saml_signing.id
algorithm = "SHA256withRSA"
}
# Require signed authn requests from SP
sp_verification {
authn_request_signed = true
}
}
}
# L2+: SP connection with encrypted assertions
resource "pingone_application" "encrypted_sp_connection" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH Encrypted SP Connection"
enabled = true
description = "SP connection with encrypted SAML assertions for sensitive applications"
saml_options {
acs_urls = ["https://secure-sp.example.com/saml/acs"]
assertion_duration = var.saml_assertion_validity_seconds
sp_entity_id = "https://secure-sp.example.com"
response_is_signed = true
assertion_signed = true
slo_binding = "HTTP_REDIRECT"
slo_endpoint = "https://secure-sp.example.com/saml/slo"
idp_signing_key {
key_id = pingone_key.saml_signing.id
algorithm = "SHA256withRSA"
}
sp_verification {
authn_request_signed = true
}
# Encryption for L2+ environments
sp_encryption {
algorithm = "AES_256"
key_transport = {
algorithm = "RSA_OAEP"
}
}
}
}
6.2 API Client Management
| Client Type | Token Lifetime | Scopes | Controls |
|---|---|---|---|
| SCIM Provisioner | 1 hour | Users, Groups | IP restriction, audit logging |
| SSO Application | 4 hours | OpenID, Profile | Standard validation |
| Admin API | 15 minutes | Admin scopes | MFA required, IP restriction |
| Reporting | 1 hour | Read-only | Dedicated service account |
Code Pack: Terraform
# SCIM Provisioner -- 1 hour token, IP-restricted, audit logging
resource "pingone_application" "scim_provisioner" {
environment_id = var.pingone_environment_id
name = "HTH SCIM Provisioner"
enabled = true
description = "SCIM provisioning client with restricted scopes and short token lifetime"
oidc_options {
type = "WORKER"
grant_types = ["CLIENT_CREDENTIALS"]
token_endpoint_auth_method = "CLIENT_SECRET_BASIC"
}
}
# SCIM provisioner role -- limited to identity data operations
resource "pingone_application_role_assignment" "scim_identity_admin" {
environment_id = var.pingone_environment_id
application_id = pingone_application.scim_provisioner.id
role_id = data.pingone_role.identity_data_admin.id
scope_environment_id = var.pingone_environment_id
}
# Admin API client -- 15 minute token, MFA required, IP-restricted
resource "pingone_application" "admin_api_client" {
environment_id = var.pingone_environment_id
name = "HTH Admin API Client"
enabled = true
description = "Administrative API client with minimal token lifetime and restricted access"
oidc_options {
type = "WORKER"
grant_types = ["CLIENT_CREDENTIALS"]
token_endpoint_auth_method = "CLIENT_SECRET_BASIC"
}
}
# Admin API role assignment -- environment admin scoped
resource "pingone_application_role_assignment" "admin_api_role" {
environment_id = var.pingone_environment_id
application_id = pingone_application.admin_api_client.id
role_id = data.pingone_role.environment_admin.id
scope_environment_id = var.pingone_environment_id
}
# Reporting client -- read-only, 1 hour token, dedicated service account
resource "pingone_application" "reporting_client" {
environment_id = var.pingone_environment_id
name = "HTH Reporting Client"
enabled = true
description = "Read-only reporting client for dashboards and compliance reports"
oidc_options {
type = "WORKER"
grant_types = ["CLIENT_CREDENTIALS"]
token_endpoint_auth_method = "CLIENT_SECRET_BASIC"
}
}
# Reporting client role -- read-only access
resource "pingone_application_role_assignment" "reporting_readonly" {
environment_id = var.pingone_environment_id
application_id = pingone_application.reporting_client.id
role_id = data.pingone_role.identity_data_read_only.id
scope_environment_id = var.pingone_environment_id
}
# L2+: SSO Application with standard validation and 4-hour token
resource "pingone_application" "sso_app_template" {
count = var.profile_level >= 2 ? 1 : 0
environment_id = var.pingone_environment_id
name = "HTH SSO Application Template"
enabled = true
description = "Standard SSO application with OpenID and Profile scopes"
oidc_options {
type = "WEB_APP"
grant_types = ["AUTHORIZATION_CODE", "REFRESH_TOKEN"]
response_types = ["CODE"]
token_endpoint_auth_method = "CLIENT_SECRET_POST"
redirect_uris = ["https://app.example.com/callback"]
pkce_enforcement = "S256_REQUIRED"
refresh_token_duration = 14400
refresh_token_rolling_duration = 14400
}
}
7. Compliance Quick Reference
SOC 2 Mapping
| Control ID | Ping Identity Control | Guide Section |
|---|---|---|
| CC6.1 | MFA enforcement | 1.1 |
| CC6.2 | RBAC | 1.2 |
| CC6.6 | IP restrictions | 1.3 |
| CC7.2 | Audit logging | 5.1 |
NIST 800-53 Mapping
| Control | Ping Identity Control | Guide Section |
|---|---|---|
| IA-2(6) | Phishing-resistant MFA | 1.1 |
| IA-5 | Federation security | 2.1 |
| SC-23 | Token security | 3.1 |
| AU-2 | Audit logging | 5.1 |
Appendix A: Edition Compatibility
| Control | PingOne Essentials | PingOne Plus | PingOne Enterprise |
|---|---|---|---|
| MFA | ✅ | ✅ | ✅ |
| FIDO2 | ❌ | ✅ | ✅ |
| DaVinci | ❌ | Limited | ✅ |
| Risk-Based Auth | ❌ | ❌ | ✅ |
| API Access | Limited | ✅ | ✅ |
Appendix B: References
Official Ping Identity Documentation:
- Trust Center
- Security Exhibits
- Documentation Portal
- PingOne Security Guide
- PingFederate Administration
- Security Best Practices
API Documentation:
Compliance Frameworks:
- SOC 2 Type II, ISO 27001, ISO 27018, CSA STAR Level 2 — via Trust Center
Security Incidents:
- No major public security incidents involving Ping Identity infrastructure have been identified. Ping Identity operates a bug bounty program through HackerOne.
Changelog
| Date | Version | Maturity | Changes | Author |
|---|---|---|---|---|
| 2025-12-14 | 0.1.0 | draft | Initial Ping Identity hardening guide | Claude Code (Opus 4.5) |