Developer Tutorial: Part I: A SAML SSO flow from the Command Line with cURL
Part I of the tutorial will build a BASH script to run a SAML flow with cURL against the SSOCircle IDP (IDP) and the Fedlet Test SAML service provider (SP). For an introduction to the tutorial visit here. Part II will add SAML Testing functionality to the script.
At the end of Part I our script should run the following steps:
- CLIENT accesses the SP (Initiate the SSO flow – this is typically a “sign in” button or simply a request to a protected page)
- SP replies with a SAML AuthnRequest message (typically a 302 redirect to the IDP)
- CLIENT follows the redirect and sends the SAML AuthnRequest message to IDP
- If user is not already authenticated with the IDP
- a. IDP redirects to the IDP login page
- b. CLIENT submits credentials vial the login form to IDP
- c. IDP authenticates the user and redirects the user to continue with SSO
- IDP generates the SAML Response with the Assertion and sends it back to the CLIENT (typically this is a HTML page with an auto-posted form)
- SKIPPED (will be implemented in Part II)
- SKIPPED (will be implemented in Part II)
- CLIENT continues posting the SAML Response message to the SP
- SP receives the SAML Response message and authenticates the user based on the Assertion (if SSO is successful the SP typically replies with a “logged in” page or the protected content)
- CLIENT receives the SP response and evaluates if the SSO flow was successful (user is logged in)
Let’s start with step 1. CLIENT accesses the SP (initiate the SSO flow)
This is simply a cURL command which stores cookies for subsequent use:
curl -L -c cookies -b cookies -s -o - "https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com"
We start wrapping this stuff in variables as we will reuse the commands and output. Our first building snippet would look like:
CURL="curl -L -c cookies -b cookies -s -o -"
INITURL="https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com"
OUTPUT=$($CURL -L -c cookies -b cookies -s -o - ${INITURL})
Copy this snippet into a file called samltest.sh, put a #!/bin/bash at the beginning and make it executable (chmod +x ssocheck.sh). Run the script by executing ./ssocheck.sh.
Our first script would be
#!/bin/bash
# Variable Definitions
CURL="curl -L -c cookies -b cookies -s -o -"
INITURL="https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com"
# Run use case
OUTPUT=$($CURL -L -c cookies -b cookies -s -o - ${INITURL})
# Just a control output
echo $OUTPUT
The output of running the script above will be a lengthy HTML page, which is the login page of the SSOCircle IDP.
Goals achieved: step 1,2,3 and 4a. We have implemented step 1 and simply got step 2,3,4a for free as cURL follows 302-redirects automatically.
TIP: If you do not get the expected output. Add a -v option to the cURL command which makes cURL run more verbose.
Next stop: 4b: CLIENT submits credentials via the login form to IDP
We should keep in mind that the login page does only appear if the user is not already logged in to the IDP. For this reason, we add the logic to recognize the login page and if it is the login page we simply submit the credentials (username and password in that case).
Let’s start to put this part into a function to achieve a little modularity. Our new code snippet would look like:
USERNAME=<YOUR SSOCIRCLE USER>
PASSWORD=<YOUR SSOCIRCLE USER PASSWORD>
LOGINURL="https://idp.ssocircle.com/sso/UI/Login"
# recognize the login page through simply pattern match for the page title and submit credentials
function doLogin
{
>&2 echo "doLogin"
local in="$1"
if [[ $in =~ "SSOCircle Identity Provider (Login)" ]]
then
>&2 echo "Login page detected"
local in="$1"
local goto=$(echo "$in" | grep -m 1 name=\"goto\" | cut -d '"' -f6)
local response=$($CURL --data-urlencode IDToken1="${USERNAME}" --data-urlencode IDToken2="${PASSWORD}" --data-urlencode goto="${goto}" ${LOGINURL})
>&2 echo "Logging in to IDP"
echo "$response"
else
>&2 echo "Already logged in to IDP"
echo "$in"
fi
}
Note that we have defined new variables USERNAME, PASSWORD and LOGINURL
Let’s put this into the complete script, so that we can run it:
#!/bin/bash
USERNAME=<YOUR SSOCIRCLE USER>
PASSWORD=<YOUR SSOCIRCLE USER PASSWORD>
#Variable definitions
CURL="curl -L -c cookies -b cookies -s -o -"
INITURL=https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com
LOGINURL="https://idp.ssocircle.com/sso/UI/Login"
#functions
# recognize the login page through simply pattern match for the page title and submit credentials
function doLogin
{
>&2 echo "doLogin"
local in="$1"
if [[ $in =~ "SSOCircle Identity Provider (Login)" ]]
then
>&2 echo "Login page detected"
local in="$1"
local goto=$(echo "$in" | grep -m 1 name=\"goto\" | cut -d '"' -f6)
local response=$($CURL --data-urlencode IDToken1="${USERNAME}" --data-urlencode IDToken2="${PASSWORD}" --data-urlencode goto="${goto}" ${LOGINURL})
>&2 echo "Logging in to IDP"
echo "$response"
else
>&2 echo "Already logged in to IDP"
echo "$in"
fi
}
# MAIN ROUTINE
#Run use case
OUTPUT=$($CURL -L -c cookies -b cookies -s -o - ${INITURL})
# check if we need to login to the IDP
>&2 echo "Checking Login"
OUTPUT=$(doLogin "$OUTPUT")
# Just a control output
echo $OUTPUT
As the output of running the script above you will retrieve another HTML output. The HTML page basically is the HTML form which contains the SAML assertion and a directive to automatically post the form to the SP on page load.
Goals achieved: step 4b,4c, and 5. As step 6 and 7 will be implemented in Part II of the tutorial, we can skip it for now and proceed with step 8.
In step 8 the script needs to be capable to recognize the HTML form, retrieve the parameters and then POST the parameters to the SP. Normally it is a simple task, if you process the HTML page in a browser. A bash based cURL script needs some programming logic to accomplish the task:
We are going to divide this step into two functions: recognizing and retrieving parameters out of the form and the actual POSTing of the parameters to the SP.
# recognizes a page with body on load and
# retrieves the parameters from the form
function onloadSAML
{
>&2 echo "onloadSAML"
local in="$1"
if [[ $in =~ "body onload=" ]]
then
>&2 echo "Autopost detected"
ACTION_URL=$(echo "$in" | grep "action=" | cut -d '"' -f4 | recode html)
SAML_RESPONSE=$(echo "$in" | grep SAMLResponse | cut -d '"' -f6)
RELAY_STATE=$(echo "$in" | grep RelayState | cut -d '"' -f6 )
fi
}
# function that actually posts the SAML to the Service Provider
function postSAML
{
>&2 echo "postSAML"
local encodeopt="$1"
local url="$2"
local saml="$3"
local relay="$4"
local response=$($CURL --${encodeopt} SAMLResponse="${saml}" --data-urlencode RelayState="${relay}" ${url})
echo "$response"
}
We need to include these functions into the complete script and add the function call to the use case implementation. We also added decoding of the parameters and some error handling if no SAML response is returned from the IDP. Our script looks now:
#!/bin/bash
USERNAME=<YOUR SSOCIRCLE USER>
PASSWORD=<YOUR SSOCIRCLE USER PASSWORD>
#Variable definitions
CURL="curl -L -c cookies -b cookies -s -o -"
INITURL="https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com"
LOGINURL="https://idp.ssocircle.com/sso/UI/Login"
#functions
# recognize the login page through simply pattern match for the page title and submit credentials
function doLogin
{
>&2 echo "doLogin"
local in="$1"
if [[ $in =~ "SSOCircle Identity Provider (Login)" ]]
then
>&2 echo "Login page detected"
local in="$1"
local goto=$(echo "$in" | grep -m 1 name=\"goto\" | cut -d '"' -f6)
local response=$($CURL --data-urlencode IDToken1="${USERNAME}" --data-urlencode IDToken2="${PASSWORD}" --data-urlencode goto="${goto}" ${LOGINURL})
>&2 echo "Logging in to IDP"
echo "$response"
else
>&2 echo "Already logged in to IDP"
echo "$in"
fi
}
# recognizes a page with body on load and
# retrieves the parameters from the form
function onloadSAML
{
>&2 echo "onloadSAML"
local in="$1"
if [[ $in =~ "body onload=" ]]
then
>&2 echo "Autopost detected"
ACTION_URL=$(echo "$in" | grep "action=" | cut -d '"' -f4 | recode html)
SAML_RESPONSE=$(echo "$in" | grep SAMLResponse | cut -d '"' -f6)
RELAY_STATE=$(echo "$in" | grep RelayState | cut -d '"' -f6 )
fi
}
# function that actually posts the SAML to the Service Provider
function postSAML
{
>&2 echo "postSAML"
local encodeopt="$1"
local url="$2"
local saml="$3"
local relay="$4"
local response=$($CURL --${encodeopt} SAMLResponse="${saml}" --data-urlencode RelayState="${relay}" ${url})
echo "$response"
}
# MAIN ROUTINE
# Run use case: The SAML SSO flow
OUTPUT=$($CURL -L -c cookies -b cookies -s -o - ${INITURL})
# check if we need to login to the IDP
>&2 echo "Checking Login"
OUTPUT=$(doLogin "$OUTPUT")
# Evaluate the SAML autoposting page
>&2 echo "Autoposting"
onloadSAML "$OUTPUT"
# decode the parameters
SAML="$(echo -n $SAML_RESPONSE | recode html | tr -d '\r\n')"
RELAY="$(echo -n $RELAY_STATE | recode html)"
# some error handling
if [ -z "$SAML" ]
then
echo "Error: No SAML payload received"
exit
fi
# Send the message to the service provider
>&2 echo "Submitting the response"
OUTPUT=$(postSAML data-urlencode $ACTION_URL "$SAML" "$RELAY")
# Just a control output
echo $OUTPUT
When the script is run the output will be the HTML page which is returned from the SP after receiving the SAML message. If everything is fine it should be a page indicating that SSO was successful and the user is authenticated.
Goals achieved: step 8 and 9. The only thing missing now, is step 9. Recognizing the user is authenticated.
Let’s continue to implement the check whether the SSO process was successful and the user is authenticated to the SP. In case of our sample SP, a page will be returned that contains the string “Single Sign-On successful”. For a positive check we simple parse for that:
# function checks whether login to the service provider was successful
# and returns 0 on success and 1 if not
function loginSuccess
{
>&2 echo "loginSuccess"
local in="$1"
if [[ $in =~ "Single Sign-On successful" ]]
then
# success
echo 0
else
echo 1
fi
}
Put this small snippet into our script to finish the script for Part I of the tutorial.
#!/bin/bash
USERNAME=<YOUR SSOCIRCLE USER>
PASSWORD=<YOUR SSOCIRCLE USER PASSWORD>
#Variable definitions
CURL="curl -L -c cookies -b cookies -s -o -"
INITURL="https://fedlet.idpee.com/sp/saml2/jsp/fedletSSOInit.jsp?metaAlias=/sp&idpEntityID=http%3A%2F%2Fidp.ssocircle.com"
LOGINURL="https://idp.ssocircle.com/sso/UI/Login"
LOGOUTURL="https://fedlet.idpee.com/sp/localLO.jsp"
#functions
# recognize the login page through simply pattern match for the page title and submit credentials
function doLogin
{
>&2 echo "doLogin"
local in="$1"
if [[ $in =~ "SSOCircle Identity Provider (Login)" ]]
then
>&2 echo "Login page detected"
local in="$1"
local goto=$(echo "$in" | grep -m 1 name=\"goto\" | cut -d '"' -f6)
local response=$($CURL --data-urlencode IDToken1="${USERNAME}" --data-urlencode IDToken2="${PASSWORD}" --data-urlencode goto="${goto}" ${LOGINURL})
>&2 echo "Logging in to IDP"
echo "$response"
else
>&2 echo "Already logged in to IDP"
echo "$in"
fi
}
# recognizes a page with body on load and
# retrieves the parameters from the form
function onloadSAML
{
>&2 echo "onloadSAML"
local in="$1"
if [[ $in =~ "body onload=" ]]
then
>&2 echo "Autopost detected"
ACTION_URL=$(echo "$in" | grep "action=" | cut -d '"' -f4 | recode html)
SAML_RESPONSE=$(echo "$in" | grep SAMLResponse | cut -d '"' -f6)
RELAY_STATE=$(echo "$in" | grep RelayState | cut -d '"' -f6 )
fi
}
# function that actually posts the SAML to the Service Provider
function postSAML
{
>&2 echo "postSAML"
local encodeopt="$1"
local url="$2"
local saml="$3"
local relay="$4"
local response=$($CURL --${encodeopt} SAMLResponse="${saml}" --data-urlencode RelayState="${relay}" ${url})
echo "$response"
}
# function checks whether login to the service provider was successful
# and returns 0 on success and 1 if not
function loginSuccess
{
>&2 echo "loginSuccess"
local in="$1"
if [[ $in =~ "Single Sign-On successful" ]]
then
# success
echo 0
else
echo 1
fi
}
####################################
# MAIN ROUTINE
####################################
# check user and password are set
if [ "$USERNAME" == '<YOUR SSOCIRCLE USER>' ]
then
echo "Username not set - exit"
exit 1
fi
# Run use case: The SAML SSO flow
OUTPUT=$($CURL ${INITURL})
# check if we need to login to the IDP
>&2 echo "Checking Login"
OUTPUT=$(doLogin "$OUTPUT")
# Evaluate the SAML autoposting page
>&2 echo "Autoposting"
onloadSAML "$OUTPUT"
# decode the parameters
SAML="$(echo -n $SAML_RESPONSE | recode html | tr -d '\r\n')"
RELAY="$(echo -n $RELAY_STATE | recode html)"
# some error handling
if [ -z "$SAML" ]
then
echo "Error: No SAML payload received"
exit
fi
# Send the message to the service provider
>&2 echo "Submitting the response"
OUTPUT=$(postSAML data-urlencode $ACTION_URL "$SAML" "$RELAY")
# Check the output for successful SSO
SUCCESS=$(loginSuccess "$OUTPUT")
# DO a SP local Logout
OUTPUT=$($CURL ${LOGOUTURL})
# Just a control output
if [ $SUCCESS == 0 ]
then
echo "SSO successful"
exit 0
else
echo "SSO failed"
exit 1
fi
The script will output a “SSO successful” message, if everything runs fine. Don’t forget to insert your user credentials at the beginning.
Please note that we have added a local logout from the SP. This will help us to repeat the SSO flow.
End of tutorial part I. The script does now accomplish the task that we targeted: Run a full SAML SSO flow from the command line. Optionally the script is authenticating with the user credentials to SSOCircle. At the end the script evaluates whether the login was successful and prints the result.
In tutorial part II we are using the script again and we will add SAML testing functionality leveraging the SSOCheck SAML Test API. The script will then run in a loop and execute almost 100 SAML conformity and security tests.
Read Part II of the tutorial.