Tutorial: Multi-factor login

Serverless AuthenticationSigning in and out Multi-factor login

The underlying model of the Webcom Authentication Service allows to bind several identities to a given Webcom account. In other words, a given user may use several authentication methods to authenticate (namely one per identity). This feature is used by the Authentication Service to implement multi-factor authentication.

When signing a user in using any authentication method, s/he is by default authenticated with only the corresponding identity. This means that if the user was previously authenticated with another identity, it is forgotten. In order to keep previous authenticated identities of a given user when signing her/him in with a new identity, and so have a multi-factor authentication, the authentication method must be initialized in multi-factor mode before performing the sign in operation.

When performed in multi-factor mode, an authentication operation merges the current authentication state into its regular resulting state in the following way:

  • If the current authentication state refers to a Webcom account different from the one bound to the user being authenticated, then the authentication operation fails. Only identities bound to the same account may enrich the current authentication state.
  • If the authentication operation succeeds, its result becomes the new authentication state and its Context attribute (see Authentication state) is updated with the concatenation of the identity referred to by the previous authentication state and the Context attribute of the previous authentication state.
graph TB S1["Current state is uid123 with:
"] S2["Current state is uid123 with:
"] S3["Current state is uid123 with:
"] S4["Current state is uid123 with:
"] S1 --Authenticate in multi-factor mode--> S2 S2 --Authenticate in multi-factor mode--> S3 S3 --Authenticate in default mode--> S4

In order to set the next authentication operation in multi-factor mode, all you need is calling the useCurrentContextForNextAuthOperation() method before performing an authentication operation.

For example a 2-factor authentication with email/password and SMS OTP can be implemented this way (replace “<your-app>” with your actual application identifier):

// const app = Webcom.App("<your-app>"); // UNCOMMENT if you haven't yet an instance of your app!
// Get an instance of the authentication service
const auth = app.authentication;
// Sign in
// Initial authentication (in default mode)
auth.signInWithCredentials("password", theUserCredentials)
    .then(() => {
        // 2nd factor authentication (in multi-factor mode)
        auth.sendOtp("phone", theUserMsisdn)
            .then(challenge => {
                auth.signInWithCredentials("phone", {id:theUserMsisdn,password:getReceivedOtp(),challenge:challenge})
                    .then(authDetails => console.log("Logged with 2 factor authentication: ", authDetails))
                    .catch(error => console.error("2nd authentication failed: ", error));
            .catch(error => console.error("Could not send OTP to authenticate with the 2nd factor: ", error));
    .catch(error => console.error("1st authentication failed: ", error));

In order to set an authentication operation in multi-factor mode, all you need is setting the context parameter of the authenticate() method of the chosen authentication method to Context.Current.

For example a 2-factor authentication with email/password and SMS OTP can be implemented this way:

import com.orange.webcom.sdk.authentication.AuthenticationService.Context

fun askTheUserForThePersonalPassword(callback: (String) -> Unit): Unit {
    // ...

fun askTheUserForTheReceivedOtp(callback: (String) -> Unit): Unit {
    // ...

val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val authenticator = myApp.authenticationService
val emailPasswordMethod = authenticator.getPersonalPasswordMethod(Provider.Email, "macadamia@webcom.com")
val phoneOtpMethod = authenticator.getOneTimePasswordMethod(Provider.Phone, "33612345678")
askTheUserForThePersonalPassword { password: String ->
    // classic 1-factor authentication
    emailPasswordMethod.authenticate(password) { emailAuth: WebcomResult<AuthenticationDetails> ->
        if (emailAuth is WebcomResult.Failure) print("Email login failed: ${emailAuth.error.message}")
        else phoneOtpMethod.sendOneTimePassword { // it: WebcomResult<Unit>
            if (it is WebcomResult.Failure) print("SMS could not be sent: ${it.error.message}")
            else askTheUserForTheReceivedOtp { otp: String ->
                // multi-factor authentication (context=Context.Current)
                phoneOtpMethod.authenticate(otp, Context.Current) { auth: WebcomResult<AuthenticationDetails> ->
                    when (auth) {
                        is WebcomResult.Success -> print("logged in with 2-factor authentication: ${auth.result.previousFactors}")
                        is WebcomResult.Failure -> print("Phone login failed: ${auth.error.message}")

In order to set the next authentication operation in multi-factor mode, all you need is using the .current value for the context parameter when calling the authenticate() or the initializeAuthentication() method.

For example a 2-factor authentication with email/password and SMS OTP can be implemented this way:

let authenticationService = Webcom.defaultApplication.authenticationService
let eMailMethod = AuthenticationMethodInternal(eMail: "macadamia@webcom.com")
authenticationService.initializeAuthentication(with: eMailMethod)
authenticationService.continueAuthentication { result in
    if case let .failure(error) = result {
        print("1st authentication failed:", error)
    let phoneMethod = AuthenticationMethodInternal(msisdn: "33612345678")
    authenticationService.initializeAuthentication(with: phoneMethod)
    phoneMethod.requestOneTimePassword { result in
        if case let .failure(error) = result {
            print("Could not send OTP to authenticate with the 2nd factor:", error)
        let code = "..." // In fact, you would probably use another view controller to ask the code to the user.
        authenticationService.continueAuthentication { result in
            switch result {
            case let .success(details):
                print("Logged with 2 factor authentication:", details)
            case let .failure(error):
                print("2nd authentication failed:", error)