- 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
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
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 theSecurityProvider
and the just createdAuthenticator
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
Let’s start by adding a couple of new libraries into the mix.
Placeholder
@webiny/app-security
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
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
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:
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
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:
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
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:
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
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.