Building a Frictionless Authentication Flow

Dan Lages
Just Eat Takeaway-tech
5 min readJun 15, 2023

Account Takeover (ATO) is a reality that all platforms and their users must reckon with. It describes a scenario in which a bad actor will gain unauthorised access to a user’s account. This, of course, has negative consequences for the victim of such an attack, allowing the perpetrator to order goods or even sell personal information.

There are many steps a user can take to help mitigate such risks against their account, such as avoiding the re-use of passwords. However, through implementing a set of secure features on the Just Eat Takeaway platform, we can protect our users against this challenge.

One of these features is Multi-Factor Authentication (MFA), which provides an extra authentication step, protecting our customers and adding another layer of security for their accounts. Although not a new concept, we have worked hard to implement a flow that does not introduce friction. Here, we’ll be talking through how this new MFA flow was implemented in the iOS application, creating a secure but intuitive user experience.

At the heart of this feature is a new screen that prompts the user to enter a single-use code in order to complete their login journey.

  1. Enter your email address and password
  2. Get prompted to check your email
  3. Enter the access code into the challenge screen
  4. Place your order!
Images showcasing the MFA flow on the iOS application.

Challenging only when required

Despite its clear benefits, implementing multi-factor authentication adds an extra hurdle for the user. With this in mind, our services adopt a machine learning tool that evaluates the risk factor associated with an account and will inform on whether to challenge a user, triggering multi-factor authentication only when required.

This reduces friction immediately for those that don’t require an MFA challenge. For those that do, we have implemented a smooth flow to combat the extra step.

A flow diagram demonstrating the MFA flow. If the user is challenged by the ATO service, the user will be sent an email with a one time access code and a new screen in which to enter it. If not the user is not challenged, the user is authenticated straight away.

A design that limits friction

The key factor in this flow is adding the minimum number of steps required to protect the user. The challenge is doing so in a clear and descriptive way. Therefore, the Multi-factor authentication flow consists of two screens.

  • The Code Entry Screen: Here, the user can see a brief description and enter the one-time access code.
  • The Help Screen: Many users may have further questions on the flow Here we provide a more detailed summary of the reasons for MFA and guidance on troubleshooting.

The help screen may not be needed by most of the users who encounter the MFA flow. This is by design. Many users who encounter the MFA flow will be familiar with the process from other apps. By moving the further details into an easily accessible separate screen, we make it easier to quickly complete the flow but offer guidance at just a tap away. This was a carefully considered aspect of the design process across all platforms.

For those that need it, the help screen provides key information on how to grab their one-time access code and proceed. We are conscious that the notion of navigating to a separate email application may not be intuitive. As such, we have introduced a number of helpful points. For example, we highlight that the email may be sent to the spam folder, or take a few minutes to arrive. These are key points that all aid in helping the user through this process.

Embedded links

The help screen also provides a link to the FAQ page, which offers further information on the flow. Besides adopting SFSafariViewController here, keeping the user in the app as to not disrupt the flow, we also use an embedded link.

To do so within the iOS application, a new class was implemented, allowing us to implement and manipulate bullet point lists. At the heart of this class was a simple ‘for each’ loop, taking in an array of text to be replaced with links.

let formattatedString: String // The value of this string is the bullet point list
let links: [String] // The text within the main string to be formatted into links

links.forEach { linkText in
if let rangeLinkText = formattedString.range(of: linkText),
let url = URL(string: linkText) {
let nsLinkText = NSRange(rangeLinkText, in: formattatedString)

formattedString.link(url: url, range: nsLinkText)
// Attribute style to the links here
}

The .link method consists of the following:

links.append(.init(style: url, range: range))

Another function within the class serves to set all linked text as interactable elements using the textView(_:shouldInteractWith) instance method.

This code requires the link and associated text to be parsed in order. As such, the class accepts these as initialization properties. This implementation allows the links to be modified and added too with a simple code change. If more links were required in the future, we could easily add to the respective arrays. Furthermore, the abstract nature of the class allows it to be used throughout the application.

Guiding user interaction

The key interactable element of the screen is the one-time access code text field. The ability for the user to proceed past this verification step is the correct entry of this code. There are many considerations to factor in when designing input limitations of such a code. As such, the application handles many edge cases that may occur:

  • Formatting errors when copying and pasting a code
  • Unexpected whitespace introduced by different email clients when rendering the code
  • Changes to the format of the code, such as capitalisation style.
  • Entering too many characters

As you can see, there are various challenges that had to be overcome when implementing this text field. In order to do so, we introduced a max length, we only permit capital letters, and we remove whitespace. These improvements ensure that what is entered is correct, serving as guidance.

The use of the textField(_:shouldChangeCharactersIn:) instance method allows us to adjust the entered string as the user types it. It works seamlessly, adopting modifiers and updating the value in real time.

    let filteredOneTimePasscode = passcode
.replacingCharacters(in: stringRange, with: string)
.replacingOccurrences(of: " ", with: "")
.prefix(maxLength)

textField.text = String(filteredOneTimePasscode).uppercased()
.trimmingCharacters(in: .whitespaces)
viewModel.passcode.value = textField.text

A sum of parts

As demonstrated, there were a lot of product considerations at the core of developing this flow, ensuring that the user is assisted at every step of the MFA process. The adoption of Swift instance methods and the implementation of a reusable class for the bullet point list allowed us to build a frictionless authentication flow.

--

--