This guide demonstrates how to authenticate to CyberArk's Self-Hosted Privileged Access Management (PAM) REST API using PingFederate SAML authentication with PowerShell.
The script implements a complete SAML authentication flow that:
- Initiates SAML authentication with CyberArk
- Redirects to PingFederate for authentication
- Processes the SAML response
- Exchanges the SAML assertion for a CyberArk session token
<#
.SYNOPSIS
Authenticates to CyberArk's Self-Hosted PAM REST API using PingFederate SAML.
.DESCRIPTION
This script performs SAML-based authentication through PingFederate to CyberArk's
PAM REST API using the /saml/logon endpoint. It handles the full authentication
flow and returns a session token that can be used for subsequent API calls.
.PARAMETER CyberArkUrl
Base URL for CyberArk PAM (e.g., 'https://pam.example.com')
.PARAMETER Username
Username for PingFederate authentication
.PARAMETER Password
Password for PingFederate authentication
.PARAMETER SkipCertificateCheck
Switch to skip SSL certificate validation (for testing only)
.EXAMPLE
.\CyberArk-PingFederate-Auth.ps1 -CyberArkUrl "https://pam.example.com" -Username "[email protected]" -Password "Password123"
.NOTES
Author: CyberArk DevOps Solutions Engineer
Date: March 27, 2025
Requirements: PowerShell 5.1 or higher
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$CyberArkUrl,
[Parameter(Mandatory = $true)]
[string]$Username,
[Parameter(Mandatory = $true)]
[string]$Password,
[Parameter(Mandatory = $false)]
[switch]$SkipCertificateCheck
)
# Set error action preference
$ErrorActionPreference = "Stop"
# Create function to handle HTML parsing
function Get-HtmlElement {
param (
[Parameter(Mandatory = $true)]
[string]$Html,
[Parameter(Mandatory = $true)]
[string]$TagName,
[Parameter(Mandatory = $false)]
[hashtable]$Attributes
)
# Load HTML Agility Pack if available, otherwise use regex
try {
# Try to load HTML Agility Pack if available
if (-not ([System.Management.Automation.PSTypeName]'HtmlAgilityPack.HtmlDocument').Type) {
Add-Type -Path (Join-Path $PSScriptRoot "HtmlAgilityPack.dll") -ErrorAction SilentlyContinue
}
if (([System.Management.Automation.PSTypeName]'HtmlAgilityPack.HtmlDocument').Type) {
$doc = New-Object HtmlAgilityPack.HtmlDocument
$doc.LoadHtml($Html)
$xpath = "//$TagName"
if ($Attributes) {
$conditions = @()
foreach ($key in $Attributes.Keys) {
$conditions += "@$key='$($Attributes[$key])'"
}
$xpath += "[" + ($conditions -join " and ") + "]"
}
return $doc.DocumentNode.SelectNodes($xpath)
}
}
catch {
Write-Verbose "HTML Agility Pack not available, falling back to regex"
}
# Fallback to simple regex-based parsing (less reliable)
$pattern = "<$TagName"
if ($Attributes) {
foreach ($key in $Attributes.Keys) {
$pattern += "(?:.*?)$key=`"$($Attributes[$key])`""
}
}
$pattern += "(?:.*?)>(.*?)</$TagName>"
$matches = [regex]::Matches($Html, $pattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
return $matches
}
function Get-FormInputs {
param (
[Parameter(Mandatory = $true)]
[string]$Html,
[Parameter(Mandatory = $false)]
[string]$FormId
)
$inputs = @{}
# Try to use HTML Agility Pack first
try {
if (([System.Management.Automation.PSTypeName]'HtmlAgilityPack.HtmlDocument').Type) {
$doc = New-Object HtmlAgilityPack.HtmlDocument
$doc.LoadHtml($Html)
$formNode = $null
if ($FormId) {
$formNode = $doc.DocumentNode.SelectSingleNode("//form[@id='$FormId']")
}
else {
$formNode = $doc.DocumentNode.SelectSingleNode("//form")
}
if ($formNode) {
$inputNodes = $formNode.SelectNodes(".//input")
foreach ($input in $inputNodes) {
$name = $input.GetAttributeValue("name", $null)
$value = $input.GetAttributeValue("value", "")
if ($name) {
$inputs[$name] = $value
}
}
# Get form action
$action = $formNode.GetAttributeValue("action", $null)
if ($action) {
$inputs["__form_action"] = $action
}
}
return $inputs
}
}
catch {
Write-Verbose "Error using HTML Agility Pack: $_"
}
# Fallback to regex
# Extract form if form ID is specified
if ($FormId) {
$formPattern = "<form(?:.*?)id=`"$FormId`"(?:.*?)>(.*?)</form>"
$formMatch = [regex]::Match($Html, $formPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
if ($formMatch.Success) {
$Html = $formMatch.Groups[1].Value
}
}
# Get form action
$actionPattern = "<form(?:.*?)action=`"([^`"]*)`""
$actionMatch = [regex]::Match($Html, $actionPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
if ($actionMatch.Success) {
$inputs["__form_action"] = $actionMatch.Groups[1].Value
}
# Get input elements
$inputPattern = "<input(?:.*?)name=`"([^`"]*)`"(?:.*?)(?:value=`"([^`"]*)`")?(?:.*?)/?>"
$inputMatches = [regex]::Matches($Html, $inputPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)
foreach ($match in $inputMatches) {
$name = $match.Groups[1].Value
$value = $match.Groups[2].Value
if ($name) {
$inputs[$name] = $value
}
}
return $inputs
}
function Extract-SAMLResponse {
param (
[Parameter(Mandatory = $true)]
[string]$Html
)
$inputs = Get-FormInputs -Html $Html
return $inputs["SAMLResponse"]
}
# Skip certificate validation if requested (for testing only)
if ($SkipCertificateCheck) {
Write-Warning "SSL certificate validation has been disabled. This should only be used for testing."
# For PowerShell 5.1
if (-not ([System.Management.Automation.PSTypeName]'ServerCertificateValidationCallback').Type) {
$certCallback = @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class ServerCertificateValidationCallback
{
public static void Ignore()
{
if(ServicePointManager.ServerCertificateValidationCallback == null)
{
ServicePointManager.ServerCertificateValidationCallback +=
delegate
(
Object obj,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors errors
)
{
return true;
};
}
}
}
"@
Add-Type $certCallback -ErrorAction SilentlyContinue
[ServerCertificateValidationCallback]::Ignore()
}
# For PowerShell Core
if ($PSVersionTable.PSEdition -eq 'Core') {
$PSDefaultParameterValues.Add('Invoke-RestMethod:SkipCertificateCheck', $true)
$PSDefaultParameterValues.Add('Invoke-WebRequest:SkipCertificateCheck', $true)
}
}
# Ensure CyberArkUrl doesn't end with a trailing slash
$CyberArkUrl = $CyberArkUrl.TrimEnd('/')
# Create a session to maintain cookies
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
Write-Host "Step 1: Initiating SAML authentication with CyberArk..." -ForegroundColor Cyan
try {
# Initial request to CyberArk SAML logon endpoint
$initialResponse = Invoke-WebRequest -Uri "$CyberArkUrl/PasswordVault/API/auth/saml/logon" -WebSession $session -UseBasicParsing
# Extract form data from response
$formData = Get-FormInputs -Html $initialResponse.Content
if (-not $formData -or -not $formData["__form_action"]) {
throw "Failed to extract SAML request form from CyberArk response"
}
$pingFedUrl = $formData["__form_action"]
$formData.Remove("__form_action")
Write-Verbose "PingFederate URL: $pingFedUrl"
Write-Host "Step 2: Submitting SAML request to PingFederate..." -ForegroundColor Cyan
# Build form data for PingFederate request
$pingFedFormData = @{}
foreach ($key in $formData.Keys) {
$pingFedFormData[$key] = $formData[$key]
}
# Submit SAML request to PingFederate
$pingFedResponse = Invoke-WebRequest -Uri $pingFedUrl -Method POST -Body $pingFedFormData -WebSession $session -UseBasicParsing
# Extract login form from PingFederate response
$loginFormData = Get-FormInputs -Html $pingFedResponse.Content -FormId "loginForm"
if (-not $loginFormData -or -not $loginFormData["__form_action"]) {
# Check if we're already logged in and have a SAML response
$samlResponse = Extract-SAMLResponse -Html $pingFedResponse.Content
if ($samlResponse) {
Write-Host "Already authenticated with PingFederate, proceeding with SAML response..." -ForegroundColor Green
}
else {
throw "Failed to extract login form from PingFederate response"
}
}
else {
$loginUrl = $loginFormData["__form_action"]
$loginFormData.Remove("__form_action")
# Add username and password to form data
$loginFormData["username"] = $Username
$loginFormData["password"] = $Password
Write-Host "Step 3: Authenticating with PingFederate..." -ForegroundColor Cyan
# Submit credentials to PingFederate
$authResponse = Invoke-WebRequest -Uri $loginUrl -Method POST -Body $loginFormData -WebSession $session -UseBasicParsing
# Check for additional authentication pages (e.g., MFA)
# This is a simplified implementation - you may need to add handling for MFA challenges
# Extract SAML response from authentication response
$samlResponse = Extract-SAMLResponse -Html $authResponse.Content
if (-not $samlResponse) {
throw "Failed to obtain SAML response from PingFederate"
}
}
Write-Host "Step 4: Submitting SAML response to CyberArk..." -ForegroundColor Cyan
# Create payload for CyberArk authentication
$cyberArkPayload = @{
SAMLResponse = $samlResponse
} | ConvertTo-Json
# Submit SAML response to CyberArk
$headers = @{
"Content-Type" = "application/json"
}
$cyberArkAuthResponse = Invoke-RestMethod -Uri "$CyberArkUrl/PasswordVault/API/auth/saml/logon" -Method POST -Body $cyberArkPayload -Headers $headers -WebSession $session
# Extract session token
if ($cyberArkAuthResponse.CyberArkLogonResult) {
Write-Host "Authentication successful!" -ForegroundColor Green
# Output results
$result = @{
Success = $true
SessionToken = $cyberArkAuthResponse.CyberArkLogonResult
Username = $cyberArkAuthResponse.UserName
Source = $cyberArkAuthResponse.Source
SessionId = $cyberArkAuthResponse.SessionID
}
# Return the session token and other details
Write-Output $result
# Example of how to use the session token
Write-Host "`nExample API call using the session token:" -ForegroundColor Yellow
Write-Host "Invoke-RestMethod -Uri '$CyberArkUrl/PasswordVault/API/Accounts' -Method GET -Headers @{`"Authorization`" = `"$($cyberArkAuthResponse.CyberArkLogonResult)`"}"
return $result
}
else {
throw "Authentication failed. CyberArk did not return a session token."
}
}
catch {
Write-Host "Error during authentication: $_" -ForegroundColor Red
$errorDetails = @{
Success = $false
Error = $_.Exception.Message
}
if ($_.Exception.Response) {
try {
$reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
$errorDetails.ResponseContent = $reader.ReadToEnd()
$reader.Close()
}
catch {
$errorDetails.ResponseContent = "Could not read response content"
}
}
return $errorDetails
}
Since PowerShell doesn't have built-in HTML parsing capabilities, the script includes three custom functions:
-
Get-HtmlElement - Extracts specific HTML elements:
- Tries to use HTML Agility Pack if available
- Falls back to regex-based parsing if necessary
-
Get-FormInputs - Extracts form inputs and action URLs:
- Parses all input elements within a form
- Returns a hashtable with input names/values
- Includes the form's action URL as a special "__form_action" key
-
Extract-SAMLResponse - Specifically extracts SAML responses:
- Uses Get-FormInputs to find the SAMLResponse input
- Simplifies handling this critical authentication component
For development and testing environments, the script includes certificate validation bypass:
if ($SkipCertificateCheck) {
# Different approaches for PowerShell 5.1 vs PowerShell Core
# Creates certificate validation callback that always returns true
}
This is implemented differently for PowerShell 5.1 (using a custom C# callback class) and PowerShell Core (using built-in parameters).
The authentication flow follows these steps:
-
Initiate SAML Authentication with CyberArk
$initialResponse = Invoke-WebRequest -Uri "$CyberArkUrl/PasswordVault/API/auth/saml/logon" -WebSession $session
This request to CyberArk's SAML logon endpoint returns an HTML form with a SAML request.
-
Extract and Submit SAML Request to PingFederate
$formData = Get-FormInputs -Html $initialResponse.Content $pingFedUrl = $formData["__form_action"] $pingFedResponse = Invoke-WebRequest -Uri $pingFedUrl -Method POST -Body $pingFedFormData -WebSession $session
The SAML request is extracted and submitted to PingFederate.
-
Submit Credentials to PingFederate
$loginFormData["username"] = $Username $loginFormData["password"] = $Password $authResponse = Invoke-WebRequest -Uri $loginUrl -Method POST -Body $loginFormData -WebSession $session
Username and password are submitted to PingFederate's login form.
-
Extract SAML Response
$samlResponse = Extract-SAMLResponse -Html $authResponse.Content
After successful authentication, PingFederate returns a SAML response.
-
Submit SAML Response to CyberArk
$cyberArkPayload = @{ SAMLResponse = $samlResponse } | ConvertTo-Json $cyberArkAuthResponse = Invoke-RestMethod -Uri "$CyberArkUrl/PasswordVault/API/auth/saml/logon" -Method POST -Body $cyberArkPayload
The SAML response is submitted to CyberArk to obtain a session token.
The script uses PowerShell's WebRequestSession to maintain cookies across requests:
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
This ensures cookies set during the authentication flow are preserved for subsequent requests.
Comprehensive error handling provides detailed information about failures:
catch {
$errorDetails = @{
Success = $false
Error = $_.Exception.Message
}
if ($_.Exception.Response) {
# Extract response content from failed web requests
}
}
The script attempts to extract response content from failed web requests, which can be invaluable for troubleshooting authentication issues.
The script includes a placeholder for Multi-Factor Authentication (MFA):
# Check for additional authentication pages (e.g., MFA)
# This is a simplified implementation - you may need to add handling for MFA challenges
To support MFA, additional logic would need to be added to detect and respond to MFA challenges from PingFederate.
.\CyberArk-PingFederate-Auth.ps1 -CyberArkUrl "https://pam.example.com" -Username "[email protected]" -Password "Password123"
.\CyberArk-PingFederate-Auth.ps1 -CyberArkUrl "https://pam.example.com" -Username "[email protected]" -Password "Password123" -SkipCertificateCheck
After successful authentication, the script provides an example of how to use the obtained session token:
Invoke-RestMethod -Uri "https://pam.example.com/PasswordVault/API/Accounts" -Method GET -Headers @{"Authorization" = "your-session-token"}
This script provides a complete implementation for authenticating to CyberArk's Self-Hosted PAM REST API using PingFederate SAML. It handles the entire authentication flow, from initiating the SAML request to exchanging the SAML assertion for a CyberArk session token.
The modular design with custom HTML parsing functions makes it adaptable to different PingFederate configurations and can be extended to support additional authentication factors if required.