"Representational State Transfer (REST) is a software architecture style for building scalable web services." [1] [2]
The Authentication Chain
OpenAM's authentication service can be composed of multiple authentication modules. The authentication chain consists 4 authentication modules including LDAP and device fingerprint. The relevance of such a chain was discussed in a previous article titled "Smarter Security with Device Fingerprints".
For the purpose of simplicity, the OneTimePassword is replaced with a DataStore authentication module. Rather than providing a one time password (e.g. SMS passcode), the user has to provide username and password (same or different than before depending on the OpenAM configuration).
As there is no browser involved, any json structure can be sent back as the fingerprint.
The authentication process consists of 6 steps if no corresponding and valid device fingerprint was previously registered. In case it was, the authentication finishes after step 3.
The Tools: CURL, JQ and the Scripts
As client in this example serves the straightforward curl tool. This allows easy investigation of requests and responses.
Note that the JWT (JSON Web Token) needs to be passed on between all callbacks. For extracting specific elements of json, like the JWT, the jq tool can be of great use (for instance JWT_TOKEN=`echo ${CURL_RESPONSE} | jq --raw-output '.authId'`).
In case you want to build this example or something similar, I published in a GitHub project scripts which could be of inspiration (see in particular 620-deviceid-base-config, 621-deviceid-rest-base, 622-deviceid-rest-ootb).
The Authentication Steps in Detail [3]
Step 1: Get the authentication token
An "empty" request is sent to obtain the JWT (JSON Web Token) and the first set of callbacks.
Request
curl -s --request POST --header "Content-Type: application/json" https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm
Response
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "LDAP1",
"header": "Sign in to OpenAM",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": "User Name:"
}
],
"input": [
{
"name": "IDToken1",
"value": ""
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": "Password:"
}
],
"input": [
{
"name": "IDToken2",
"value": ""
}
]
}
]
}
Step 2: Provide callbacks for LDAP moduleThe JWT is passed with any subsequent request in this authentication process. Username and password are passed as required by the LDAP authentication module's callback.
Request
curl -s --request POST --header "Content-Type: application/json" --data @callback-step2.json https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm
callback-step2.json :
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "LDAP1",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": " User Name: "
}
],
"input": [
{
"name": "IDToken1",
"value": "demo"
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": " Password: "
}
],
"input": [
{
"name": "IDToken2",
"value": "changeit"
}
]
}
]
}
ResponseAfter successful LDAP authentication, the JavaScript which computes the fingerprint is sent in the TextOutputCallback.
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceIdMatch2",
"header": "Sign in to OpenAM",
"callbacks": [
{
"type": "HiddenValueCallback",
"output": [
{
"name": "value",
"value": ""
}
],
"input": [
{
"name": "IDToken1",
"value": "clientScriptOutputData"
}
]
},
{
"type": "TextOutputCallback",
"output": [
{
"name": "message",
"value": "JAVASCRIPT TO BE EXECUTED IN THE CLIENT BROWSER. OMITTED FOR READABILITY"
},
{
"name": "messageType",
"value": "4"
}
]
}
]
}
Step 3: Provide callbacks for DeviceId (Match) moduleThe fingerprint is sent back to the authentication process through the HiddenValueCallback. As there is no browser involved, any json element can be sent in. Note that the value element is of type String and the String contains the json. So properly encode the json (i.e. escape double quotes).
Request
curl -s --request POST --header "Content-Type: application/json" --data @callback-step3.json https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm callback-step3.json :
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceIdMatch2",
"callbacks": [
{
"type": "HiddenValueCallback",
"input": [
{
"name": "IDToken1",
"value": "THE FINGERPRINT GATHERED "
}
]
},
{
"type": "TextOutputCallback",
"output": [
{
"name": "messageType",
"value": "4"
}
]
}
]
}
Response
If the fingerprint corresponds to a registered fingerprint, then the authentication process would return successfully at this point. Otherwise (in this case), a second factor is required (DataStore module). {
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DataStore1",
"header": "Sign in to OpenAM",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": "User Name:"
}
],
"input": [
{
"name": "IDToken1",
"value": ""
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": "Password:"
}
],
"input": [
{
"name": "IDToken2",
"value": ""
}
]
}
]
}
Step 4: Provide callbacks for DataStore moduleRequest
curl -s --request POST --header "Content-Type: application/json" --data @callback-step4.json https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm
callback-step4.json :
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DataStore1",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": " User Name: "
}
],
"input": [
{
"name": "IDToken1",
"value": "demo"
}
]
},
{
"type": "PasswordCallback",
"output": [
{
"name": "prompt",
"value": " Password: "
}
],
"input": [
{
"name": "IDToken2",
"value": "changeit"
}
]
}
]
}
Response
Now that both factors succeeded, the user is asked whether to store the device fingerprint or not.{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceIdSave2",
"header": "Add to Trusted Devices",
"callbacks": [
{
"type": "ChoiceCallback",
"output": [
{
"name": "prompt",
"value": "Add to Trusted Devices?"
},
{
"name": "choices",
"value": [
"Yes",
"No"
]
},
{
"name": "defaultChoice",
"value": 1
}
],
"input": [
{
"name": "IDToken1",
"value": 1
}
]
}
]
}
Step 5: Provide callbacks for DeviceId (Save) module - Consent to store device profile\The value 0 in the ChoiceCallback indicates that the user want to store the device fingerprint with his profile.
Request
curl -s --request POST --header "Content-Type: application/json" --data @callback-step5.json https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm
callback-step5.json :
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceSave2",
"callbacks": [
{
"type": "ChoiceCallback",
"input": [
{
"name": "IDToken1",
"value": "0"
}
]
}
]
}
Response
Then the user is prompted to provide a name under which the new profile shall be stored. {
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceIdSave3",
"header": "Trusted Device Name",
"callbacks": [
{
"type": "NameCallback",
"output": [
{
"name": "prompt",
"value": "Trusted Device Name?"
}
],
"input": [
{
"name": "IDToken1",
"value": ""
}
]
}
]
}
Step 6: Provide callbacks for DeviceId (Save) module - Device profile nameThen If an empty Then If an empty v is provided as the device name, the authentication module will compute one which looks like "Profile: 21/08/2015 15:51".
Request
curl -s --request POST --header "Content-Type: application/json" --data @callback-step6.json https://sso.yellowstone.com:443/sso/json/authenticate?realm=/deviceidrealm
callback-step6.json :
{
"authId": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAib3RrIjogImdvbGhwOHFrNDVtbTI5cm91YWpyOHI2b2dkIiwgInJlYWxtIjogIm89ZGV2aWNlaWRyZWFsbSxvdT1zZXJ2aWNlcyxkYz1zc28tY29uZmlnLGRjPWNvbSIsICJzZXNzaW9uSWQiOiAiQVFJQzV3TTJMWTRTZmN5dDJ5VTFoc3J1OHZNZkhzVW1hMjd1QjA0S3YtQk5iZEUuKkFBSlRTUUFDTURJQUFsTkxBQk10TlRrek9UQXlOek0yTURJME1EZ3lNakkyQUFKVE1RQUNNREUuKiIgfQ.ZQGuCe3qDUEchsxcDCLdRU73g1i-isUAZDdp5ZOvVdM",
"template": "",
"stage": "DeviceIdSave3",
"callbacks": [
{
"type": "NameCallback",
"input": [
{
"name": "IDToken1",
"value": ""
}
]
}
]
}
Response
Now the response contains an SSOToken. This means the authentication was successful.
{
"tokenId": "AQIC5wM2LY4SfczOURj5zOskLzpSNfuSU9GYnW5XTu8Tudg.*AAJTSQACMDIAAlNLABM2OTIyMTc1MTIwOTUxMTA2NTcxAAJTMQACMDE.*",
"successUrl": "\/sso\/console"
}
"And the LORD gave them rest on every side". Joshua 21:44 [4]
References
[1] Pautasso, Cesare; Wilde, Erik; Alarcon, Rosa (2014), REST: Advanced Research Topics and Practical Applications
[2] Wikipedia contributors. "Representational state transfer." Wikipedia, The Free Encyclopedia. Wikipedia, The Free Encyclopedia, 1 Sep. 2015. Web. 1 Sep. 2015.
[2] Wikipedia contributors. "Representational state transfer." Wikipedia, The Free Encyclopedia. Wikipedia, The Free Encyclopedia, 1 Sep. 2015. Web. 1 Sep. 2015.
[3] Craig, Mark; Goldsmith, David; Hirayama, Gene: Lee, Chris, et al. OpenAM Developer's Guide Version 13.0.0-SNAPSHOT. ForgeRock, AS., 2015. <http://openam.forgerock.org/openam-documentation/openam-doc-source/doc/webhelp/dev-guide/rest-api-auth-json.html>
[4] The Holy Bible, New International Version, NIV Copyright 1973, 1978, 1984, 2011 by Biblica
[4] The Holy Bible, New International Version, NIV Copyright 1973, 1978, 1984, 2011 by Biblica