Building AWS Cognito Authentication Context In React.js

Building AWS Cognito Authentication Context In React.js
Building AWS Cognito Authentication Context In React.js

Authentication is a crucial aspect of modern web applications, ensuring secure access to resources and protecting user data. In this blog post, we'll explore how to integrate Amazon Cognito, a fully managed authentication service by AWS, into a React.js application to create a robust security context.

Prerequisites

Before diving into the implementation, make sure you have the following prerequisites:

  • AWS account with Cognito User Pool and App Client set up.
  • A React.js application with dependencies such as amazon-cognito-identity-js and axios.

Setting up AWS Cognito

Amazon Cognito simplifies the authentication process by handling user registration, authentication, and account recovery. To get started, create a new User Pool and App Client in the AWS Management Console.

Retrieve the UserPoolId and ClientId from the created User Pool, as these will be used in the React.js application.

Integrating Cognito in React.js

The provided React.js code encapsulates the Cognito integration in a custom AuthProvider. Let's break down the key components and functionalities:

import PropTypes from 'prop-types';
import { createContext, useCallback, useEffect, useReducer } from 'react';
import { CognitoUser, CognitoUserPool, AuthenticationDetails } from 'amazon-cognito-identity-js';
import axios from '../utils/axios';
import { PATH_AUTH } from '../routes/paths';
import { cognitoConfig } from '../config';

// ... (imports and configurations)

Imports and Configurations

  • PropTypes: A library for defining the types of props expected by React components.
  • createContext, useCallback, useEffect, useReducer: Hooks and functions provided by React for managing state and side effects.
  • CognitoUser, CognitoUserPool, AuthenticationDetails: Classes from the amazon-cognito-identity-js library for working with AWS Cognito.
  • axios: A popular library for making HTTP requests.
  • PATH_AUTH: Import of path constants for routing.
  • cognitoConfig: Configuration object containing AWS Cognito User Pool details.
export const UserPool = new CognitoUserPool({
  UserPoolId: cognitoConfig.userPoolId,
  ClientId: cognitoConfig.clientId
});

const initialState = {
  isAuthenticated: false,
  isInitialized: false,
  user: null
};

User Pool Initialization and Initial State

  • UserPool: Instantiation of a new CognitoUserPool using the configuration from cognitoConfig.
  • initialState: The initial state for the authentication context, indicating that the user is not authenticated, the initialization is not complete, and there is no user data.
const handlers = {
  AUTHENTICATE: (state, action) => {
    const { isAuthenticated, user } = action.payload;

    return {
      ...state,
      isAuthenticated,
      isInitialized: true,
      user
    };
  },
  LOGOUT: (state) => ({
    ...state,
    isAuthenticated: false,
    user: null
  })
};

Reducer and Handlers

  • handlers: An object containing functions that handle different actions dispatched to the reducer. In this case, it handles authentication (AUTHENTICATE) and logout (LOGOUT) actions.
const reducer = (state, action) => (handlers[action.type] ? handlers[action.type](state, action) : state);

Reducer Function

  • reducer: A function that takes the current state and an action, then returns the new state based on the action type. If the action type is not recognized, it returns the current state unchanged.
const AuthContext = createContext({
  ...initialState,
  method: 'cognito',
  login: () => Promise.resolve(),
  register: () => Promise.resolve(),
  logout: () => Promise.resolve()
});

AuthContext

  • AuthContext: A React context that provides the authentication state and methods to its consumers. It has an initial value containing the initialState, a method identifier ('cognito'), and placeholder functions for login, register, and logout.
AuthProvider.propTypes = {
  children: PropTypes.node
};

function AuthProvider({ children }) {
  const [state, dispatch] = useReducer(reducer, initialState);

  // ... (other functions and hooks)
}

AuthProvider Component

  • AuthProvider: A component that serves as the provider for the authentication context. It uses the useReducer hook to manage the authentication state.
// ... (other functions and hooks)

const getSession = useCallback(
  () =>
    new Promise((resolve, reject) => {
      const user = UserPool.getCurrentUser();

      // ... (session handling logic)
    }),
  [getUserAttributes]
);

const initial = useCallback(async () => {
  try {
    await getSession();
  } catch {
    dispatch({
      type: 'AUTHENTICATE',
      payload: {
        isAuthenticated: false,
        user: null
      }
    });
  }
}, [getSession]);

getSession and initial Functions

  • getSession: A function that returns a promise to retrieve the current session. It sets up the necessary headers for axios and dispatches an AUTHENTICATE action if successful.
  • initial: A function that initializes the authentication state, calling getSession and handling errors.
useEffect(() => {
  initial();
}, [initial]);

useEffect Hook

  • useEffect: A hook that runs the initial function when the component mounts. It ensures that the authentication state is properly initialized.
curl -fsSL https://bun.sh/install | bash # for macOS, Linux, and WSL

login Function

  • login: A function that takes an email and password, authenticates the user using Cognito, and resolves with the authentication data. It also handles cases where a new password is required.
const logout = () => {
  const user = UserPool.getCurrentUser();
  if (user) {
    user.signOut();
    dispatch({ type: 'LOGOUT' });
  }
};

logout Function

  • logout: A function that signs out the current user and dispatches a LOGOUT action.
const register = (email, password, firstName, lastName) =>
  new Promise((resolve, reject) =>
    UserPool.signUp(
      email,
      password,
      [
        { Name: 'email', Value: email },
        { Name: 'name', Value: `${firstName} ${lastName}` }
      ],
      null,
      async (err) => {
        if (err) {
          reject(err);
          return;
        }
        resolve();
        window.location.href = PATH_AUTH.login;
      }
    )
  );

register Function

  • register: A function that registers a new user using Cognito. It resolves when registration is successful and redirects to the login page.
const resetPassword = () => {};

// ... (other functions and hooks)

return (
  <AuthContext.Provider
    value={{
      ...state,
      method: 'cognito',
      user: {
        displayName: state?.user?.name || 'Minimals',
        role: 'admin',
        ...state.user
      },
      login,
      register,
      logout,
      resetPassword
    }}
  >
    {children}
  </AuthContext.Provider>
);

AuthContext.Provider

  • The AuthContext.Provider component wraps the application, providing the authentication context to its children.
  • It exposes the authentication state

Step 1: Set Up a New React App

If you haven't already, create a new React app using Create React App or any other preferred method:

npx create-react-app my-react-app
cd my-react-app

Step 2: Install Dependencies

Install the necessary dependencies used in the provided code:

npm install amazon-cognito-identity-js axios

Step 3: Create Configuration and Utility Files

Create configuration files for AWS Cognito and utility files as needed. The provided code assumes the existence of cognitoConfig, axios, and route-related files.

Step 4: Create a AuthProvider.js file

Copy the provided AuthProvider code into a new file, such as AuthProvider.js, and place it in a relevant location in your project.

Step 5: Integrate AuthProvider in index.js or App.js

In your index.js or App.js, import and wrap your main component with the AuthProvider. Make sure to import any necessary styles or additional setup your application might require.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { AuthProvider } from './path/to/AuthProvider';

ReactDOM.render(
  <React.StrictMode>
    <AuthProvider>
      <App />
    </AuthProvider>
  </React.StrictMode>,
  document.getElementById('root')
);

Step 6: Use Authentication Context in Components

Now, you can use the authentication context in your components. Import the AuthContext and use the useContext hook to access the authentication state and methods.

import React, { useContext } from 'react';
import { AuthContext } from './path/to/AuthProvider';

function MyComponent() {
  const { isAuthenticated, user, login, logout, register } = useContext(AuthContext);

  // Your component logic based on authentication state and user information

  return (
    <div>
      {isAuthenticated ? (
        <p>Hello, {user.displayName}!</p>
      ) : (
        <button onClick={() => login('example@email.com', 'password')}>Login</button>
      )}
    </div>
  );
}

Step 7: Run Your React App

Start your React app to see the changes:

npm start
Sign In Page
Field Validation

GitHub URL : https://github.com/kaviyarasumaran/Firebase-authContext/blob/main/AwsAuthContext.js

Conclusion

By integrating Amazon Cognito into your React.js application, you enhance security and provide a seamless authentication experience for users. The AuthProvider simplifies the management of authentication state and actions, allowing developers to focus on building feature-rich applications while ensuring robust security.

Feel free to adapt and expand upon this code to meet the specific requirements of your application. Happy coding!

Mulecraft Footer