Tutorial: Login with email or phone

Serverless AuthenticationSigning in and out Login with email or phone

Webcom is able to provide internally managed identities to authenticate users. Currently, two kinds of identities are provided:

  • Email-based identities rely on the user's email address,
  • Phone-based identities rely on the user's mobile phone number (or at least a phone number which can receive SMS).

Credentials to authenticate these identities are either securely stored and encrypted in the Webcom back end (typically when users are prompted to enter a permanent password) or securely and randomly generated on demand (typically when the users are sent a one time password).

By default, only the email-based authentication method is enabled on new Webcom applications. Email- and phone-based authentication methods may be freely enabled or disabled in the "authentication" tab of the Webcom developer console.

Webcom SDK exposes functions to create, authenticate and update internally managed (email- and phone-based) identities, letting the application developer have full control over the user interface.

In order to ensure that the SMS will be successfully sent to the users when using phone authentication, you must subscribe to a (generally charged) SMS sending service and properly set it up in the "phone" cartridge of the "authentication" tab in the Webcom developer console. You can find the complete instructions at the Setting up SMS sending section.

Sign in with internally managed identities

Email-based identities

Once confirmed, an email-based identity can be used to sign in, using the following snippet (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 with email
auth.signInWithCredentials("password", {
        id: "macadamia@webcom.com",
        password: "my-password"})
   .then(authDetails => console.log("Authentication succeeded", authDetails))
   .catch(error => {
     switch (error.code) {
       case "INVALID_CREDENTIALS":
         console.log("The email or password is incorrect.");
         break;
       case "PROVIDER_DISABLED":
         console.log("The email-based authentication method is disabled in the application. It must be enabled in the Webcom developer console.");
         break;
       default:
         console.log("An unexpected error occurs, please retry and contact your administrator.", error);
     }
   });

The authDetails parameter returned by the promise is a JSON object representing the signed in identity, which directly feeds the current authentication state. The following properties are specialized this way:

Property Type Description
provider string Equals "password".
providerUid string The email address of the signed in user.
providerProfile object (optional) The profile of the signed in user, when specified at signup with the addIdentity() method.
val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val authenticator = myApp.authenticationService

authenticator
  .getPersonalPasswordMethod(Provider.Email, "macadamia@webcom.com")
  .authenticate("my-password") {
    when (it) {
      is WebcomResult.Success -> print("logged in with the token: ${it.result.authenticationToken}")
      is WebcomResult.Failure -> print("error: ${it.error.errorCode}")
    }
  }
let authenticationService = Webcom.defaultApplication.authenticationService
let internalMethod = AuthenticationMethodInternal(eMail: "macadamia@webcom.com")
authenticationService.initializeAuthentication(with: internalMethod)
internalMethod.setStaticPassword("my-password")
authenticationService.continueAuthentication { result in
    switch result {
    case let .success(details):
        print("Authenticated with token:", details.authenticationToken)
    case let .failure(error):
        print("Error:", error)
    }
}

Phone-based identities

Signing in with a phone-based identity is a 2-steps process:

  1. Send to the user an SMS containing a one time password (OTP) dedicated to the sign in operation,
  2. Sign the user in by providing, in addition to the user's phone number, the identifier of the OTP previously sent (called challenge) and the OTP itself received by the user.
sequenceDiagram participant U as User participant A as Application participant B as Webcom A->>+B: Request to send a one time password B->>U: Send *otp* by SMS B->>-A: *challenge* U->>A: Provide received *otp* A->>B: Request to authenticate with *msisdn*, *challenge*, *otp* B->>A: *authentication state*

To do so, use the following snippet (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;
const phoneId = "33612345678";
// Send the OTP
auth.sendOtp("phone", phoneId)
    .then(challenge => {
        askTheUserForTheReceivedOtp() // function expected to prompt the user with an input box to enter the OTP
            .then(otp => {
                // Actually sign in
                auth.signInWithCredentials("phone", {id: phoneId, challenge: challenge, password: otp})
                    .then(authDetails => console.log(`Logged in with uid: ${authDetails.uid}`))
                    .catch(error => console.log(`Login failed: ${error}`));
            })
            .catch(() => console.log('The user gave up the login operation...'));
    })
    .catch(error => console.log(`Failed to send OTP: ${error}`));

The authDetails parameter returned by the promise of the authInternally() method is a JSON object representing the signed in identity, which directly feeds the current authentication state. The following properties are specialized this way:

Property Type Description
provider string Equals "phone".
providerUid string The MSISDN of the signed in user (without the "+" prefix).
providerProfile object (optional) The profile of the signed in user, when specified at signup with the addIdentity() method.
val myApp = WebcomApplication.default // the app defined by the 'webcom.properties' asset file
val authenticator = myApp.authenticationService

fun askTheUserForTheReceivedOtp(callback: (String) -> Unit) {
    // this method is expected to prompt the user with an input box to enter the received OTP
    // the entered OTP is the passed back to the `callback` parameter as a String
}

with(authenticator.getOneTimePasswordMethod(Provider.Phone, "33612345678")) {
    sendOneTimePassword { // it: WebcomResult<Unit>
        if (it is WebcomResult.Failure) print("SMS could not be sent: ${it.error.errorCode}")
        else askTheUserForTheReceivedOtp { otp: String -> 
            authenticate(otp) { auth: WebcomResult<AuthenticationDetails> ->
                when (auth) {
                    is WebcomResult.Success -> print("logged in with the account: ${auth.result.authenticationToken}")
                    is WebcomResult.Failure -> print("error: ${auth.error.errorCode}")
                }
            }
        }
    }
}

Note that the OneTimePasswordMethod object returned by the getOneTimePasswordMethod() manages the challenge received from the back end by its own, and its authenticate() method requires to be passed only the OTP received by the user.

let authenticationService = Webcom.defaultApplication.authenticationService
let internalMethod = AuthenticationMethodInternal(msisdn: "33612345678")
authenticationService.initializeAuthentication(with: internalMethod)
internalMethod.requestOneTimePassword { result in
    switch result {
    case .success:
        let code = "..." // In fact, you would probably use another view controller to ask the code to the user.
        internalMethod.setOneTimePassword(code)
        authenticationService.continueAuthentication { result in
            switch result {
            case let .success(details):
                print("Authenticated with token:", details.authenticationToken)
            case let .failure(error):
                print("Error:", error)
            }
        }
    case let .failure(error):
        print("Failed to send OTP:", error)
    }
}