Building a Flexible Login System with BankID, Email, and Phone in React

2024-10-27

Recently, I had the privileged to be part of a team that built a login system tailored for the Swedish market that allows users to authenticate with various methods, including Sweden’s BankID, email, or a Swedish phone number. BankID is a digital identification solution widely used in Sweden to securely verify individuals for online services. This system provides flexible options for users who want to access services with their existing digital identity, whether through BankID for added security or traditional email and phone verification for convenience.

The project was originally coded using React class components, but for this blog post, I'll share how I would approach it using functional components to streamline and modernize the codebase. To create this functionality, I implemented an input validation component that debounces user input, checks if a user exists, and adjusts the form's options dynamically based on the user's registration status.

import React, { useReducer, useCallback } from 'react';

const initialState = {
    formData: {},
    isRegisteredUser: false,
    isBankIDEnabled: false,
    showError: false,
};

const reducer = (state, action) => {
    switch (action.type) {
        case 'UPDATE_FORM':
            return { ...state, formData: { ...state.formData, ...action.payload } };
        case 'SET_USER_STATUS':
            return { ...state, ...action.payload };
        case 'TOGGLE_ERROR':
            return { ...state, showError: action.payload };
        default:
            return state;
    }
};

const BankIDLogin = () => {
    const [state, dispatch] = useReducer(reducer, initialState);

    const handleInput = (value) => {
        dispatch({ type: 'UPDATE_FORM', payload: { emailOrPhone: value } });
        checkUserExists(value);
    };

    const checkUserExists = useCallback((value) => {
        clearTimeout(state.timeoutId);
        const newTimeout = setTimeout(async () => {
            try {
                const resp = await userExists(value);
                if (!resp.ok) throw new Error();
                const { exists, bankidEnabled } = await resp.json();
                dispatch({ type: 'SET_USER_STATUS', payload: { isRegisteredUser: exists, isBankIDEnabled: bankidEnabled } });
            } catch {
                dispatch({ type: 'TOGGLE_ERROR', payload: true });
            }
        }, 200);
        dispatch({ type: 'SET_TIMEOUT', payload: newTimeout });
    }, [state.timeoutId]);

What the code does:

A. Initial State and Reducer Function: The component uses initialState to define the starting values for form data and flags that track the user’s registration status, BankID availability, and any errors.

The reducer function handles different actions to update the component’s state. These actions include updating form data, setting the user’s registration status, and toggling the error state.

B. handleInput Function: This function is triggered whenever the user types into the input field.

It updates formData in the state with the user's current input, which could be an email or a phone number. It then calls the checkUserExists function to verify if the user exists and if BankID login is enabled.

C. checkUserExists Function: This function uses debouncing to avoid making an API call every time the user types; it waits 200ms after the user stops typing before proceeding.

It calls an asynchronous function, userExists(value), to check if a user with the given email or phone number is already registered. This function also checks if the BankID option is available. The result of this API call updates the isRegisteredUser and isBankIDEnabled flags in the state. If there’s an error (e.g., network issue), it sets showError to true, prompting the component to display an error message.

Challenges:

Since this is a large-scale project with diverse requirements, one of the challenges that I faced is breaking down the project into User Stories. Below are some of the User Stories that I have written down:

User Story 1: As a user, I want to log in with my phone number so that I can quickly access my account without having to remember an email address.

User Story 2: As a user, I want to be able to log in with my BankID for a secure and reliable login experience.

User Story 3: As a returning user with multiple registered phone numbers, I want a way to easily confirm which account to access, so that I can avoid confusion and enter the correct details.

Conclusion:

In conclusion, despite the tight deadline and the complexity of managing multiple login flows, our team successfully refactored the code and launched the login feature on schedule. This new system provides users with seamless access to their accounts using BankID, email, or phone, which significantly improves the overall user experience by giving them secure and flexible login options.

The streamlined login process is a major benefit to the company, as it makes onboarding much smoother and helps reduce friction for both new and returning users. The added flexibility is especially valuable in the Swedish market, where BankID is a trusted and widely-used authentication method. By implementing a clear, user-friendly, and efficient login flow, my contributions have not only enhanced user satisfaction but also strengthened the foundation for further growth, allowing more users to log in quickly and securely.


Also on codewithkatyrosli.com

Creating Responsive and Customizable Components in Puck Editor, 2024-09-29

Today I would like to share my experience working part of the team integrating Puck Editor, a powerful tool that allows editors to build and create web pages. I’ve contributed to developing several key components aimed at enhancing both user experience and business goals. One of the components I created is the CustomerTestimonials section, which allows website visitors to easily read customer feedback. This component has been essential in building trust and credibility for businesses, as testimonials serve as a powerful tool to showcase social proof.

Read More

This website uses cookies to enchance the user experience.