I have a form that collects text from 2 text inputs. From there i need to collect the inputs and then run 2 http calls, one for a token the other for the response needed.
When i get a response i either need to continue or display the error message back into the form so the user can try again.
Is it possible to listen to the click events in a form? The flow http fucntion is very basic and doesnt allow for the repsonse to be used with actions and logic.
Below is an example of what I need.
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
api.prompt.render(event.secrets.FORM_ID);
const input1 = event.prompt.fields.input1
const input2 = event.prompt.fields.input1
const button = event.prompt.id = 'button1'
button.onclick () => {
// GET A TOKEN
const getToken = await fetch(url)
const token = getToken.response()
// GET THE DATA
const getData = await fetch(url, token)
const data = getData.response()
if(data.err){
api.prompt.render(':form_id', {
vars: {
errmsg: '123',
}
});
} else {
return;
}
}
};
Do you have a litle more info or code oh how to do the following? From my understanding the actions are linear and cant be re-run? Whats the event or api to do that?
Is it something like this?
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
api.prompt.render(event.secrets.FORM_ID);
const input1 = event.prompt.fields.input1
const input2 = event.prompt.fields.input1
const getToken = await fetch(url)
const token = getToken.response()
// GET THE DATA
const getData = await fetch(url)
const data = getToken.response()
if(data.err === '123'){
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: '123',
}
});
} else if(data.err === '456') {
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: '456',
}
});
} else{
return;
}
};
Or here where I run the logic on the onContinue? How does state for the var work if I rerun the action from the top?
/**
* Handler that will be called during the execution of a PostLogin flow.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onExecutePostLogin = async (event, api) => {
api.prompt.render(event.secrets.FORM_ID);
};
/**
* Handler that will be invoked when this action is resuming after an external redirect. If your
* onExecutePostLogin function does not perform a redirect, this function can be safely ignored.
*
* @param {Event} event - Details about the user and the context in which they are logging in.
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
*/
exports.onContinuePostLogin = async (event, api) => {
const input1 = event.prompt.fields.input1
const input2 = event.prompt.fields.input2
const getToken = await fetch(url)
const token = getToken.response()
// GET THE DATA
const getData = await fetch(url)
const data = getToken.response()
if(data.err === '123'){
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: '123',
}
});
// HOW TO RERUN THE ACTION AND KEEP STATE FOR ERR MSG??
} else if(data.err === '456') {
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: '456',
}
});
// HOW TO RERUN THE ACTION AND KEEP STATE FOR ERR MSG??
} else{
return;
}
};
You do not use onContinuePostLogin for this. That function is only for handling returns from an external redirect (like api.redirect). The api.prompt.render call STOPS your Action and waits. When the user submits the form, Auth0 re-runs your ENTIRE onExecutePostLogin function from the very beginning.
The only way you know it’s the “second run” is that event.prompt.fields will exist.
The flow should look like this:
First Run (User logs in):
onExecutePostLogin starts.
Your code checks: “Does event.prompt.fields have any data?”
No, it’s the first run.
Your code goes to the else block.
It calls api.prompt.render(FORM_ID).
The Action STOPS. The user sees your form.
Second Run (User submits form):
onExecutePostLogin starts AGAIN FROM THE TOP.
Your code checks: “Does event.prompt.fields have any data?”
Yes! The user just submitted input1 and input2.
Your code enters the if block.
You run your fetch calls.
Scenario A (Success): Your fetch is good. You return normally. The user is logged in.
Scenario B (Error): Your fetch returns an error.
You call api.prompt.render(FORM_ID, { vars: { errmsg: '...' } }).
The Action STOPS. The user sees your form again, but this time with the error message.
Third Run (User submits form again):
onExecutePostLogin starts AGAIN FROM THE TOP.
The whole process repeats from Step 2.
As for an example, you can try this:
exports.onExecutePostLogin = async (event, api) => {
// Check if form data exists. If it does, this is NOT the first run.
if (event.prompt && event.prompt.fields) {
// STAGE 2: Process the submitted data
// NOTE: Your example code was missing "event.prompt.fields" for input2
const { input1, input2 } = event.prompt.fields;
try {
// GET A TOKEN
// Make sure to use 'await', secrets, and check the response!
const tokenResponse = await fetch(event.secrets.TOKEN_URL, { /* ...options... */ });
if (!tokenResponse.ok) {
throw new Error('Token request failed');
}
const tokenData = await tokenResponse.json();
const token = tokenData.access_token; // Or whatever your response is
// GET THE DATA
const dataResponse = await fetch(event.secrets.DATA_URL, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!dataResponse.ok) {
throw new Error('Data request failed');
}
const data = await dataResponse.json();
// Check for your application-specific error
if (data.err) {
// Validation Failed: Re-render the form WITH the error
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: data.err.message || 'An error occurred. Please try again.'
}
});
// IMPORTANT: Stop execution so the user doesn't log in
return;
}
// SUCCESS: All checks passed. Do nothing and let the function end.
// The user's login will now complete.
} catch (error) {
// Network Error: Handle fetch failures or other exceptions
// Re-render the form with a generic error
api.prompt.render(event.secrets.FORM_ID, {
vars: {
errmsg: 'A system error occurred. Please try again.'
}
});
return; // Stop execution
}
} else {
// STAGE 1: This is the first run. Just show the form.
api.prompt.render(event.secrets.FORM_ID);
}
};
That was super helpful! Though i made some changes to get it to work.
I do have 1 final question below.
/**
* Initial trigger - render the form
*/
exports.onExecutePostLogin = async (event, api) => {
const formId = event.secrets.FORM_ID;
// Skip if already validated
if (event.user.app_metadata?.verified) {
return;
}
// Show the form
api.prompt.render(formId);
};
/**
* Form submission handler - validate and continue or show error
*/
exports.onContinuePostLogin = async (event, api) => {
const config = {
apiBaseUrl: event.secrets.API_BASE_URL,
username: event.secrets.API_USERNAME,
password: event.secrets.API_PASSWORD,
formId: event.secrets.FORM_ID
};
// Get form data
const userInput = event.prompt?.fields?.user_input;
if (!userInput) {
api.prompt.render(config.formId, {
vars: { errmsg: 'Input is required.' }
});
return;
}
try {
// Step 1: Get API token
const authResponse = await fetch(`${config.apiBaseUrl}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: config.username,
password: config.password
})
});
if (!authResponse.ok) {
throw new Error('Authentication failed');
}
const { token } = await authResponse.json();
// Step 2: Validate user input with API
const validationResponse = await fetch(`${config.apiBaseUrl}/validate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
email: event.user.email,
input: userInput
})
});
const result = await validationResponse.json();
// Step 3: Success - mark as verified and continue
if (validationResponse.ok && result.valid) {
api.user.setAppMetadata('verified', true);
return; // Continue with login
}
// Step 4: Failed - re-render form with error
api.prompt.render(config.formId, {
vars: { errmsg: result.error || 'Validation failed. Please try again.' }
});
} catch (error) {
// Error handling - re-render form
api.prompt.render(config.formId, {
vars: { errmsg: 'Unable to validate. Please try again.' }
});
}
};
Obviously when the user clicks continue, the action repeats until the logic is satisfied.
But, Id like to be able to offer a second button to skip all the logic and then contine through the rest of the actions.
In the action, can we do something like this and just return?
// Check if user clicked "Skip" button
if (event.prompt.action === 'skip') {
console.log(`PIVOTAL_VALIDATION_SKIP: User ${event.user.email} clicked Skip button`);
// Allow login without validation
return;
}