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
November 14, 2018

PostgreSQL Tips: Template Databases

In this PostgreSQL Tip, we demystify PostgreSQL template databases and how and when you might use them. Template databases are really useful when you use the same database objects every time you create a new database.

Continue reading

November 13, 2018

How Can Banks Benefit from the Cloud and Stay Compliant?

With consumer behaviour changing, companies need to adapt fast. We see this need in all sectors, including banking. See how banks can benefit from using IBM Cloud to stay compliant in a number of ways.

Continue reading

November 9, 2018

Build Messaging Solutions with Apache Kafka or Event Streams for IBM Cloud: Part 3

One key aspect of a robust architecture is that it is built to smoothly handle system failures, outages, and configuration changes without violating the data loss and consistency requirements of the use case. To proactively build such solutions needs an understanding of the possible exceptions and risky scenarios and preparedness to manage them efficiently.

Continue reading