Problem statement
When making api.samlResponse.setAttribute call within a callback function, the set attribute is missing in the SAML response. This article explains why and how to pass the attribute correctly.
Here is a simple example that reproduces the issue:
exports.onExecutePostLogin = async (event, api) => {
api.samlResponse.setAttribute("test1", "value1");
setTimeout(function(){
console.log("===== async delayed message =====");
api.samlResponse.setAttribute("test2", "value2");
}, 1000);
};
Here is the SAML response, stripping the unrelated sections, “test2” claim is missing in the SAML response.
<samlp:Response ...>
....
<saml:Assertion ....">
....
</saml:AuthnStatement>
<saml:AttributeStatement ...>
<saml:Attribute Name="test1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">
value1
</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
Solution
This attribute is not passed because the Action completes and moves the control over to the Auth0 authentication server before the callback function executes and sets the SAML attribute. To avoid this issue, a synchronous execution of the API call is required. This can be implemented using async/await.
Here is a modified sample, which will help to have both attributes in the SAML response.
const util = require('util');
exports.onExecutePostLogin = async (event, api) => {
const sleep = util.promisify(setTimeout)
async function addNewAttributeSync() {
console.log("===== synced delayed message =====");
await sleep(1000);
api.samlResponse.setAttribute("test2", "value2");
}
api.samlResponse.setAttribute("test1", "value1");
await addNewAttributeSync();
};
This issue can happen when an Action makes an API call, and the callback handler executes api.samlResponse.setAttribute to set a claim. This function will then need to be converted to a Promise (E.g., using a utility function like util.promisify ) so that the external API call is synchronized.