The following endpoints apply to this flow:
Prerequisites
- Server-side SDK installed for your platform or use the API endpoints directly if using a different back end language.
- Authentication credentials from the Prove Portal configured in your environment.
- Client-side SDK installed for your platform.
How to implement
To integrate Prove authentication, you must use the client-side SDK.
- Prove Possession
- Customer-Supplied Possession with Force Bind
- Prove Passive Authentication with Customer-Supplied Possession Fallback
Determine Type of Flow
- Web SDK
- Android SDK
- iOS SDK
Decide if the customer is on a mobile or desktop browser using this example. If the
isMobile is true, set mobile as the flowType for the Start() function on the server, otherwise you can set desktop:// Check if the customer is on a mobile or desktop browser.
const authCheck = new proveAuth.AuthenticatorBuilder().build();
let isMobile = authCheck.isMobileWeb()
When using the Android SDK, set
mobile as the flowType for the Start() function on the server.When using the iOS SDK, set
mobile as the flowType for the Start() function on the server.Initialize the Flow
Mobile Auth in the United States doesn’t require a phone number, but all other countries do.
finalTargetURL: required whenflowType=desktop. This should be a URL you support. Once the customer clicks the Instant Link, redirect to this URL. It should instruct the customer to continue the workflow. Maximum length is 128 characters.checkReputation: if true, Prove performs TrustScore verification.clientHumanId: a client-generated unique ID to identify a specific customer across business lines.clientRequestId: a client-generated unique ID for a specific session.smsMessage: optional field to customize the message body sent in the Instant Link or OTP SMS message. Otherwise, you can use Prove defaults.clientCustomerId: a client-generated unique ID for a specific customer. You can link calls related to the same customer, across different requests or sessions. The client defines the format of this ID.deviceId: the unique identifier for the Prove Key on the device.emailAddress: the email address of the customer.ipAddress: the IP address of the customer.proveId: a unique ID to identify a specific customer obtained from a earlier successful authentication.allowOTPRetry: set totrueto allow the customer to re-enter the OTP up to three times. Defaults tofalse.
// Send the Unify request.
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
PhoneNumber: "2001004014",
PossessionType: "mobile",
ClientRequestID: provesdkservergo.String("client-abc-123"),
AllowOTPRetry: true,
})
if err != nil {
t.Fatal(err)
}
authToken: send this to your client-side code to pass into theAuthenticate()function - it’s a short lived JSON Web Token (JWT) tied to the current flow and used for the possession checks.correlationId: save this in your current session, then pass it in to theUnifyStatus()function call of the same flow. The correlation ID ties together different system calls for the same Prove flow. It also aids in troubleshooting. The session expires in 15 minutes from when the correlation ID returns from theUnify()call.success: returnspendingfor this initial call.
authToken in a response to the front end.Authenticate
Once you have the To set the One-Time Password (OTP) handler, In the desktop flow, a WebSocket opens for 3 minutes on the desktop browser while waiting for the customer to select the link in the text message. Once clicked, the WebSocket closes and the The mobile data connection can sometimes be unavailable during testing. The The To set the One-Time Password (OTP) handler, To integrate Instant Link, review the technical prerequisites, followed by the required function calls, and finally the detailed configuration steps for different use cases.Follow these instructions to allow the customer to request a new SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:You can then send a new Instant Link SMS to the same phone number by implementing the Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Implement the start step:You can prompt for a new phone number by implementing the The redirect URL returned by the Prove server has your original If a mobile data connection is unavailable during testing, use the Builder class. It permits simulated successful session results while connected to a Wi-Fi network. Testing using a Wi-Fi connection is useful in the Sandbox environment.The Prove Auth object is thread safe and used as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the SDK’s ability to process concurrent blocking requests.To set the One-Time Password (OTP) handler, To integrate Instant Link, review the technical prerequisites, followed by the required function calls, and finally the detailed configuration steps for different use cases.Follow these instructions to allow the customer to request a new SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:You can send a new Instant Link SMS to the same phone number by implementing the Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Implement the start step:You can prompt for a new phone number by implementing the Invoke
authToken, build the authenticator for both the mobile and desktop flows.For Android and iOS flows, you either must implement
OTP or Instant Link - not both.- Web SDK
- Android SDK
- iOS SDK
For desktop mode, Prove ignores the Prove Key and runs Instant Link.
async function authenticate(isMobileWeb, authToken) {
// Set up the authenticator for either mobile or desktop flow.
let builder = new proveAuth.AuthenticatorBuilder();
if (isMobileWeb) {
// Set up Mobile Auth and OTP.
builder = builder
.withAuthFinishStep((input) => verify(input.authId))
.withMobileAuthImplementation("fetch")
.withOtpFallback(otpStart, otpFinish);
} else {
// Set up Instant Link.
builder = builder
.withAuthFinishStep((input) => verify(input.authId))
.withInstantLinkFallback(instantLink)
.withRole("secondary");
}
const authenticator = builder.build();
// Authenticate with the authToken.
return authenticator.authenticate(authToken);
}
Configure OTP
To use the Resend/Retry/Phone Change features, install the Web SDK version 2.15.1 or later.
withOtpFallback(startStep: OtpStartStep | OtpStartStepFn, finishStep: OtpFinishStep | OtpFinishStepFn), requires implementing the OtpStartStep and OtpFinishStep. When returning the phone number in the functions, ensure you return an object with the field phoneNumber to the resolve() function.The OTP session has a two minute timeout from when it’s sent through Short Message Service (SMS) to when the customer can enter in the OTP.- Default
- Prompt for Phone Number
- Resend
- Retry OTP
- Phone Number Change
Follow these instructions if you are implementing OTP and you are passing in the phone number on the Call the
/v3/start endpoint.Since you passed the phone number in the Start() function, call resolve(null) to communicate to the SDK you have the customer’s agreement to deliver the SMS message. Ensure you return an object to resolve() function.function otpStartStep(phoneNumberNeeded, phoneValidationError) {
return new Promise((resolve, reject) => {
// Since no phone number is needed, don't prompt the user.
resolve(null);
});
}
reject('some error message') method to communicate to the SDK any issues while trying to obtain the phone number or the OTP. Report an error if the customer cancels the SMS transaction or presses the back button to leave the screen.In the finish step, call the resolve(result: OtpFinishResult) method to return the collected OTP value in which result variable has OnSuccess value for OtpFinishResultType and the OTP value wrapped in OtpFinishInput.function otpFinishStep(otpError) {
return new Promise((resolve, reject) => {
// If error message is found, handle it.
if (otpError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = otpError.message;
}
// Prompt the user for whether they received the SMS.
// Typically, this is a page that shows the OTP already. We are simplifying
// it by requiring an input.
var input = confirm('Did you receive a text message?');
if (!input) {
// Close the modal if a text message was not received.
return;
}
// Prompt the user for the OTP.
var otp = prompt('Enter OTP code:');
if (otp) {
// If the input is valid and the user clicked `OK`, return the OTP.
resolve({
input: { otp }, // OTP value
resultType: 0, // OnSuccess enum type = 0
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
Follow these instructions if implementing MobileAuth and collecting the phone number only if MobileAuth fails. This implements OTP without allowing for resending an SMS and phone number changes. If you do want those capabilities, please reference the Resend, Retry OTP, and Phone Number Change tabs.In the start step, call the Implement the finish step:
resolve(input: OtpStartInput) method to return the collected phone number to the SDK.function otpStartStep(phoneNumberNeeded, phoneValidationError) {
return new Promise((resolve, reject) => {
// If no phone number is needed, then don't prompt the user.
if (!phoneNumberNeeded) {
resolve(null);
return;
}
// If error message is found around phone number, handle it.
// The `phoneValidationError` is ONLY available when `phoneNumberNeeded`
// has a value.
if (phoneValidationError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = phoneValidationError.message;
}
// Prompt the user for the phone number.
var input = prompt('Enter phone number:');
if (input) {
// If the input is valid and the user clicked `OK`, return the phone
// number.
resolve({
phoneNumber: input,
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
function otpFinishStep(otpError) {
return new Promise((resolve, reject) => {
// If error message is found, handle it.
if (otpError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = otpError.message;
}
// Prompt the user for whether they received the SMS.
// Typically, this is a page that shows the OTP already. We are simplifying
// it by requiring an input.
var input = confirm('Did you receive a text message?');
if (!input) {
// Close the modal if a text message was not received.
return;
}
// Prompt the user for the OTP.
var otp = prompt('Enter OTP code:');
if (otp) {
// If the input is valid and the user clicked `OK`, return the OTP.
resolve({
input: { otp }, // OTP value
resultType: 0, // OnSuccess enum type = 0
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
Follow these instructions to allow the customer to request a new OTP via SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:You can then send a new OTP SMS to the same phone number by implementing the finish step like this:
function otpStartStep(phoneNumberNeeded, phoneValidationError) {
return new Promise((resolve, reject) => {
// If no phone number is needed, then don't prompt the user.
if (!phoneNumberNeeded) {
resolve(null);
return;
}
// If error message is found around phone number, handle it.
// The `phoneValidationError` is ONLY available when `phoneNumberNeeded`
// has a value.
if (phoneValidationError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = phoneValidationError.message;
}
// Prompt the user for the phone number.
var input = prompt('Enter phone number:');
if (input) {
// If the input is valid and the user clicked `OK`, return the phone
// number.
resolve({
phoneNumber: input,
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
function otpFinishStep(otpError) {
return new Promise((resolve, reject) => {
// If error message is found, handle it.
if (otpError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = otpError.message;
}
// Prompt the user for whether they received the SMS.
// Typically, this is a page that shows the OTP already. We are simplifying
// it by requiring an input.
var input = confirm('Did you receive a text message?');
if (!input) {
// If `Cancel`, then resend to the same phone number.
resolve({
resultType: 1, // OnResendOtp enum type = 1
});
return;
}
// Prompt the user for the OTP.
var otp = prompt('Enter OTP code:');
if (otp) {
// If the input is valid and the user clicked `OK`, return the OTP.
resolve({
input: { otp }, // OTP value
resultType: 0, // OnSuccess enum type = 0
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
Follow these instructions to allow the customer to re-enter the OTP PIN if they type it wrong. There is a max of 3 attempts. To implement this capability, pass in Implement the finish step - no client side code changes necessary. If the OTP is invalid, the finish step prompts the user for a new input. Once you reach the max attempts, the
allowOTPRetry=true to the /v3/start endpoint.Implement the start step - no client side code changes necessary:function otpStartStep(phoneNumberNeeded, phoneValidationError) {
return new Promise((resolve, reject) => {
// If no phone number is needed, then don't prompt the user.
if (!phoneNumberNeeded) {
resolve(null);
return;
}
// If error message is found around phone number, handle it.
// The `phoneValidationError` is ONLY available when `phoneNumberNeeded`
// has a value.
if (phoneValidationError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = phoneValidationError.message;
}
// Prompt the user for the phone number.
var input = prompt('Enter phone number:');
if (input) {
// If the input is valid and the user clicked `OK`, return the phone
// number.
resolve({
phoneNumber: input,
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
AuthFinish function runs.function otpFinishStep(otpError) {
return new Promise((resolve, reject) => {
// If error message is found, handle it.
if (otpError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = otpError.message;
}
// Prompt the user for whether they received the SMS.
// Typically, this is a page that shows the OTP already. We are simplifying
// it by requiring an input.
var input = confirm('Did you receive a text message?');
if (!input) {
// Close the modal if a text message was not received.
return;
}
// Prompt the user for the OTP.
var otp = prompt('Enter OTP code:');
if (otp) {
// If the input is valid and the user clicked `OK`, return the OTP.
resolve({
input: { otp }, // OTP value
resultType: 0, // OnSuccess enum type = 0
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Implement the start step - no client side code changes necessary:You can prompt for a new phone number by implementing the finish step like this:
Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
function otpStartStep(phoneNumberNeeded, phoneValidationError) {
return new Promise((resolve, reject) => {
// If no phone number is needed, then don't prompt the user.
if (!phoneNumberNeeded) {
resolve(null);
return;
}
// If error message is found around phone number, handle it.
// The `phoneValidationError` is ONLY available when `phoneNumberNeeded`
// has a value.
if (phoneValidationError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = phoneValidationError.message;
}
// Prompt the user for the phone number.
var input = prompt('Enter phone number:');
if (input) {
// If the input is valid and the user clicked `OK`, return the phone
// number.
resolve({
phoneNumber: input,
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
function otpFinishStep(otpError) {
return new Promise((resolve, reject) => {
// If error message is found, handle it.
if (otpError) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
var someErrorMessage = otpError.message;
}
// Prompt the user for whether they received the SMS.
// Typically, this is a page that shows the OTP already. We are simplifying
// it by requiring an input.
var input = confirm('Did you receive a text message?');
if (!input) {
// If `Cancel`, then trigger the otpStartStep to re-prompt for
// phone number.
resolve({
resultType: 2, // OnMobileNumberChange enum type = 2
});
return;
}
// Prompt the user for the OTP.
var otp = prompt('Enter OTP code:');
if (otp) {
// If the input is valid and the user clicked `OK`, return the OTP.
resolve({
input: { otp }, // OTP value
resultType: 0, // OnSuccess enum type = 0
});
} else {
// Else, exit the flow.
reject('phone invalid or user cancelled');
}
});
}
AuthFinishStep function finishes.If you’re using Content Security Policy headers, ensure you allow
wss: device.uat.proveapis.com and wss: device.proveapis.com.Java
// Object implementing AuthFinishStep interface
AuthFinishStep authFinishStep = new AuthFinishStep() {
...
};
// Objects implementing OtpStartStep/OtpFinishStep interfaces
OtpStartStep otpStartStep = new OtpStartStep() {
...
};
OtpFinishStep otpFinishStep = new OtpFinishStep() {
...
};
ProveAuth proveAuth = ProveAuth.builder()
.withAuthFinishStep(authId -> verify(authId)) // verify(authId) call defined in #Validate the Mobile Phone section
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(this)
.build();
Builder class offers a withTestMode(boolean testMode) method, which permits simulated successful session results while connected to a Wi-Fi network only. Testing using a Wi-Fi connection is useful in the Sandbox environment.Java
ProveAuth proveAuth = ProveAuth.builder()
.withAuthFinishStep(authId -> verify(authId))
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(this)
.withTestMode(true) // Test mode flag
.build();
ProveAuth object is thread safe. You can use it as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the ability to process concurrent blocking requests.Java
public class MyAuthenticator {
private final MyBackendClient backend = new MyBackendClient(); // Backend API client
private final AuthFinishStep authFinishStep = new AuthFinishStep() {
@Override
void execute(String authId) {
try {
AuthFinishResponse response = backend.authFinish("My App", authId);
... // Check the authentication status returned in the response
} catch (IOException e) {
String failureCause = e.getCause() != null ? e.getCause().getMessage() : "Failed to request authentication results";
// Authentication failed due to request failure
}
}
};
private ProveAuth proveAuth;
public MyAuthenticator(Context context) {
proveAuth = ProveAuth.builder()
.withAuthFinishStep(authFinishStep)
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(context)
.build();
}
public void authenticate() throws IOException, ProveAuthException {
AuthStartResponse response = backend.authStart("My Prove Auth App");
proveAuth.authenticate(response.getAuthToken());
}
}
Configure OTP
To use the Resend/Retry/Phone Change features, install the Android SDK version 6.5.0 or later.
withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep), requires implementing the OtpStartStep and OtpFinishStep.The OTP session has a two minute timeout from when it’s sent through SMS to when the customer can enter in the OTP.- Default
- Prompt for Phone Number
- Resend
- Retry OTP
- Phone Number Change
Follow these instructions if you are implementing OTP and you are passing in the phone number on the Call the
/v3/start endpoint. In this case, you’ve already prompted for a phone number so don’t prompt for it in the client SDK.Since you passed the phone number in the Start() function, call OtpStartStepCallback.onSuccess(OtpStartInput); to communicate to the SDK you have the customer’s agreement to deliver the SMS message. Ensure you return an instance of OtpStartInput with empty string or null to OtpStartStepCallback.onSuccess() function.Java
import com.prove.sdk.proveauth.OtpStartInput;
import com.prove.sdk.proveauth.OtpStartStep;
import com.prove.sdk.proveauth.OtpStartStepCallback;
import com.prove.sdk.proveauth.ProveAuthException;
public class NoPromptStart implements OtpStartStep {
@Override
public void execute(boolean phoneNumberNeeded, ProveAuthException otpException,
OtpStartStepCallback callback) {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new OtpStartInput(""));
}
}
OtpStartStepCallback.onError(); method to communicate to the SDK any issues while trying to obtain the phone number or the OTP. Report an error if the customer cancels the SMS transaction or presses the back button to leave the screen.In the finish step, call the OtpFinishStepCallback.onSuccess(OtpFinishInput); method to return the collected OTP value wrapped in OtpFinishInput.Java
import com.prove.sdk.proveauth.OtpFinishInput;
import com.prove.sdk.proveauth.OtpFinishStep;
import com.prove.sdk.proveauth.OtpFinishStepCallback;
import com.prove.sdk.proveauth.OtpValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class NoPromptFinish implements OtpFinishStep {
@Override
public void execute(@Nullable ProveAuthException otpException,
OtpFinishStepCallback otpFinishStepCallback) {
// If error message is found, handle it.
if (otpException instanceof OtpValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for OTP delivered by SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForOtpCode which gives us the OTP code.
String otpCode = promptForOtpCode();
otpFinishStepCallback.onSuccess(new OtpFinishInput(otpCode));
} catch (Exception e) {
// if any issue with the OTP collection from the end user or the user wants to cancel
// then call onError to exit the flow. In this example we simplify it as catching
// an exception.
otpFinishStepCallback.onError();
}
}
}
Follow these instructions if implementing MobileAuth and collecting the phone number only if MobileAuth fails. This implements OTP without allowing SMS re-sends and phone number changes. If you want those capabilities, see the tabs Resend, Retry OTP, and Phone Number Change.In the start step, call the Implement the finish step:
OtpStartStepCallback.onSuccess(OtpStartInput); method to return the collected phone number to the SDK.Java
import com.prove.sdk.proveauth.OtpStartInput;
import com.prove.sdk.proveauth.OtpStartStep;
import com.prove.sdk.proveauth.OtpStartStepCallback;
import com.prove.sdk.proveauth.PhoneNumberValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class PromptStart implements OtpStartStep {
@Override
public void execute(boolean phoneNumberNeeded, @Nullable ProveAuthException otpException,
OtpStartStepCallback callback) {
// If phone number is needed, need to ask the end user for phone number input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (otpException instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for phone number to receive OTP SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForPhoneNumber which gives us the collected
// phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new OtpStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new OtpStartInput(""));
}
}
}
Java
import com.prove.sdk.proveauth.OtpFinishInput;
import com.prove.sdk.proveauth.OtpFinishStep;
import com.prove.sdk.proveauth.OtpFinishStepCallback;
import com.prove.sdk.proveauth.OtpValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class NoPromptFinish implements OtpFinishStep {
@Override
public void execute(@Nullable ProveAuthException otpException,
OtpFinishStepCallback otpFinishStepCallback) {
// If error message is found, handle it.
if (otpException instanceof OtpValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for OTP delivered by SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForOtpCode which gives us the OTP code.
String otpCode = promptForOtpCode();
otpFinishStepCallback.onSuccess(new OtpFinishInput(otpCode));
} catch (Exception e) {
// if any issue with the OTP collection from the end user or the user wants to cancel
// then call onError to exit the flow. In this example we simplify it as catching
// an exception.
otpFinishStepCallback.onError();
}
}
}
Follow these instructions to allow the customer to request a new OTP via SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:You can then send a new OTP SMS to the same phone number by implementing the finish step like this:
Java
import com.prove.sdk.proveauth.OtpStartInput;
import com.prove.sdk.proveauth.OtpStartStep;
import com.prove.sdk.proveauth.OtpStartStepCallback;
import com.prove.sdk.proveauth.PhoneNumberValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class PromptStart implements OtpStartStep {
@Override
public void execute(boolean phoneNumberNeeded, @Nullable ProveAuthException otpException,
OtpStartStepCallback callback) {
// If phone number is needed, need to ask the end user for phone number input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (otpException instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for phone number to receive OTP SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForPhoneNumber which gives us the collected
// phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new OtpStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new OtpStartInput(""));
}
}
}
Java
import com.prove.sdk.proveauth.OtpFinishInput;
import com.prove.sdk.proveauth.OtpFinishStep;
import com.prove.sdk.proveauth.OtpFinishStepCallback;
import com.prove.sdk.proveauth.OtpValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class MultipleResendFinish implements OtpFinishStep {
@Override
public void execute(@Nullable ProveAuthException otpException,
OtpFinishStepCallback otpFinishStepCallback) {
// If error message is found, handle it.
if (otpException instanceof OtpValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
// Prompt the user for whether they received the SMS.
if (promptForResend("Didn't receive the SMS OTP? Click resend button for a new one!")) {
// If the end user wants to send again to the same phone number call onOtpResend().
otpFinishStepCallback.onOtpResend();
return;
}
try {
// Prompt the user for OTP delivered by SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForOtpCode which gives us the OTP code.
String otpCode = promptForOtpCode();
otpFinishStepCallback.onSuccess(new OtpFinishInput(otpCode));
} catch (Exception e) {
// if any issue with the OTP collection from the end user or the user wants to cancel
// then call onError to exit the flow. In this example we simplify it as catching
// an exception.
otpFinishStepCallback.onError();
}
}
}
Follow these instructions to allow the customer to re-enter the OTP PIN if they type it wrong. There is a max of 3 attempts. To implement this capability, you also must pass in Implement the finish step - no client side code changes necessary. If the OTP is invalid, the finish step prompts the user for a new input. Once you reach the max attempts, the
allowOTPRetry=true to the /v3/start endpoint.Implement the start step - no client side code changes necessary:Java
import com.prove.sdk.proveauth.OtpStartInput;
import com.prove.sdk.proveauth.OtpStartStep;
import com.prove.sdk.proveauth.OtpStartStepCallback;
import com.prove.sdk.proveauth.PhoneNumberValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class PromptStart implements OtpStartStep {
@Override
public void execute(boolean phoneNumberNeeded, @Nullable ProveAuthException otpException,
OtpStartStepCallback callback) {
// If phone number is needed, need to ask the end user for phone number input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (otpException instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for phone number to receive OTP SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForPhoneNumber which gives us the collected
// phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new OtpStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new OtpStartInput(""));
}
}
}
AuthFinish function runs.Java
import com.prove.sdk.proveauth.OtpFinishInput;
import com.prove.sdk.proveauth.OtpFinishStep;
import com.prove.sdk.proveauth.OtpFinishStepCallback;
import com.prove.sdk.proveauth.OtpValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class NoPromptFinish implements OtpFinishStep {
@Override
public void execute(@Nullable ProveAuthException otpException,
OtpFinishStepCallback otpFinishStepCallback) {
// If error message is found, handle it.
if (otpException instanceof OtpValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for OTP delivered by SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForOtpCode which gives us the OTP code.
String otpCode = promptForOtpCode();
otpFinishStepCallback.onSuccess(new OtpFinishInput(otpCode));
} catch (Exception e) {
// if any issue with the OTP collection from the end user or the user wants to cancel
// then call onError to exit the flow. In this example we simplify it as catching
// an exception.
otpFinishStepCallback.onError();
}
}
}
Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Implement the start step:You can prompt for a new phone number by implementing the finish step like this:
Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Java
import com.prove.sdk.proveauth.OtpStartInput;
import com.prove.sdk.proveauth.OtpStartStep;
import com.prove.sdk.proveauth.OtpStartStepCallback;
import com.prove.sdk.proveauth.PhoneNumberValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class PromptStart implements OtpStartStep {
@Override
public void execute(boolean phoneNumberNeeded, @Nullable ProveAuthException otpException,
OtpStartStepCallback callback) {
// If phone number is needed, need to ask the end user for phone number input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (otpException instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
try {
// Prompt the user for phone number to receive OTP SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForPhoneNumber which gives us the collected
// phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new OtpStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new OtpStartInput(""));
}
}
}
Java
import com.prove.sdk.proveauth.OtpFinishInput;
import com.prove.sdk.proveauth.OtpFinishStep;
import com.prove.sdk.proveauth.OtpFinishStepCallback;
import com.prove.sdk.proveauth.OtpValidationException;
import com.prove.sdk.proveauth.ProveAuthException;
public class PhoneChangeFinish implements OtpFinishStep {
@Override
public void execute(@Nullable ProveAuthException otpException,
OtpFinishStepCallback otpFinishStepCallback) {
// If error message is found, handle it.
if (otpException instanceof OtpValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = otpException.getMessage();
}
// Prompt the user for whether they received the SMS.
if (promptForPhoneNumberChange("Didn't receive the SMS OTP? Try a different phone number.")) {
// If the end user wants to correct the phone number already in use, or changing to a
// different phone number to receive the future SMS OTP, call onMobileNumberChange(), and
// the otpStartStep will re-prompt for phone number input from the end user.
otpFinishStepCallback.onMobileNumberChange();
return;
}
try {
// Prompt the user for OTP delivered by SMS. You can build UI to provide
// best UX based on your application and business logic, here we simplify to a
// generic function named promptForOtpCode which gives us the OTP code.
String otpCode = promptForOtpCode();
otpFinishStepCallback.onSuccess(new OtpFinishInput(otpCode));
} catch (Exception e) {
// if any issue with the OTP collection from the end user or the user wants to cancel
// then call onError to exit the flow. In this example we simplify it as catching
// an exception.
otpFinishStepCallback.onError();
}
}
}
Configure Instant Link
Instant Link for Android is an add-on feature. To enable, contact your Prove representative.
Instant Link prerequisites
For the Instant Link flow to work in Android, the customer host app must receive a deep link. It must then redirect back to the original host app after the user taps the Instant Link in the SMS. It’s highly recommended to use App Links for Android apps. Full instructions: Supporting App Link in your app | Android developer documentationRequired functions
To set the Instant Link handler,withInstantLinkFallback(InstantLinkStartStep startStep, @Nullable InstantLinkRetryStep retryStep), requires implementing the InstantLinkStartStep protocol and optionally the InstantLinkRetryStep protocol if you wish for advanced capabilities. When returning the phone number, ensure you pass an InstantLinkStartInput with the mobileNumber field to the callback.The Instant Link session has a five minute timeout from when it’s sent through SMS to when the customer can click the received link.Configure Instant Link steps
- Default
- Prompt for Phone Number
- Resend
- Phone Number Change
Follow these instructions if you are implementing Instant Link and you are passing in the phone number in the initial server-side call. In this case, you’ve already prompted for a phone number so you don’t prompt for it in the client SDK.Since you passed the phone number, call
callback.onSuccess(InstantLinkStartInput input) to communicate to the SDK you have the customer’s agreement to deliver the SMS message.Java
InstantLinkStartStep noPromptStartStep = (phoneNumberNeeded, instantLinkError, callback) -> {
// No phone number needed, no need to ask end user for input.
if (!phoneNumberNeeded) {
callback.onSuccess(new InstantLinkStartInput(""));
}
};
Follow these instructions to implement Instant Link without allowing for resending an SMS and phone number changes.Call the
callback.onSuccess(InstantLinkStartInput input) method to return the collected phone number to the SDK.Call the callback.onError() method to communicate to the SDK any issues while trying to obtain the phone number. Report an error if the customer cancels the Instant Link transaction or presses the back button to leave the Instant Link start step dialog.Java
InstantLinkStartStep promptStartStep = (phoneNumberNeeded, instantLinkError, callback) -> {
// No phone number needed, no need to ask end user for input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (instantLinkError instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = instantLinkError.getMessage();
}
try {
// Prompt the user for phone number to receive InstantLink SMS.
// You can build UI to provide best UX based on your application and business logic,
// here we simplify to a generic function named promptForPhoneNumber which gives us
// the collected phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new InstantLinkStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new InstantLinkStartInput(""));
}
};
To use the Resend/Phone Number Change features, install the Android SDK version 6.10.3 or later.
Java
InstantLinkStartStep promptStartStep = (phoneNumberNeeded, instantLinkError, callback) -> {
// No phone number needed, no need to ask end user for input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (instantLinkError instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = instantLinkError.getMessage();
}
try {
// Prompt the user for phone number to receive InstantLink SMS.
// You can build UI to provide best UX based on your application and business logic,
// here we simplify to a generic function named promptForPhoneNumber which gives us
// the collected phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new InstantLinkStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new InstantLinkStartInput(""));
}
};
InstantLinkRetryStep protocol, for example:Java
InstantLinkRetryStep promptMultiResendRetryStep = callback -> {
// Prompt the user for whether they received the SMS.
if (promptForResend(
"Didn't receive the InstantLink SMS? Click resend button for a new one!")) {
// If the end user wants to send again to the same phone number call onResend().
callback.onResend();
}
};
To use the Resend/Phone Number Change features, install the Android SDK version 6.10.3 or later.
Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Java
InstantLinkStartStep promptStartStep = (phoneNumberNeeded, instantLinkError, callback) -> {
// No phone number needed, no need to ask end user for input.
if (phoneNumberNeeded) {
// If error message is found around phone number, handle it.
// The `PhoneNumberValidationException` is ONLY available when `phoneNumberNeeded`
// has a value.
if (instantLinkError instanceof PhoneNumberValidationException) {
// Set to a variable and display it in a field.
// In this example, we don't do anything with the error.
String errorMsg = instantLinkError.getMessage();
}
try {
// Prompt the user for phone number to receive InstantLink SMS.
// You can build UI to provide best UX based on your application and business logic,
// here we simplify to a generic function named promptForPhoneNumber which gives us
// the collected phone number.
String phoneNumber = promptForPhoneNumber();
callback.onSuccess(new InstantLinkStartInput(phoneNumber));
} catch (Exception e) {
// if any issue with the phone number collection from the end user or the user
// wants to cancel then call onError to exit the flow.
// In this example we simplify it as catching an exception.
callback.onError();
}
} else {
// No phone number needed, no need to ask end user for input.
callback.onSuccess(new InstantLinkStartInput(""));
}
};
InstantLinkRetryStep protocol, for example:Java
InstantLinkRetryStep promptPhoneNumChangeRetryStep = callback -> {
// Prompt the user for whether they received the SMS.
if (promptForChangePhoneNumber(
"Didn't receive the InstantLink SMS? Try a different phone number.")) {
// If the end user wants to send again to the same phone number call onResend().
callback.onResend();
}
};
Call finishInstantLink function after obtaining redirect URL to resume the auth session
The app must also callfinishInstantLink(String redirectUrl) after the session redirects back to the app when the Instant Link click is complete.This redirect occurs after the following steps, which take place outside the customer host app:- The end-user taps the Instant Link delivered via SMS.
- The end-user goes to a stand-alone website to complete Instant Link authentication.
- That page then redirects the user back to the original customer host app via the same deep link provided as the
finalTargetUrlfield in the earlier Start() server-to-server call. See: Server Integration Guide — Device Auth with Mobile Auth and Instant Link Fallback. Upon successful redirect back to the customer host app, the app must callfinishInstantLink(String redirectUrl)to continue the session.
finishInstantLink(String redirectUrl): From your app’s entry point that handles the deep link redirect (for example, onCreate() method in the app link receiving activity). Pass the full redirect URL string returned by the Prove server (delivered to the app after the user opens the Instant Link in SMS).Make sure to register your app link handling activity similar to this:<activity android:name=".MyAppLinkHanlderActivity"
android:exported="true"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:taskAffinity="">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="my.applink.com" />
</intent-filter>
</activity>
finalTargetUrl, plus extra parameters needed to resume the session in the customer host app. For example, if the original finalTargetUrl provided via the Start() call is https://yourDeepLinkUrl.com, the redirect URL that opens your app is https://yourDeepLinkUrl.com?asc=true&authId=some-uuid-string. Those parameters are:- asc — Must be the string
"true"or"false". Indicates whether the auth session was completed successfully on the server side (true) or not yet (false). - authId — Must be a valid UUID string. Identifies the authentication session; the SDK uses it to match the redirect to the correct in-progress session.
ProveAuthException and does not continue the flow.Swift
// Object implementing ProveAuthFinishStep protocols
let finishStep = FinishAuthStep()
// Objects implementing OtpStartStep/OtpFinishStep protocols
let otpStartStep = MobileOtpStartStep()
let otpFinishStep = MobileOtpFinishStep()
let proveAuthSdk: ProveAuth
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep)
.build()
Swift
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withMobileAuthTestMode() // Test mode flag
.build()
Swift
// authToken retrieved from your server via StartAuthRequest
proveAuthSdk.authenticate(authToken) { error in
DispatchQueue.main.async {
self.messages.finalResultMessage = "ProveAuth.authenticate returned error: \(error.localizedDescription)"
print(self.messages.finalResultMessage)
}
}
Configure OTP
To use the Resend/Retry/Phone Change features, install the iOS SDK version 6.5.1 or later.
withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep), requires implementing the OtpStartStep and OtpFinishStep.The OTP session has a two minute timeout from when it’s sent through SMS to when the customer can enter in the OTP.- Default
- Prompt for Phone Number
- Resend
- Retry OTP
- Phone Number Change
Follow these instructions if you are implementing OTP and you are passing in the phone number on the Call the
/v3/start endpoint. In this case, you’ve already prompted for a phone number so you don’t prompt for it in the client SDK.Since you passed the phone number in the Start() function, call callback.onSuccess(input: nil) to communicate to the SDK you have the customer’s agreement to deliver the SMS message.Swift
class OtpStartStepNoPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to handle phone number collection for SMS OTP,
// or to obtain user confirmation for initiating an SMS message.
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
// Since no phone number is needed, don't prompt the user.
callback.onSuccess(input: nil)
}
}
callback.onError() method to communicate to the SDK any issues while trying to obtain the phone number or the OTP. Report an error if the customer cancels the SMS transaction or presses the back button to leave the screen.Swift
class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions if implementing MobileAuth and collecting the phone number only if MobileAuth fails. This implements OTP without allowing for resending the SMS and phone number changes. If you do want those capabilities, please reference the Resend, Retry OTP, and Phone Number Change tabs.In the start step, call the Implement the finish step:
callback.onSuccess(input: otpStartInput) method to return the collected phone number to the SDK.Swift
class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
Swift
class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions to allow the customer to request a new OTP via SMS using the same phone number. There is a max of three send attempts including the initial message.Implement the start step:You can then send a new OTP SMS to the same phone number by implementing the finish step like this:
Swift
class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
Swift
class OtpFinishStepMultipleResend: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered in the SMS message.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Update your UI to display the OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Update your UI to indicate that the provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Return the OTP value to the SDK
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Communicate to the SDK any issues when host app trys to obtain the OTP value
// or when users cancel the OTP flow
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
// Call this method to request a new OTP code for the same mobile number.
func sendNewOtp() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onOtpResend()
}
}
Follow these instructions to allow the customer to re-enter the OTP PIN if they type it wrong. There is a max of 3 attempts. To implement this capability, pass in Implement the finish step - no client side code changes necessary. If the OTP is invalid, the finish step prompts the user for a new input. Once you reach the max attempts, the
allowOTPRetry=true to the /v3/start endpoint.Implement the start step - no client side code changes necessary:Swift
class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
AuthFinish function runs.Swift
class OtpFinishStepNoPrompt: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Signal to UI components to display OtpFinishView
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Signal to your UI components that the last provided OTP is invalid
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Provide the collected OTP value to the SDK for validation.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// Notify the SDK of any issues encountered while obtaining the OTP value or if the user cancels the OTP flow.
func handleOtpFinishError() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
callback.onError()
}
}
Follow these instructions to allow the customer to re-enter their phone number. There is a max of three entries/send attempts.Implement the start step:You can prompt for a new phone number by implementing the finish step like this:
Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Swift
class OtpStartStepWithPrompt: OtpStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool, phoneValidationError: ProveAuthError?, callback: OtpStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display OtpStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display OtpStartView if a phone number is needed.
self.sheetObservable.isOtpStartActive = true
}
}
}
// Return collected phone number to the SDK
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
let otpStartInput = OtpStartInput(phoneNumber: phoneNumber)
// This is how you pass collected phone number to SDK
callback.onSuccess(input: otpStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the customer explicitly cancels the SMS OTP transaction
// or presses the back button to exit out the SMS OTP start step screen.
func handleOtpStartError() {
guard let callback = self.callback else {
print("Error: OtpStartStepCallback is not set ")
return
}
callback.onError()
}
}
Swift
class OtpFinishStepWithPhoneChange: OtpFinishStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: OtpFinishStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to collect the OTP value delivered via SMS.
func execute(otpError: ProveAuthError?, callback: OtpFinishStepCallback) {
self.callback = callback
// Handle the OTP validation error if present.
// Update your UI to display the OTP finish view.
DispatchQueue.main.async {
if case .otpValidationError = otpError {
print("found otpError: \(String(describing: otpError?.localizedDescription))")
// Update your UI to indicate that the provided OTP is invalid.
self.sheetObservable.isOtpValidationError = true
} else {
self.sheetObservable.isOtpValidationError = false
}
self.sheetObservable.isOtpFinishActive = true
}
}
// Return the collected OTP value to the SDK.
func handleOtp(_ otp: String) {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set ")
return
}
let otpFinishInput = OtpFinishInput(otp: otp)
callback.onSuccess(input: otpFinishInput)
}
// When callback.onMobileNumberChange() is evoked, OtpStartStep will be re-initiated
// so that end-users can enter a different phone number via OtpStartStep.
func handleMobileNumberChange() {
guard let callback = self.callback else {
print("Error: OtpFinishStepCallback is not set")
return
}
callback.onMobileNumberChange()
}
}
Configure Instant Link
Instant Link for iOS is an add-on feature. To enable, contact your Prove representative.
Instant Link prerequisites
For the Instant Link flow on iOS, the customer host app must have a successful deep link setup. To redirect back to the original host app after the user taps the Instant Link in the SMS, the iOS host app must handle deep links. It’s highly recommended to use Universal Links for iOS. Full instructions: supporting universal links in your app | Apple developer documentationRequired functions
To set the Instant Link handler,withInstantLinkFallback(startStep: InstantLinkStartStep, retryStep?: InstantLinkRetryStep) requires implementing the InstantLinkStartStep protocol and optionally the InstantLinkRetryStep protocol if you wish for advanced capabilities. When returning the phone number, ensure you pass an InstantLinkStartInput with the phoneNumber field to the callback.The Instant Link session has a five-minute timeout from when it’s sent through SMS to when the customer can click the received link.Configure Instant Link steps
- Default
- Prompt for Phone Number
- Resend
- Phone Number Change
Follow these instructions if you are implementing Instant Link and you are passing in the phone number in the initial server-side call. In this case, you’ve already prompted the customer for a phone number so you don’t prompt for it in the client SDK.Since you passed the phone number, call
callback.onSuccess(input: nil) to communicate to the SDK that you have the customer’s agreement to deliver the SMS message.Swift
class InstantLinkStartStepNoPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to handle phone number collection for instant link,
// or to obtain user confirmation for initiating an instant link.
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
// Since no phone number is needed, don't prompt the user.
callback.onSuccess(input: nil)
}
}
Follow these instructions if you are implementing Mobile Auth and collecting the phone number for desktop. Use this to implement Instant Link without allowing the customer to resend the SMS or phone number changes.Call the
callback.onSuccess(input: instantLinkStartInput) method to return the collected phone number to the SDK.Call the callback.onError() method to communicate to the SDK any issues while trying to obtain the phone number. Report an error if the customer cancels the Instant Link transaction or presses the back button to leave the Instant Link start step dialog.Swift
class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
To use the Resend/Phone Number Change features, install the iOS SDK version 6.10.2 or later.
Swift
class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
InstantLinkRetryStep protocol, for example:Swift
class InstantLinkRetryStepMultipleResend: InstantLinkRetryStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkRetryStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to present retry options: resend to the same phone number or cancel.
func execute(callback: InstantLinkRetryStepCallback) {
self.callback = callback
// Update your UI to display the InstantLinkRetryView (e.g. "Did you receive a text message?").
// User can confirm (close modal / finish) or request resend to the same phone number.
DispatchQueue.main.async {
self.sheetObservable.isInstantLinkRetryActive = true
}
}
// Call this method to request a new instant link to the same phone number.
func handleResend() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onResend()
}
// Notify the SDK if the user cancels or if the app fails to handle the retry step.
func handleInstantLinkRetryError() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onError()
}
}
To use the Resend/Phone Number Change features, install the iOS SDK version 6.10.2 or later.
Manual Request RequiredTo enable phone number change capabilities on your credentials, contact your Prove representative.
Swift
class InstantLinkStartStepWithPrompt: InstantLinkStartStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkStartStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
func execute(
phoneNumberNeeded: Bool,
phoneValidationError: ProveAuthError?,
callback: InstantLinkStartStepCallback
) {
self.callback = callback
if !phoneNumberNeeded {
// If no phone number is needed, then don't prompt the user.
callback.onSuccess(input: nil)
} else {
DispatchQueue.main.async {
// If a phone number validation error is detected, ensure it is handled to provide feedback to the user.
if case .phoneNumberValidationError = phoneValidationError {
print(
"found phoneValidationError: \(String(describing: phoneValidationError?.localizedDescription))"
)
// Update UI components to display InstantLinkStartView with the phone number validation error.
self.sheetObservable.isPhoneValidationError = true
} else {
self.sheetObservable.isPhoneValidationError = false
}
// Update UI components to display InstantLinkStartView if a phone number is needed.
self.sheetObservable.isInstantLinkStartActive = true
}
}
}
// Return collected phone number to the SDK.
func handlePhoneNumber(phoneNumber: String) {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
let instantLinkStartInput = InstantLinkStartInput(phoneNumber: phoneNumber)
callback.onSuccess(input: instantLinkStartInput)
}
// Communicate any issues encountered while trying to obtain the phone number to the SDK.
// Error should be reported if the user cancels the instant link flow or exits the start step screen.
func handleInstantLinkStartError() {
guard let callback = self.callback else {
print("Error: InstantLinkStartStepCallback is not set ")
return
}
callback.onError()
}
}
InstantLinkRetryStep protocol, for example:Swift
class InstantLinkRetryStepPhoneChange: InstantLinkRetryStep {
@ObservedObject var sheetObservable: SheetObservable
var callback: InstantLinkRetryStepCallback?
init(sheetObservable: SheetObservable) {
self.sheetObservable = sheetObservable
}
// Implement this method to present retry options: resend, change phone number, or cancel.
func execute(callback: InstantLinkRetryStepCallback) {
self.callback = callback
// Update your UI to display the InstantLinkRetryView (e.g. "Did you receive a text message?").
// User can confirm (close modal / finish), request resend, or request phone number change.
DispatchQueue.main.async {
self.sheetObservable.isInstantLinkRetryActive = true
}
}
// Call this method to request resend to the same phone number.
func handleResend() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onResend()
}
// When this is invoked, InstantLinkStartStep will be re-initiated so that the user
// can enter a different phone number.
func handleMobileNumberChange() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onMobileNumberChange()
}
// Notify the SDK if the user cancels or if the app fails to handle the retry step.
func handleInstantLinkRetryError() {
guard let callback = self.callback else {
print("Error: InstantLinkRetryStepCallback is not set ")
return
}
callback.onError()
}
}
Call finishInstantLink to resume auth session after redirect
The Configure Instant Link Steps and callingproveAuth.authenticate(), the app must also call finishInstantLink(redirectUrl:) after the session redirects back to the app when the Instant Link click is complete.This redirect occurs after the following steps, which take place outside the customer host app:- The end-user taps the Instant Link delivered via SMS.
- The end-user goes to a stand-alone website to complete Instant Link authentication.
- That page then redirects the user back to the original customer host app via the same deep link provided as the
finalTargetUrlfield in the earlier server-to-server call. See: Server Integration Guide — Device Auth with Mobile Auth and Instant Link Fallback. Upon successful redirect back to the customer host app, the app must callfinishInstantLink(redirectUrl:)to continue the session.
Swift
/// Finishes the Instant Link authentication flow using the redirect URL from the deep link.
/// This is the URL to which the user is redirected after tapping the Instant Link in SMS.
finishInstantLink(redirectUrl: redirectUrl) { error in
// Handle errors due to invalid format of redirectUrl here
}
finishInstantLink(redirectUrl:) from your app’s entry point that handles the deep link redirect, application(_:open:options:) or application(_:continue:restorationHandler:). Pass the full redirect URL string returned by the Prove server (delivered to the app after the user opens the Instant Link in SMS).The redirect URL returned by the Prove server has your original finalTargetUrl plus extra parameters needed to resume the session in the customer host app. For example, if the original finalTargetUrl provided is https://yourDeepLinkUrl.com, the redirect URL that opens your app is https://yourDeepLinkUrl.com?asc=true&authId=some-uuid-string. Those parameters are:- asc — Must be the string
"true"or"false". Indicates whether the auth session was completed successfully on the server side (true) or not yet (false). - authId — Must be a valid UUID string. Identifies the authentication session; the SDK uses it to match the redirect to the correct in-progress session.
onError and doesn’t continue the flow.Verify Mobile Number
In the Prove Key persistence enhancementUsing Prove Key in web applications can be affected by strict browser privacy policies aimed at preventing cross-site tracking. For example, Safari completely disables third-party cookies and limits the lifetime of all script-writable website data. Prove Auth uses script-writable website data (localStorage and IndexedDB) for storing Prove Auth Web app setupFollow these steps to add Prove Key persistence in your web app:Handling multiple device registrationsIf a user registers their device multiple times before using Device Context, Prove Auth may have multiple registration candidates matching the same visitor ID. To improve recovery accuracy:The function returns the following fields:
AuthFinishStep, specify a function to call once the possession checks complete on the mobile phone. This endpoint on your back end server calls the UnifyStatus() function to validate the phone number. The AuthFinishStep then completes.rspUnifyStatus, err := client.V3.V3UnifyStatusRequest(context.TODO(), &components.V3UnifyStatusRequest{
CorrelationID: rspUnify.V3UnifyResponse.CorrelationID,
})
if err != nil {
return fmt.Errorf("error on UnifyStatus(): %w", err)
}
If you’re using Content Security Policy headers, ensure you allow
wss: device.uat.proveapis.com and wss: device.proveapis.com.deviceId, crypto key material, and other critical metadata for subsequent device re-authentication.If the browser deletes this script-writable data, Prove Auth is no longer able to recognize the device and perform authentication. To mitigate this limitation and avoid repeating the device registration process, follow these steps to enable Device Context.The device context feature is not enabled by default. Contact Prove to enable this add-on feature.
Install the Device Context Module
Install the Device Context integration module and add activation code to your app:Then activate it in your app:
NPM
npm install @prove-identity/prove-auth-device-context
import * as dcMod from "@prove-identity/prove-auth-device-context";
dcMod.activate();
Configure the Authenticator Builder
When configuring
authenticatorBuilder for any flow, add the withDeviceContext() method to enable device context data collection:// Prove supports integration within the US and EU regions for device context data collection
// The public API Key for each build config will be provided by Prove
const publicApiKey = "apiKeyAssignedForThisBuildConfig";
const buildConfig = BuildConfig.US_UAT;
const options = {
publicApiKey: publicApiKey,
buildConfig: buildConfig,
};
// Enable device context data with builder method
let builder = new proveAuth.AuthenticatorBuilder();
builder = builder
.withAuthFinishStep((input) => verify(input.authId))
.withMobileAuthImplementation("fetch")
.withOtpFallback(otpStart, otpFinish)
.withDeviceContext(options);
const authenticator = builder.build();
Call
authenticatorBuilder.build() immediately after the web app loads. This enables the Web SDK to load components for device context collection and collect signals before authentication begins, which helps decrease latency.Enable Cookies for Auth Requests
Ensure cookies are enabled when you make
AuthStart and AuthFinish calls:// Enable cookies for AuthStart
axios.post(backendUrl + '/authStart', data, { withCredentials: true });
// Enable cookies for AuthFinish
axios.post(backendUrl + '/authFinish', data, { withCredentials: true });
- Store the
deviceIdreturned from successful authentications in your backend (database or web cookie) - Pass the stored
deviceIdwith the/unifyrequest to specify which registration to restore
// Send the Unify request with deviceId for registration recovery
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
PhoneNumber: "2001004014",
PossessionType: "mobile",
DeviceId: provesdkservergo.String("stored-device-id-from-previous-auth"),
})
success: eithertrueif the mobile number validation was successful, orfalseif it failed.phoneNumber: the phone number associated with the possession check.clientHumanId: if provided in the request.clientRequestId: if provided in the request.deviceId: the unique identifier for the Prove Key on the device.evaluation: the evaluation result for the Global Fraud Policy.proveId: a unique ID to identify a specific customer obtained from a successful possession check.
If you send a different phone number to /unify than the one registered to the Prove key,
success=false returns when calling /unify-status. This is because the Prove Key is bound to a different number. Run the flow again with rebind=true in the /unify endpoint. This forces a full possession check and then, if valid, rebinds the Prove Key to the new phone number.This section only applies to mobile channels.
Initialize the Flow
Send a request to your back end server with the phone number and The function returns the following fields:
possessionType=none to start the flow.Optional parameters:checkReputation: if true, Prove performs TrustScore verification.clientHumanId: a client-generated unique ID to identify a specific customer across business lines.clientCustomerId: a client-generated unique ID for a specific customer. You can link calls related to the same customer, across different requests or sessions. The client defines the format of this ID.deviceId: the unique identifier for the Prove Key on the device.emailAddress: the email address of the customer.ipAddress: the IP address of the customer.proveId: a unique ID to identify a specific customer obtained from an earlier successful authentication.rebind: iftrue, rebinds the Prove Key with the newly verified phone number.
// Send the Unify request.
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
PhoneNumber: "2001004014",
PossessionType: "mobile",
})
if err != nil {
t.Fatal(err)
}
authToken: send this to your client-side code to pass into theAuthenticate()function - it’s a short lived JSON Web Token (JWT) tied to the current flow and used for the possession checks.correlationId: save this in your current session, then pass it in to theUnifyStatus()function call of the same flow. The correlation ID ties together different system calls for the same Prove flow. It also aids in troubleshooting. The session expires in 15 minutes from when the correlation ID returns from theUnify()call.success: returnspendingfor this initial call.
authToken in a response to the front end.Authenticate
Initialize the client-side SDK to place a Prove key on the device or to check whether a Prove key is bound.
- Web SDK
async function authenticate(isMobileWeb, authToken) {
// Set up the authenticator for the mobile flow.
let builder = new proveAuth.AuthenticatorBuilder();
builder = builder
.withAuthFinishStep((input) => verify(input.authId));
const authenticator = builder.build();
// Authenticate with the authToken.
return authenticator.authenticate(authToken);
}
Verify Mobile Number
In the The function returns the following fields:
AuthFinishStep of the client SDK, have the function make a call to an endpoint on your back end server. Your backend server should then call the UnifyStatus() function to validate the phone number. The AuthFinishStep then completes.rspUnifyStatus, err := client.V3.V3UnifyStatusRequest(context.TODO(), &components.V3UnifyStatusRequest{
CorrelationID: rspUnify.V3UnifyResponse.CorrelationID,
})
if err != nil {
return fmt.Errorf("error on UnifyStatus(): %w", err)
}
success: eitherpossession_requiredif the reputation check was successful, orfalseif it failed.phoneNumber: the phone number associated with the possession check.clientHumanId: if provided in the request.clientRequestId: if provided in the request.deviceId: the unique identifier for the Prove Key on the device.evaluation: the evaluation result for the Global Fraud Policy.proveId: a unique ID to identify a specific customer obtained from a successful possession check.
Perform Possession Check
Your app must perform a customer-supplied possession check such as SMS OTP. Only proceed to the next step if the possession check passes.
Call the Bind Endpoint
Call
UnifyBind() after UnifyStatus() returns success=possession_required. Call this endpoint if and only if the possession check has passed. This binds the phone number to the Prove Key for future authentications.This function takes these required parameters:-
correlationId: the ID returned by theUnify()function. -
phoneNumber: the phone number to bind to the Prove Key. -
clientRequestId: a client-generated unique ID for a specific session.The function returns the following fields:rspUnifyBind, err := client.V3.V3UnifyBindRequest(context.TODO(), &components.V3UnifyBindRequest{ CorrelationID: rspUnify.V3UnifyResponse.CorrelationID, PhoneNumber: "2001004018", }) if err != nil { return fmt.Errorf("error on UnifyBind(): %w", err) }success:trueif the binding succeeded,falseif it failed.phoneNumber: the phone number bound to the Prove Key.clientHumanId: if provided in the request.clientRequestId: if provided in the request.deviceId: the unique identifier for the Prove Key on the device.evaluation: the evaluation result for the Global Fraud Policy.
This section only applies to mobile channels.
Determine Type of Flow
- Web SDK
- Android SDK
- iOS SDK
Decide if the customer is on a mobile or desktop browser using this example. If the
isMobile is true, set mobile as the flowType for the Start() function on the server, otherwise you can set desktop:// Check if the customer is on a mobile or desktop browser.
const authCheck = new proveAuth.AuthenticatorBuilder().build();
let isMobile = authCheck.isMobileWeb()
When using the Android SDK, set
mobile as the flowType for the Start() function on the server.When using the iOS SDK, set
mobile as the flowType for the Start() function on the server.Initialize the Flow
Send a request to your back end server with The function returns the following fields:
possessionType=none to start the flow.Optional parameters:clientCustomerId: a client-generated unique ID for a specific customer. You can link calls related to the same customer, across different requests or sessions. The client defines the format of this ID.clientRequestId: a client-generated unique ID for a specific session. You can identify specific requests using this field. You decide the format of this ID.
// Send the Unify request.
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
PossessionType: "none",
ClientRequestID: provesdkservergo.String("client-abc-123"),
})
if err != nil {
t.Fatal(err)
}
authToken: send this to your client-side code to pass into theAuthenticate()function - it’s a short lived JSON Web Token (JWT) tied to the current flow and used for the possession checks.correlationId: save this in your current session, then pass it in to theUnifyStatus()function call of the same flow. The correlation ID ties together different system calls for the same Prove flow. It also aids in troubleshooting. The session expires in 15 minutes from when the correlation ID returns from theUnify()call.success: returnspendingfor this initial call.
authToken in a response to the front end.Authenticate
Once you have the The mobile data connection can sometimes be unavailable during testing. The The If a mobile data connection is unavailable during testing, use the Builder class. It permits simulated successful session results while connected to a Wi-Fi network. Testing using a Wi-Fi connection is useful in the Sandbox environment.The Prove Auth object is thread safe and used as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the SDK’s ability to process concurrent blocking requests.
authToken, build the authenticator for both the mobile and desktop flows.- Web SDK
- Android SDK
- iOS SDK
async function authenticate(isMobileWeb, authToken) {
// Set up the authenticator for either mobile or desktop flow.
let builder = new proveAuth.AuthenticatorBuilder();
if (isMobileWeb) {
// Set up Mobile Auth and OTP.
builder = builder
.withAuthFinishStep((input) => verify(input.authId))
.withMobileAuthImplementation("fetch")
.withOtpFallback(otpStart, otpFinish);
} else {
// Set up Instant Link.
builder = builder
.withAuthFinishStep((input) => verify(input.authId))
.withInstantLinkFallback(instantLink)
.withRole("secondary");
}
const authenticator = builder.build();
// Authenticate with the authToken.
return authenticator.authenticate(authToken);
}
If you’re using Content Security Policy headers, ensure you allow
wss: device.uat.proveapis.com and wss: device.proveapis.com.Java
// Object implementing AuthFinishStep interface
AuthFinishStep authFinishStep = new AuthFinishStep() {
...
};
// Objects implementing OtpStartStep/OtpFinishStep interfaces
OtpStartStep otpStartStep = new OtpStartStep() {
...
};
OtpFinishStep otpFinishStep = new OtpFinishStep() {
...
};
ProveAuth proveAuth = ProveAuth.builder()
.withAuthFinishStep(authId -> verify(authId)) // verify(authId) call defined in #Validate the Mobile Phone section
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(this)
.build();
Builder class offers a withTestMode(boolean testMode) method, which permits simulated successful session results while connected to a Wi-Fi network only. Testing using a Wi-Fi connection is useful in the Sandbox environment.Java
ProveAuth proveAuth = ProveAuth.builder()
.withAuthFinishStep(authId -> verify(authId))
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(this)
.withTestMode(true) // Test mode flag
.build();
ProveAuth object is thread safe. You can use it as a singleton. Most Prove Auth methods are blocking and therefore can’t execute in the main app thread. The app employs an executor service with a minimum of two threads to manage threads due to the ability to process concurrent blocking requests.Java
public class MyAuthenticator {
private final MyBackendClient backend = new MyBackendClient(); // Backend API client
private final AuthFinishStep authFinishStep = new AuthFinishStep() {
@Override
void execute(String authId) {
try {
AuthFinishResponse response = backend.authFinish("My App", authId);
... // Check the authentication status returned in the response
} catch (IOException e) {
String failureCause = e.getCause() != null ? e.getCause().getMessage() : "Failed to request authentication results";
// Authentication failed due to request failure
}
}
};
private ProveAuth proveAuth;
public MyAuthenticator(Context context) {
proveAuth = ProveAuth.builder()
.withAuthFinishStep(authFinishStep)
.withOtpFallback(otpStartStep, otpFinishStep)
.withContext(context)
.build();
}
public void authenticate() throws IOException, ProveAuthException {
AuthStartResponse response = backend.authStart("My Prove Auth App");
proveAuth.authenticate(response.getAuthToken());
}
}
Swift
// Object implementing ProveAuthFinishStep protocols
let finishStep = FinishAuthStep()
// Objects implementing OtpStartStep/OtpFinishStep protocols
let otpStartStep = MobileOtpStartStep()
let otpFinishStep = MobileOtpFinishStep()
let proveAuthSdk: ProveAuth
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withOtpFallback(otpStart: otpStartStep, otpFinish: otpFinishStep)
.build()
Swift
proveAuthSdk = ProveAuth.builder(authFinish: finishStep)
.withMobileAuthTestMode() // Test mode flag
.build()
Swift
// authToken retrieved from your server via StartAuthRequest
proveAuthSdk.authenticate(authToken) { error in
DispatchQueue.main.async {
self.messages.finalResultMessage = "ProveAuth.authenticate returned error: \(error.localizedDescription)"
print(self.messages.finalResultMessage)
}
}
Verify Mobile Number
In the The function returns the following fields:
AuthFinishStep, specify a function to call once the possession checks complete on the mobile phone. This endpoint on your back end server calls the UnifyStatus() function to validate the phone number. The AuthFinishStep then completes.rspUnifyStatus, err := client.V3.V3UnifyStatusRequest(context.TODO(), &components.V3UnifyStatusRequest{
CorrelationID: rspUnify.V3UnifyResponse.CorrelationID,
})
if err != nil {
return fmt.Errorf("error on UnifyStatus(): %w", err)
}
success: eithertrueif the mobile number validation was successful, orpossession_requiredif validation requires a possession check to complete.phoneNumber: the phone number associated with the possession check.clientHumanId: if provided in the request.clientRequestId: if provided in the request.deviceId: the unique identifier for the Prove Key on the device.evaluation: the evaluation result for the Global Fraud Policy.proveId: a unique ID to identify a specific customer obtained from a successful possession check.
Perform Possession Check
If
success returned possession_required, your app must perform a customer-supplied possession check such as SMS OTP. Only proceed to the next step if the possession check passes.Call the Bind Endpoint
Call The function returns the following fields:
UnifyBind() after UnifyStatus() returns success=possession_required. Call this endpoint if and only if the possession check has passed. This binds the phone number to the Prove Key for future authentications.This function takes these required parameters:correlationId: the ID returned by theUnify()function.phoneNumber: the phone number to bind to the Prove Key.clientRequestId: a client-generated unique ID for a specific session.
rspUnifyBind, err := client.V3.V3UnifyBindRequest(context.TODO(), &components.V3UnifyBindRequest{
CorrelationID: rspUnify.V3UnifyResponse.CorrelationID,
PhoneNumber: "2001004018",
})
if err != nil {
return fmt.Errorf("error on UnifyBind(): %w", err)
}
success:trueif the binding succeeded,falseif it failed.phoneNumber: the phone number bound to the Prove Key.clientHumanId: if provided in the request.clientRequestId: if provided in the request.deviceId: the unique identifier for the Prove Key on the device.evaluation: the evaluation result for the Global Fraud Policy.
If you send a different phone number to /unify than the one registered to the Prove key, the customer receives
possession_required on the /unify-status call. Call /unify-bind to rebind the Prove Key to the new number. Once it’s rebound, the first number responds with possession_required. The Prove key only supports one phone number.Error handling
If authentication was not successful, theevaluation field returned by UnifyStatus() has failure reasons explaining what went wrong according to the Global Fraud Policy.
Possession failure reasons
| Code | Description |
|---|---|
| 9100 | Phone possession incomplete. |
| 9101 | Phone number doesn’t match Prove Key. Rebind required. |
Error code 9100 - Phone possession incomplete
For Prove Possession - Desktop and Prove Possession - Mobile, error code 9100 indicates that authentication was not successful.Error code 9101 - Phone number doesn’t match Prove Key
For Prove Possession - Mobile and Customer-Supplied Possession, error code 9101 indicates that someone attempted authentication using a phone number that doesn’t match the phone number registered to the Prove Key. How to resolve: To rebind the Prove Key to the new phone number, restart the flow withrebind=true in the call to /unify:
rspUnify, err := client.V3.V3UnifyRequest(ctx, &components.V3UnifyRequest{
PhoneNumber: "2001004014",
PossessionType: "mobile",
Rebind: provesdkservergo.Bool(true),
})
rebind=true:
- Prove initiates a new authentication check.
- If the authentication is successful, the Prove Key is rebound to the new phone number passed into the request.
- Future authentications with this phone number succeed without requiring rebind.

