WHAT YOU’LL LEARN
  • what are the initial steps we need to perform, before integrating the Hosted UI authentication flow into our React application
  • how to perform all of the initial steps
Can I use this?

In order to follow this tutorial, you must use Webiny version 5.18.0 or greater.

The code that we cover in this section can also be found in our GitHub examples repository . Also, if you’d like to see the complete and final code of the application we’re building, check out the full-example folder.

Overview
anchor

With all of the necessary new cloud infrastructure resources deployed and our webiny.config.ts configuration file updated, we’re now finally ready to start integrating the Hosted UI authentication flow into our React application. But, before we do that, we need to perform a couple of initial setup steps.

First of all, we’ll add two new libraries that will make it a bit easier to work with the current user within our React application code. Once that’s in place, we will perform the following:

  • create the Authenticator React component, which will be responsible for performing the initial authentication process (every time a user starts using our React application)
  • modify our App entrypoint React component, by rendering the SecurityProvider and the just created Authenticator component
  • register a new Apollo Link plugin, which will ensure user’s ID token (JWT) is attached on every issued GraphQL HTTP request

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

Check out this Introduction to JSON Web Tokens article to learn more.

Let’s see how we can achieve all of these.

1. Adding the Needed Libraries
anchor

Let’s start by adding a couple of new libraries into the mix.

Placeholder
anchor

@webiny/app-security
anchor

Via the useSecurity React hook, this library will make it easy to retrieve the currently signed in user in any React component we might have.

@aws-amplify/auth
anchor

This library exposes the Auth class which will enable us to perform essential authentication-related operations against Amazon Cognito’s HTTP API, all with a couple of public class methods. And although the library is a part of the AWS Amplify framework, note that we’re only adding the authentication segment of it. We don’t need the rest.

The following command will add both libraries to our React application:

yarn workspace pinterest-clone-app add @webiny/app-security @aws-amplify/auth

2. Create the Authenticator React Component
anchor

The Authenticator React component will be a small React component that will consist of a single useEffect React hook, in which we’ll perform the following.

First, via the mentioned Auth class and its currentSession method, we’ll try to retrieve the currently signed in user. If successful, then, via the useSecurity React hook and the setIdentity function that it exposes, we’ll simply set the user information into the hook’s internal state. As we’ll soon see, this is what will enable us to easily retrieve the currently signed in user in any React component that we might have.

We will define the Authenticator React component in the pinterest-clone/app/code/src/components folder, in the new Authenticator.tsx file:

pinterest-clone/app/code/src/components/Authenticator.tsx
import React, { useEffect } from "react";
import Auth from "@aws-amplify/auth";
import { useSecurity } from "@webiny/app-security";

// Apart from the React component, we also configure the Auth class here.
Auth.configure({
    region: process.env.REACT_APP_USER_POOL_REGION,
    userPoolId: process.env.REACT_APP_USER_POOL_ID,
    userPoolWebClientId: process.env.REACT_APP_USER_POOL_WEB_CLIENT_ID,
    oauth: {
        domain: process.env.REACT_APP_USER_POOL_DOMAIN,
        redirectSignIn: `${location.origin}?signIn`,
        redirectSignOut: `${location.origin}?signOut`,
        responseType: "token"
    }
});

// The `Authenticator` component.
const Authenticator: React.FC = props => {
    const { setIdentity } = useSecurity();

    useEffect(() => {
        // Get the currently signed in user.
        Auth.currentSession()
            .then(response => {
                const user = response.getIdToken().payload;
                setIdentity({
                    id: user.email,
                    type: "user",
                    displayName: user.given_name + " " + user.family_name,
                    permissions: [],
                    logout: () => {
                        Auth.signOut();
                        setIdentity(null);
                    }
                });
            })
            .catch(() => {
                /* Do nothing. */
            });
    }, []);

    return <>{props.children}</>;
};

export default Authenticator;

Notice how we’re also configuring the Auth class, by passing all of the environment variables that we’ve defined in the previous section of this tutorial. We decided to do this here simply because this way we have all of the relevant code in one place, within a single file.

3. Modify the App React component
anchor

Next, let’s modify the App entrypoint React component, located in the pinterest-clone/app/code/src folder.

Essentially, what we’ll be doing here is wrapping all of the existing React components with the newly created Authenticator and the SecurityProvider components. The latter one comes from the @webiny/app-security library and is necessary for the shown useSecurity hook to actually work.

Ultimately, this is how our App.tsx file should look like:

pinterest-clone/app/code/src/App.tsx
import React from "react";import { ApolloProvider } from "@apollo/react-components";import { Routes } from "@webiny/app/components/Routes";import { BrowserRouter } from "@webiny/react-router";import { SecurityProvider } from "@webiny/app-security";import Authenticator from "./components/Authenticator";import { createApolloClient } from "./apollo";
// An entrypoint for all SCSS styles your application might have.import "./App.scss";import "antd/dist/antd.css";
// The beginning of our React application, where we mount a couple of useful providers.// If needed, feel free to add new or modify existing providers.export const App = () => (  <>      {/*          <SecurityProvider> is a generic provider of identity information. 3rd party identity providers (like Cognito,          Okta, Auth0) will handle the authentication, and set the information about the user into this provider,          so other parts of the system have a centralized place to fetch user information from.      */}      <SecurityProvider>          <Authenticator>              {/* Sets up a new Apollo GraphQL client, pointed to an existing GraphQL API. */}              <ApolloProvider                  client={createApolloClient({ uri: process.env.REACT_APP_GRAPHQL_API_URL })}              >                  {/* Enables routing in our application. */}                  <BrowserRouter basename={process.env.PUBLIC_URL}>                      <Routes />                  </BrowserRouter>              </ApolloProvider>          </Authenticator>      </SecurityProvider>  </>);

4. Create the Apollo Link Plugin
anchor

The last piece of the puzzle is the creation of a new ApolloLinkPlugin plugin, which will simply ensure that the user’s ID token (JWT) token is attached to every HTTP request issued by the Apollo GraphQL (v2) client. This will then ensure that the user is also successfully authenticated on the GraphQL API side (more on this soon).

We can define the new plugin within the existing pinterest-clone/app/code/src/plugins/apollo.ts file:

pinterest-clone/app/code/src/plugins/apollo.ts
import { ConsoleLinkPlugin } from "@webiny/app/plugins/ConsoleLinkPlugin";import { NetworkErrorLinkPlugin } from "@webiny/app/plugins/NetworkErrorLinkPlugin";import { OmitTypenameLinkPlugin } from "@webiny/app/plugins/OmitTypenameLinkPlugin";import { ApolloLinkPlugin } from "@webiny/app/plugins/ApolloLinkPlugin";import { setContext } from "apollo-link-context";import { Auth } from "@aws-amplify/auth";
export default [// This link removes `__typename` from the variables being sent to the API.new OmitTypenameLinkPlugin(),
// This link checks for presence of `extensions.console` in the response and logs all items to browser console.new ConsoleLinkPlugin(),
// This plugin creates an ApolloLink that checks for `NetworkError` and shows an ErrorOverlay in the browser.new NetworkErrorLinkPlugin(),
 new ApolloLinkPlugin(() => {   return setContext(async (_, { headers }) => {     try {       const user = await Auth.currentSession();       return {        headers: {          ...headers,          Authorization: user.getIdToken().getJwtToken(),        },      };    } catch (error) {      return { headers };    }  });}),];

As we can see, on each HTTP request, we’re again calling the currentSession method of the Auth class to retrieve the currently signed in user’s information, from which we’re retrieving her/his ID token (JWT form of it) and assigning it as the value of the Authorization header.

Final Result
anchor

With the new Authenticator React component and the Apollo Link plugin defined, and the modified App entrypoint React component, we’re now ready to perform the final steps and finally see the Hosted UI authentication flow in action.