Configuring a single-page application (SPA) to work with App ID.

New to IBM Cloud App ID? Welcome!

With App ID, you can easily add authentication, authorization, and user profile services to web apps, mobile apps, and APIs with minimal to zero code changes, without redeploying your app. By using App ID, you no longer have to worry about setting up infrastructure, ensuring geo-availability, or trying to understand complex compliance regulations when it comes to managing user identity. 

With the SDKs, you can get a sign-in flow going in minutes with Facebook, SAML 2.0, and more. You can also choose to use App ID’s scalable user registry (called Cloud Directory) to let your users manage their own account. Additionally, the App ID user profiles feature can be used to store information about your users—such as their preferences—to make their experience of your app personalized and, most importantly, secure.

In this blog, we’re going to walk you through configuring a single-page application (SPA) to work with App ID. With the client-side JavaScript SDK, you can easily secure your Angular, React, or other frontend framework. If your application has a backend that you manage or needs to be refreshed to load content, try the web app flow instead.

Before getting started, we should note that the App ID JavaScript SDK is one of very few on the market that uses the Authorization Code with Proof Key for Code Exchange (PKCE) flow instead of the implicit flow to secure your SPAs. Although it is currently the industry standard, the OAuth working group no longer recommends using the implicit flow due to several security concerns. For more information about why we chose PKCE over implicit, see the docs.

Understanding the flow

The figure below shows how to securely authenticate and authorize single-page application (SPA) users with the Authorization Code + PKCE flow.


  1. A user attempts to log in to your single-page application.
  2. The App ID SDK creates a code verifier for the authorization request, which is the plain text version of the code challenge. 
  3. Along with the authorization request, the client sends the code challenge and the challenge method that is used to encode the challenge.
  4. The authentication flow is started by App ID in a new window.
  5. The user chooses an identity provider to authenticate with and completes the sign-in process.
  6. The App ID SDK on the application receives the grant code.
  7. The SDK then makes an XHR request to the App ID token endpoint along with the grant code and the code verifier.
  8.  App ID returns access and identity tokens.

We’re going to walk you through the steps that you can take to use the App ID JavaScript SDK to secure a single-page application. The sample app that we’re using is based on React, which is a JavaScript library that you can utilize to build user interfaces by using components to efficiently update and render a UI.

Prerequisites

Before you get started with your React SPA, be sure that you have the following prerequisites:

Creating your app

  1. Set up a frontend build pipelines using create-react-app. Then, move into the project directory:
    npx create-react-app my-app
    cd my-app
    Scroll to view full table
  2. Install the IBM Cloud App ID SDK:
    npm install ibmcloud-appid-js
    Scroll to view full table
  3. In your code editor in the src directory of the application, open the App.js file. Import App ID by adding the following code:
    import AppID from 'ibmcloud-appid-js';
    Scroll to view full table
  4. In the main App() function, declare a new App ID instance with useMemo, which recomputes a memorized value when a dependency changes:
    const appID = React.useMemo(() => {
        return new AppID()
      }, []);
    Scroll to view full table
  5. Initialize App ID and add error handling. Add your client ID and discovery endpoint, which can be found in the Applications tab of the App ID dashboard:
    const [errorState, setErrorState] = React.useState(false);
    const [errorMessage, setErrorMessage] = React.useState('');
    (async () => {
        try {
          await appID.init({
            clientId: '<SPA_CLIENT_ID>',
            discoveryEndpoint: '<WELL_KNOWN_ENDPOINT>'
          });
        } catch (e) {
          setErrorState(true);
          setErrorMessage(e.message);
        }
      })();
    Scroll to view full table
  6. Create a login action that will execute when the login button is clicked. After a successful authentication, the welcomeDisplayState will be set to true and the userName will be set to the name returned in the App ID token:
    const [welcomeDisplayState, setWelcomeDisplayState] = React.useState(false);
    const [loginButtonDisplayState, setLoginButtonDisplayState] = React.useState(true);
    const [userName, setUserName] = React.useState('');
     
     
    const loginAction = async () => {
      try {
        const tokens = await appID.signin();
        setErrorState(false);
        setLoginButtonDisplayState(false);
        setWelcomeDisplayState(true);
        setUserName(tokens.idTokenPayload.name);
      } catch (e) {
        setErrorState(true);
        setErrorMessage(e.message);
      }
    };
    Scroll to view full table
  7. Add a welcome div, the login button that calls the login action, and an error div:
    {welcomeDisplayState && <div> Welcome {userName}! You are now authenticated.</div>}
    {loginButtonDisplayState && 
       <button style={{fontSize: '24px', backgroundColor: 'skyblue', border: 'none', cursor: 'pointer'}} id='login' onClick={loginAction}>Login</button>}
    {errorState && <div style={{color: 'red'}}>{errorMessage}</div>}
    Scroll to view full table
  8. Save all of the files. Your entire App.js file should look like this:
    import React from 'react';
    import logo from './logo.svg';
    import './App.css';
    import AppID from 'ibmcloud-appid-js';
    function App() {
      const appID = React.useMemo(() => {
        return new AppID()
      }, []);
      const [errorState, setErrorState] = React.useState(false);
      const [errorMessage, setErrorMessage] = React.useState('');
      (async () => {
        try {
          await appID.init({
            clientId: '<SPA_CLIENT_ID>',
            discoveryEndpoint: '<WELL_KNOWN_ENDPOINT>'
          });
        } catch (e) {
          setErrorState(true);
          setErrorMessage(e.message);
        }
      })();
      const [welcomeDisplayState, setWelcomeDisplayState] = React.useState(false);
      const [loginButtonDisplayState, setLoginButtonDisplayState] = React.useState(true);
      const [userName, setUserName] = React.useState('');
      const loginAction = async () => {
        try {
          const tokens = await appID.signin();
          setErrorState(false);
          setLoginButtonDisplayState(false);
          setWelcomeDisplayState(true);
          setUserName(tokens.idTokenPayload.name);
        } catch (e) {
          setErrorState(true);
          setErrorMessage(e.message);
        }
      };
      return (
        <div className='App'>
        <header className='App-header'>
          <img src={logo} className='App-logo' alt='logo' />
            {welcomeDisplayState && <div> Welcome {userName}! You are now authenticated.</div>}
            {loginButtonDisplayState && <button style={{fontSize: '24px', backgroundColor: 'skyblue',
              border: 'none', cursor: 'pointer'}} id='login' onClick={loginAction}>Login</button>}
            {errorState && <div style={{color: 'red'}}>{errorMessage}</div>}
        </header>
        </div>
      );
    }
    export default App;
    Scroll to view full table
  9. Open your terminal. Run the following command to access your app from http://localhost:3000:
    npm start
    Scroll to view full table
  10. Make sure you register your redirect_uri (in this case http://localhost:3000/*) with App ID to ensure that only authorized clients are allowed to participate in the authorization workflow. This can be done on the App ID dashboard under the Manage Authentication tab in the Authentication Settings. See the docs for more details.

Well done! You successfully integrated IBM Cloud App ID’s SDK for SPA into a React application.

Note: You can view the code repository for our React sample app here.

Tips and things to note

  • You can use the App ID client SDK to automatically obtain a new pair of tokens without requiring that the user explicitly sign in. For more information, see Silent login.
  • To learn more about security vulnerabilities with the implicit grant type and authorization code flow, visit OAuth 2.0 Security Best Current Practice.
  • For more information about how PKCE and the authorization code flow work together, see the spec.

Questions and feedback

  • If you have technical questions about App ID, post your question on Stack Overflow and tag your question with ibm-appid.
  • For questions about the service and getting started instructions, use the IBM Developer Answers forum. Include the appid.
  • Open a support ticket in the IBM Cloud menu.
  • Reach out directly to the development team on Slack!

To get started with App ID, check it out in the IBM Cloud Catalog.

More from Cloud

Strengthening cybersecurity in life sciences with IBM and AWS

7 min read - Cloud is transforming the way life sciences organizations are doing business. Cloud computing offers the potential to redefine and personalize customer relationships, transform and optimize operations, improve governance and transparency, and expand business agility and capability. Leading life science companies are leveraging cloud for innovation around operational, revenue and business models. According to a report on mapping the cloud maturity curve from the EIU, 48% of industry executives said cloud has improved data access, analysis and utilization, 45% say cloud…

7 min read

Kubernetes version 1.27 now available in IBM Cloud Kubernetes Service

< 1 min read - We are excited to announce the availability of Kubernetes version 1.27 for your clusters that are running in IBM Cloud Kubernetes Service. This is our 22nd release of Kubernetes. With our Kubernetes service, you can easily upgrade your clusters without the need for deep Kubernetes knowledge. When you deploy new clusters, the default Kubernetes version remains 1.25 (soon to be 1.26); you can also choose to immediately deploy version 1.27. Learn more about deploying clusters here. Kubernetes version 1.27 In…

< 1 min read

Redefining the consumer experience: Diageo partners with SAP and IBM on global digital transformation

3 min read - In an era of evolving consumer preferences and economic uncertainties, the beverage industry stands as a vibrant reflection of changing trends and shifting priorities. Despite the challenges posed by inflation and the cost-of-living crisis, a dichotomy has emerged in consumer behavior, where individuals untouched by the crisis continue to indulge in their favorite beverages, while those directly affected pivot towards more affordable luxuries, such as a bottle of something special. This intriguing juxtaposition highlights the resilient nature of consumers and…

3 min read

IBM Cloud releases 2023 IBM Cloud for Financial Services Agreed-Upon Procedures (AUP) Report

2 min read - IBM Cloud completed its 2023 independent review of IBM Cloud services and processes. The review report demonstrates to its clients, partners and other interested parties that IBM Cloud services have implemented and adhere to the technical, administrative and physical control requirements of IBM Cloud Framework for Financial Services. What is the IBM Cloud Framework for Financial Services? IBM Cloud for Financial Services® is designed to build trust and enable a transparent public cloud ecosystem with features for security, compliance and…

2 min read