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 loginIdentifier = "";
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.
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",
"callbackOnFailure": true
}
}, {
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!");
sendError(null, {
'status': 'Authentication failed',
'statusMsg': 'Please contact your administrator.',
'i18nkey': 'auth.fail.error'
});
}
});
} 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': loginIdentifier,
'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 script 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
onSuccessfunction 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
onFailcallback function is called and the script continues.
-
-
If the
onFailfunction is called, the script will next try to locate a unique user in the system using theresolveAndInitUserfunction.Adjust script for alternate login identifiers
If your organization does not use alternate login identifiers, comment the following lines in the
resolveAndInitUserfunction. -
If a unique user is found, the script checks the value of the
is_migratedattribute of the user and does one of the following. (Theis_migrateduser 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
authenticateExternallyfunction and it works as follows:- The
connectionMetadataobject holds the start authentication endpoint URL, Choreo application credentials and the Asgardeo token endpoint. -
The script first calls the
callChoreo()function along with theconnectionMetaDataand 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
connectionMetadataobject hold the authentication status endpoint URL, Choreo application credentials and the Asgardeo token endpoint. -
The script first calls the
callChoreo()function along with theconnectionMetaDataand 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_migratedattribute of the user is set totrueand 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