On-demand silent password migration¶
This adaptive authentication script is specifically designed for on-demand silent password migration. A migrated user's password can be seamlessly migrated to Asgardeo using this method without forcing the user to reset the password.
Note
Learn how to set up on-demand silent password migration.
Conditional template¶
Shown below is the conditional authentication template for on-demand silent password migration.
Note
Learn more about the conditional authentication functions and objects in its API reference.
var user = null;
var userId = "";
var username = "";
var password = "";
var choreoContextId = "";
var onLoginRequest = function(context) {
executeStep(1, {
onSuccess: function(context) {
Log.info("Login successful. Authenticated the user locally!");
},
onFail: function(context) {
resolveAndInitUser(context);
// If the user is found, proceed with the migration.
if (user !== null) {
userId = user.uniqueId;
// Check whether the user is already migrated.
if (user.localClaims["http://wso2.org/claims/is_migrated"] === "true") {
Log.info("Password is already migrated for the user: " + userId + ".");
sendError(null, {
'status': 'Login failed!',
'statusMsg': 'Please check your username and password and try again.',
'i18nkey': 'auth.fail.error'
});
} else {
Log.info("Password is not yet migrated for the user: " + userId + ". Starting the external authentication.");
// Start the external authentication.
authenticateExternally();
}
} else {
sendError(null, {
'status': 'Login failed!',
'statusMsg': 'Please check your username and password and try again.',
'i18nkey': 'auth.fail.error'
});
}
}
});
};
/**
* This function will resolve the user using the login identifier and initialize the login variables.
*/
var resolveAndInitUser = function(context) {
// Retrieve login identifier and password provided by the user.
var loginIdentifier = context.request.params.username[0];
password = context.request.params.password[0];
Log.info("User login initiated for the user: " + loginIdentifier);
// If your organization has enabled alternative login identifiers, the username has to be resolved using the login identifier.
// If not, uncomment the immediate next line and comment out rest of the section.
// username = loginIdentifier;
Log.info("Resolving username using the login identifier: " + loginIdentifier);
username = resolveMultiAttributeLoginIdentifier(loginIdentifier, context.tenantDomain);
Log.info("Username resolved using the login identifier. Resolved username: " + username);
// End of username resolving section.
// Retrieve unique user object for the username.
var claimMap = {};
claimMap["http://wso2.org/claims/username"] = "DEFAULT/" + username;
user = getUniqueUserWithClaimValues(claimMap, context);
};
/**
* This function will authenticate the user with the external service.
*/
var authenticateExternally = function() {
// Prepare the connection data.
var connectionMetadata = {
"url": "<start_authentication_endpoint>",
"consumerKey": "<consumer_key>",
"consumerSecret": "<consumer_secret>",
"asgardeoTokenEndpoint": "<asgardeo_token_endpoint>"
};
var requestPayload = {
id: userId,
username: username,
password: password
};
Log.info("Invoking the Choreo API to start authentication for the user: " + userId + ".");
// Invoke the start authentication service hosted in Choreo.
callChoreo(connectionMetadata, requestPayload, {
onSuccess: function(context, data) {
if (data !== null && data.message !== null) {
if (data.message === "Received") {
// Set the context ID to be used in subsequent requests.
choreoContextId = data.contextId;
Log.info("Started external authentication for the user: " + userId + " with context ID: " + choreoContextId + ". Redirecting to the waiting page.");
// Redirect to the waiting page to wait until the external authentication is completed.
prompt("internalWait", {
"waitingType": "POLLING",
"waitingConfigs": {
"timeout": "10",
"pollingEndpoint": "<polling_endpoint>",
"requestMethod": "GET",
"requestData": "contextId=" + choreoContextId,
"pollingInterval": "2"
}
}, {
onSuccess: function(context) {
Log.info("Successfully redirected back from the waiting page.");
// Check authentication status, update password and re-authenticate the user.
updatePasswordAndReAuthenticate();
},
onFail: function(context, data) {
Log.info("Error occurred while redirecting. Please retry!");
}
});
} else {
Log.info("External authentication failed for the user: " + userId + ". Message: " + data.message + ".");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
} else {
Log.info("External authentication failed for the user: " + userId + ".");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
},
onFail: function(context, data) {
Log.info("Error occurred while invoking the Choreo API to start authentication.");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
},
onTimeout: function(context, data) {
Log.info("Connection timed out while invoking the Choreo API to start authentication.");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
});
};
/**
* This function will check for the authentication status, update the password and re-authenticate.
*/
var updatePasswordAndReAuthenticate = function() {
// Prepare the connection data.
var connectionMetadata = {
"url": "<authentication_status_endpoint>",
"consumerKey": "<consumer_key>",
"consumerSecret": "<consumer_secret>",
"asgardeoTokenEndpoint": "<asgardeo_token_endpoint>"
};
var requestPayload = {
contextId: choreoContextId,
username: username
};
Log.info("Invoking the Choreo API to check auth status for the user: " + userId + " with context ID: " + choreoContextId + ".");
// Invoke the external authentication API hosted in Choreo.
callChoreo(connectionMetadata, requestPayload, {
onSuccess: function(context, data) {
if (data.status !== null && data.status === "SUCCESS") {
Log.info("External authentication is successful for the user: " + userId + ". Proceeding with password update.");
// Update the user password.
updateUserPassword(user, password, {
onSuccess: function(context) {
Log.info("Password updated successfully for the user: " + userId + ".");
// Set the password migration flag to true.
user.localClaims["http://wso2.org/claims/is_migrated"] = "true";
reAuthenticate();
},
onFail: function(context) {
Log.info("Failed to update password of the user: " + userId + ".");
}
});
} else if (data.status !== null && data.status === "FAIL") {
var errorMessage = "";
if (data.message !== null) {
errorMessage = data.message;
}
Log.info("External authentication failed for the user: " + userId + ". Message: " + errorMessage + ".");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'External authentication failed with the error: ' + errorMessage + '. Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
} else {
var errorMessage = "";
if (data.message !== null) {
errorMessage = data.message;
}
Log.info("Something went wrong during the external authentication for the user: " + userId + ". Message: " + errorMessage + ".");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
},
onFail: function(context, data) {
Log.info("Error occurred while invoking the Choreo API to check auth status.");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
},
onTimeout: function(context, data) {
Log.info("Connection timed out while invoking the Choreo API to check auth status.");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
});
};
/**
* This function will re-authenticate the user with the new password.
*/
var reAuthenticate = function() {
// Re-authenticate without prompting user input.
Log.info("Re-authenticating the user: " + userId + " with the new password.");
executeStep(1, {
authenticatorParams: {
common: {
'username': username,
'password': password
}
},
}, {
onSuccess: function(context) {
Log.info("Re-authentication successful for the user: " + userId + ".");
},
onFail: function() {
Log.info("Re-authentication failed for the user: " + userId + ".");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
});
};
Replace the following parameters of the script with values relevant to your setup:
start_authentication_endpoint | URL of the start authentication endpoint deployed in Choreo |
---|---|
polling_endpoint | URL of the polling endpoint deployed in Choreo |
authentication_status_endpoint | URL of the authentication status endpoint deployed in Choreo |
consumer_key | The consumer key of your Choreo application |
consumer_secret | The consumer secret of your Choreo application |
asgardeo_token_endpoint | Token endpoint of your Asgardeo organization. For example: https://api.asgardeo.io/t/{organization_name}/oauth2/token
|
How it works¶
Let's look at how the above conditional authentication scripts works.
-
The first authentication step (Username & Password) is initiated with the
executeStep(1, ..)
function. Based on its status, one of the following happens.-
If the entered credentials match, the user's password is already migrated. Hence, the
onSuccess
function will be called and the user will be authenticated. -
If the user's credentials don't match, either the credentials are incorrect or the password may not have be migrated yet. Hence, the
onFail
callback function is called and the script continues.
-
-
If the
onFail
function is called, the script will next try to locate a unique user in the system using theresolveAndInitUser
function.Adjust script for alternate login identifiers
If your organization does not use alternate login identifiers, comment the following lines in the
resolveAndInitUser
function.Log.info("Resolving username using the login identifier: " + loginIdentifier); username = resolveMultiAttributeLoginIdentifier(loginIdentifier, context.tenantDomain); Log.info("Username resolved using the login identifier. Resolved username: " + username);
-
If a unique user is found, the script checks the value of the
is_migrated
attribute of the user and does one of the following. (Theis_migrated
user attribute holds the status of the password migration.)-
If this is set to
true
, user's password is already migrated. Hence, the entered credentials are incorrect and the flow fails with an error. -
If it is not set to
true
, the user's password is not yet migrated. Hence, the script calls for external authentication.
-
-
The script calls for external authentication with the
authenticateExternally
function and it works as follows:- The
connectionMetadata
object holds the start authentication endpoint URL, Choreo application credentials and the Asgardeo token endpoint. - The script first calls the
callChoreo()
function along with theconnectionMetaData
and invokes the start authentication endpoint. - If the API call is successful, the
onSuccess()
callback function is called which in turn calls theprompt()
function. - The
prompt()
function continuously polls the Choreo polling endpoint and redirects the user to a waiting page until the external authentication completes. - Once the authentication is complete, the
onSuccess()
callback function of theprompt()
function calls theupdatePasswordAndReAuthenticate()
function.
- The
-
The
updatePasswordAndReAuthenticate()
function is responsible for checking the status of the authentication and taking necessary actions as explained below.- The
connectionMetadata
object hold the authentication status endpoint URL, Choreo application credentials and the Asgardeo token endpoint - The script first calls the
callChoreo()
function along with theconnectionMetaData
and invokes the authentication status endpoint. - If the API call is successful, the
onSuccess()
callback function is called and the response message is checked. If it isSUCCESS
, the external authentication was successful. - The script then calls the
updateUserPassword()
function to update the user password in the Asgardeo user store. - Afterwards, the
is_migrated
attribute of the user is set totrue
and the user is re-authenticated. - The
reAuthenticate()
function that handles the re-authentication performs a silent authentication. This means that the user is not prompted to enter the credentials again.
- The