How-tos

Use your own UI and Flows for User Sign-Up and Sign-in with App ID

Share this post:

With IBM Cloud App ID, you can easily add authentication and authorization to the apps
and APIs that you have running on IBM Cloud. App ID lets you add enterprise sign-in, or social sign-in,
or you can use the Cloud Directory feature to allow users to sign up directly from your app with an
email and password. App ID provides a default sign-up and sign-in widget with out-of-the-box user
flows, such as reset password and email verification.
Or you can replace the provided flows with your own branded custom UIs and flows.

Using the Node.js SDK and App ID APIs to customize an app

In a previous blog post I showed how you can replace the App ID sign-in UI with your own branded UI.
Since then, we’ve added more capabilities to help you further customize the entire sign-up experience.
Examples of how you can customize the experience include the following:

  • Replace the provided sign in widget with your own
  • Add extra fields to your sign-up flow, e.g. phone number
  • Validate password length by policy
  • Accept only specific email addresses or check against a black-list
In this blog I will demonstrate how to replace the pre-built sign-in widget with your own, and add a phone number field in the sign-up screen. In this repository you’ll find a web application named Cloud Land that uses App ID with a customized sign-up and sign-in experience. And you’ll find a similarly customized Android application.
The Cloud Land web application also serves as a back-end for the Android application.
I will explain how use the Cloud Directory APIs to customize the sign-up and sign-in experience.
The source code and running instructions are available here.

Initializing the sample app

The Cloud Land web app uses the App ID Node.JS SDK SelfServiceManager module to interact with the App ID management APIs. If your runtime is not Node.js, you can still use the APIs directly.
The management APIs are protected with an IAM token. To work with the APIs, the SelfServiceManager module can be initialize with an iamApiKey in order to obtain the access token that you’ll append to each request. If your app runs on IBM Cloud you do not need to provide the IAM token. If you prefer not to pass your key, all of the SelfServiceManager APIs support getting IAM tokens directly.

let selfServiceManager = new SelfServiceManager();

Customizing the sign-in flow

To trigger sign-in once your user clicks ‘sign-in’ from your custom UI screen when a user clicks sign in, add the following code to your app. If you have any trouble, try walking through my previous blog before moving on.

app.post(ROP_SUBMIT, function(req, res, next) {
 passport.authenticate(WebAppStrategy.STRATEGY_NAME, function (err, user, info) {
  if (err) {
   return next(err);
  }
  if (!user) {
   req.flash(‘errorCode’, info.code);
   return res.redirect(ROP_LOGIN_PAGE_URL + languageQuery + emailInputQuery);
  }
  req.logIn(user, function (err) {
   if (err) {
    return next(err);
   }
   return res.redirect(LANDING_PAGE_URL + languageQuery);
  });
 })(req, res, next);
});

Customizing the sign-up flow

When a user signs-up from the sample app, the app calls the Cloud directory sign-up APIs. The call both registers the user and sends them a verification email that completes the registration. The are other possible flows, but the following is my configuration of the sample app.

app.post(SIGN_UP_SUBMIT, function(req, res) {
…
selfServiceManager.signUp(userData, language).then(function (user) {
 logger.debug(‘user created successfully’);
 if (platform === MOBILE_PLATFORM) {
  res.status(201).send(user);
 } else {
  _render(req, res, thanksForSignUpEjs,  {
   displayName: user.displayName ,
   email: user.emails[0].value,
   uuid: user.id
  }, language);
 }
}
…

When the user receives the verification email and clicks on the verification button, Cloud Land receives a verification request to the on_user_verified action URL that I predefined in the App ID dashboard. In order for the sample app to trust the incoming verification request and receive the result, App ID sends a context query parameter. The Cloud Land app then retrieves the verification request by using the /sign_up/confirmation_result API endpoint and displays the proper custom UI.

 

app.get(ON_USER_VERIFIED, function (req, res) {
 let context = req.query.context;
 let language = req.query.language;
 selfServiceManager.getSignUpConfirmationResult(context).then(function (result) {
  let options = {
   errorStatusCode: ”,
   errorDescription: ”,
   uuid: result && result.uuid
  };
  if (result && result.success) {
   logger.debug(‘sign up result – success’);
   _render(req, res, signUpConfirmedEjs, options, language);
  } else {
   if (result.error.code === ‘GONE’) {
    logger.debug(‘sign up result – failure: ‘ + result.error.description);
    options.errorStatusCode = ‘GONE’;
    options.errorDescription = result.error.description;
    _render(req, res, signUpConfirmedEjs, options, language);
   } else if (result.error.code === ‘NOT_FOUND’) {
    logger.debug(‘sign up result – failure: ‘ + result.error.description);
    options.errorStatusCode = ‘NOT_FOUND’;
    options.errorDescription = result.error.description;
    _render(req, res, signUpConfirmedEjs, options, language);
   } else {
    logger.error(‘unexpected sign up result ‘ + result);
    res.status(500);
    res.send(‘Something went wrong’);
   }
  }
 })
});

Customizing the forgot password flow

If a user clicks on forgot password, Cloud Land calls the forgot_password API to send a reset password email to the user.

app.post(FORGOT_PASSWORD_SUBMIT, function(req, res) {
 let email = req.body && req.body.email;
 let language = req.query.language || ‘en’;
 let platform = req.params.platform;
 selfServiceManager.forgotPassword(email, language).then(function (user) {
  logger.debug(‘forgot password success’);
  if (platform === MOBILE_PLATFORM) {
   res.status(202).send(user);
  } else {
   _render(req, res, resetPasswordSentEjs,  {
    displayName: user.displayName ,
    email: user.emails[0].value,
    uuid: user.id
   }, language);
  }
 }).catch(function (err) {
  res.status(err && err.statusCode || 500);
  if (err && err.statusCode >= 400 && err.statusCode < 500) {
   logger.debug(“bad input for forgot password: ” + err.message);
   if (platform !== MOBILE_PLATFORM) {
    if (err && err.statusCode === 404) {
     return _render(req, res, forgotPasswordEjs, req.body, language, USER_NOT_FOUND);
    }
   }
   res.send(err.message);
  } else {
   logger.error(err);
   res.send(‘Something went wrong’);
  }
 });
});

When the user receives the email and clicks on the reset password button, Cloud Land receives a reset password request at the predefined on_reset_password action URL. Similar to the email verification flow, in order for Cloud Land to trust the reset password request and get the result, App ID sends a context query parameter. The context query parameter is used to get the reset password request from App ID by using the /forgot_password/confirmation_result API. Cloud Land displays the custom reset password form UI according to the reset password returned.

app.get(ON_RESET_PASSWORD, function (req, res) {
 let context = req.query.context;
 let language = req.query.language;
 selfServiceManager.getForgotPasswordConfirmationResult(context).then(function (result) {
  let uuid = result && result.uuid;
  if (result && result.success) {
   //generate one time code and pass it to the reset password form,
   // here we do that in memory but it better to use DB like Redis to do that and store it for temporary time.
   let oneTimeCode = base64url.encode(crypto.randomBytes(24));
   resetPasswordCodesMap.set(oneTimeCode, {uuid: uuid});
   logger.debug(‘rendering ‘ + resetPasswordFormEjs);
   _render(req, res, resetPasswordFormEjs, {uuid: uuid, code: oneTimeCode}, language);
  } else {
   if (result.error.code === ‘NOT_FOUND’) {
    logger.debug(‘forgot password result – failure: ‘ + result.error.description);
    _render(req, res, resetPasswordExpiredEjs, {uuid: uuid, errorStatusCode: ‘NOT_FOUND’, errorDescription: result.error.description}, language);
   } else {
    logger.error(‘unexpected forgot password result ‘ + result);
    res.status(500);
    res.send(‘Something went wrong’);
   }
  }
 });
});

 

When the user submits the reset password form, Cloud Land uses the change_password API in order to change the user password.

 

app.post(RESET_PASSWORD_SUBMIT, function(req, res) {
…
selfServiceManager.setUserNewPassword(uuid, newPassword, language).then(function (user) {
 logger.debug(‘successfully update user password’);
 resetPasswordCodesMap.delete(code);
 if (platform === MOBILE_PLATFORM) {
  res.status(200).send(user);
 } else {
  let email = user.emails[0].value;
  _render(req, res, resetPasswordSuccessEjs, {email: email}, language);
 }
})
…

 

If the user didn’t receive an email, Cloud Land lets the user request that the email be sent again, by calling the /resend/{templateName} API when the user clicks ‘resend.’

app.post(RESEND, function(req, res) {
 let uuid = req.body && req.body.uuid;
 let templateName = req.params && req.params.templateName;
 let language = req.query.language || ‘en’;
 let languageMessages = require(“./public/translations/” + language).messages;
 selfServiceManager.resendNotification(uuid, templateName, language).then(function (success) {
  res.status(200).send(languageMessages.sent);
 }).catch(function (err) {
  if (err.statusCode === 409) {
   logger.debug(err.message);
   res.status(200).send(languageMessages.confirmed);
  } else {
   logger.error(err);
   res.status(200).send(languageMessages.tryLater);
  }
 });
});

 

Customizing an Android app

As I mentioned earlier, the Cloud Land web app also servers as a back-end for the Android mobile app. However, the Android app is still a fully custom native app. The app calls the App ID ROP (Resource owner password) API by using the Android SDK to allow a user to sign-in.

 

public void onLoginClick(View v) {
    Log.d(logTag(“onLoginClick”),”Attempting identified authorization”);
    progressManager.showProgress();
    String inputEmail = ((EditText) findViewById(R.id.email)).getText().toString();
    String inputPassword = ((EditText) findViewById(R.id.password)).getText().toString();
    if (inputEmail.isEmpty() || inputPassword.isEmpty()) {
        appIdSampleAuthorizationListener.onAuthorizationFailure(new AuthorizationException(“Something didn’t work out.\nPlease try entering your email and password again.”));
    } else {
        appId.signinWithResourceOwnerPassword(getApplicationContext(), inputEmail, inputPassword, appIdSampleAuthorizationListener);
    }
}

 

For sign-up, forgot password, and resend email flows, the Android app uses the Cloud Land back-end APIs with a ‘mobile’ path parameter. This indicates to the back-end that this is a mobile request and that the response should be for a mobile client.

For this Android sample, we added the ability to know whether the app is installed on the device. This is used to determine whether to launch the installed app or a browser when a user clicks a button in an email from a mobile client. Check out how to do that here.

Try it out!

We’d love to hear from you with feedback and questions. Get help for technical questions at Stack Overflow, with the ibm-appid tag. For non technical questions, use IBM developerWorks, with the appid tag. For defect or support needs, use the support section in the IBM Cloud menu. To get started with App ID, check it out in the IBM Cloud Catalog.

More stories
September 20, 2018

Deploying to IBM Cloud Private 2.1.0.3 with IBM Cloud Developer Tools CLI

IBM Cloud Developer Tools CLI version 2.1.4 adds deployment support for IBM Cloud Private 2.1.0.3. This version of IBM Cloud Private uses a more secure Helm for Kubernetes deployments and simplifies the cluster configuration for the client compared to prior IBM Cloud Private releases.

Continue reading

September 19, 2018

Serverless Functions vs. Virtual Machines: A Total Cost of Ownership Comparison

Explore relevant costs, performance, and availability issues for a Total Cost of Ownership comparison of virtual machine and serverless functions.

Continue reading

September 19, 2018

Tutorial: Apply End-to-End Security to Cloud Applications

A new tutorial will show you how to use IBM Cloud services to secure your cloud application. Capture and review security-related events, encrypt storage, integrate authentication, and more.

Continue reading