Secure Your Single-Page React Apps Using App ID

4 min read

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.

spa1
  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
  2. Install the IBM Cloud App ID SDK:
    npm install ibmcloud-appid-js
  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';
  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()
      }, []);
  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);
        }
      })();
  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);
      }
    };
  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>}
  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;
  9. Open your terminal. Run the following command to access your app from http://localhost:3000:
    npm start
  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.

Be the first to hear about news, product updates, and innovation from IBM Cloud