• Home
  • AWS Cognito login with React and AWS Amplify

AWS Cognito login with React and AWS Amplify

In almost every app that I build clients wants some sort of Authorization and Authentication in their applications so today I will present you one solution that is fast to build and it is great for today’s modern apps that are build with React (or maybe Angular, Vue, Ionic etc…). When you are working alone or in a small team you don’t have time or funds to reinvent the wheel. There are many great solutions for AuthR and AuthZ which are developed and heavily tested with many clients and developers so it’s good practice to use them in your next project instead of developing new custom solution.

Also in today’s word security is very important and if your users don’t feel that their personal data is protected in your app they will not use it. That is the main reason to use some third party solutions for deal with user data and it’s critical to know what solutions are available to help you protect all of this sensitive data. The phrase identity as a service (or IDaaS) comes up repeatedly in conversations about protecting users and let me explain a little bit what IDaaS is.

Identity as a service (IDaaS) comprises cloud-based solutions for identity and access management (IAM) functions, such as single sign-on (SSO). These methods allow all users (customers, employees, and third parties) to more securely access sensitive information both on and off-premises. IDaaS also means collecting intelligence (i.e., logging events and reporting on which users accessed what information and when) to better understand, monitor, and improve their behaviors. Multi-factor authentication (MFA), including biometrics, are core components of IDaaS.

So AWS Cognito is service that offer us basic services related to identity management. Amazon Cognito User Pool makes it easy for developers to add sign-up and sign-in functionality to web and mobile applications. It serves as your own identity provider to maintain a user directory. It supports user registration and sign-in, as well as provisioning identity tokens for signed-in users. More informations you can find here https://aws.amazon.com/cognito/?nc2=h_m1

Now I will show you how to implement React App with signin and login capabilities with user pool on AWS Cognito. This topic will be more advanced so I presume that you have AWS account created (you know basics of setting things in AWS), have NODE.js and NPM installed on your machine and you know how to install basic React App (create-react-app). If you don’t know this things maybe you need to start with some tutorials that are available on the internet.

Also in my test app we will be using AWS Amplify CLI and Bootstrap for React

First go to your terminal and create basic react bootstrap app (in my case awscognitosignin is the folder where app live), and then test if everything works with npm start.

create-bootstrap-react-app awscognitosignin
cd awscognitosignin
npm start

Next we need to install React Router, and optionally Bootstrap 4

npm install --save react-router-dom
npm install --save bootstrap-4-react

Create and open src/components/Navigator.jsx and modify content. My modified example is here:

import React, { Component } from 'react';
import { Navbar, Nav, BSpan } from 'bootstrap-4-react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { Auth, Hub, Logger } from 'aws-amplify';
import { SignOut } from 'aws-amplify-react';

const HomeItems = props => (
  <React.Fragment>
    <Nav.ItemLink href="#/" active>
      Home
      <BSpan srOnly>(current}</BSpan>
    </Nav.ItemLink>
    <Nav.ItemLink href="#/login">
      Login
    </Nav.ItemLink>
  </React.Fragment>
)

const LoginItems = props => (
  <React.Fragment>
    <Nav.ItemLink href="#/">
      Home
    </Nav.ItemLink>
    <Nav.ItemLink href="#/login" active>
      Login
      <BSpan srOnly>(current}</BSpan>
    </Nav.ItemLink>
  </React.Fragment>
)

const logger = new Logger('Navigator');

export default class Navigator extends Component {
  constructor(props) {
    super(props);

    this.loadUser = this.loadUser.bind(this);

    Hub.listen('auth', this, 'navigator'); // Add this component as a listener of auth events.

    this.state = { user: null }
  }

  componentDidMount() {
    this.loadUser(); // The first check
  }

  onHubCapsule(capsule) {
    logger.info('on Auth event', capsule);
    this.loadUser(); // Triggered every time user sign in / out.
  }

  loadUser() {
    Auth.currentAuthenticatedUser()
      .then(user => this.setState({ user: user }))
      .catch(err => this.setState({ user: null }));
  }

  render() {
    const { user } = this.state;

    return (
      <Navbar expand="md" dark bg="dark" fixed="top">
        <Navbar.Brand href="#">ChrisVz Amplify Example</Navbar.Brand>
        <Navbar.Toggler target="#navbarsExampleDefault" />

        <Navbar.Collapse id="navbarsExampleDefault">
          <Navbar.Nav mr="auto">
            <HashRouter>
              <Switch>
                <Route exact path="/" component={HomeItems} />
                <Route exact path="/login" component={LoginItems} />
              </Switch>
            </HashRouter>
          </Navbar.Nav>
          <Navbar.Text mr="2">
            { user? 'Hi ' + user.username : 'Please sign in' }
          </Navbar.Text>
          { user && <SignOut /> }
        </Navbar.Collapse>
      </Navbar>
    )
  }
}

Create and then modify src/components/Main.jsx to route to Home or Login page. My modified example is here:

import React, { Component } from 'react';
import { Container } from 'bootstrap-4-react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { Auth, Hub, Logger } from 'aws-amplify';

import { Home, Login } from '../pages';

const logger = new Logger('Main');

export default class Main extends Component {
  constructor(props) {
    super(props);

    this.loadUser = this.loadUser.bind(this);

    Hub.listen('auth', this, 'main');

    this.state = { user: null }
  }

  componentDidMount() {
    this.loadUser();
  }

  onHubCapsule(capsule) {
    logger.info('on Auth event', capsule);
    this.loadUser();
  }

  loadUser() {
    Auth.currentAuthenticatedUser()
      .then(user => this.setState({ user: user }))
      .catch(err => this.setState({ user: null }));
  }

  render() {
    const { user } = this.state;

    return (
      <Container as="main" role="main">
        <div className="starter-template">
          <HashRouter>
            <Switch>
              <Route
                exact
                path="/"
                render={(props) => <Home user={user} />}
              />
              <Route
                exact
                path="/login"
                render={(props) => <Login user={user} />}
              />
            </Switch>
          </HashRouter>
        </div>
      </Container>
    )
  }
}

Create Home and Login Page. Example here:

import React, { Component } from 'react';
import { Lead, BSpan } from 'bootstrap-4-react';

export default class Home extends Component {
  render() {
    const { user } = this.props;

    return (
      <React.Fragment>
        <h1>Home</h1>
        { user && <Lead>You are signed in as  <BSpan font="italic">{user.username}</BSpan>.</Lead> }
      </React.Fragment>
    )
  }
}
import React, { Component } from 'react';
import { Lead, BSpan } from 'bootstrap-4-react';
import { Authenticator } from 'aws-amplify-react';

export default class Login extends Component {
  render() {
    const { user } = this.props;

    return (
      <React.Fragment>
        { !user && <Authenticator /> }
        { user && <Lead>You are signed in as <BSpan font="italic">{user.username}</BSpan>.</Lead> }
      </React.Fragment>
    )
  }
}

Test if everything work with

npm start

Now it’s time to install AWS amplify to our app

npm install --save aws-amplify
npm install --save aws-amplify-react

Also we need to setup AWS Services globally to our environment

npm install -g @aws-amplify/cli
amplify configure

Here you can watch in detail how to configure AWS Amplify

 

With CLI, AWS service settings are generated at src/aws-exports.js. Open src/App.js, add these lines to configure.

import Amplify from 'aws-amplify';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

You can see full App.js script here:

import React, { Component } from 'react';
import Amplify from 'aws-amplify';

import { Navigator, Main } from './components';
import './App.css';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

class App extends Component {
  render() {
    return (
      <React.Fragment>
        <Navigator />
        <Main />
      </React.Fragment>
    );
  }
}

export default App;

Open src/pages/Login.jsx, change content to:

import React, { Component } from 'react';

import { Authenticator } from 'aws-amplify-react';

export default class Login extends Component {
    render() {
        return <Authenticator />
    }
}

Also add ‘sign out’ button, right after ‘Greetings’ text in src/components/Navigator.jsx

...
import { SignOut } from 'aws-amplify-react';
...

  render() {
    ...
          <Navbar.Text>Greetings</Navbar.Text>
          <SignOut />
    ...
  }

So now we need to know how to check user state

Now we have a sign out button, but it always shows regardless user signed in or not. We need a way to be aware of user state. This means two tasks:

  • Check user state
  • Trigger the check whenever user sign in / out.

The first task is done by an Auth method currentAuthenticatedUser.

The second task we can leverage an Amplify utility, Hub. Events are dispatched to Hub for every sign in / out. We just needed to listen to the events. Check code in pages folder of my example for better understanding.

constructor(props) {
    super(props);

    this.loadUser = this.loadUser.bind(this);

    Hub.listen('auth', this, 'navigator'); // Add this component as listener of auth event.

    this.state = { user: null }
  }

  componentDidMount() {
    this.loadUser(); // The first check
  }

  loadUser() {
    Auth.currentAuthenticatedUser()
      .then(user => this.setState({ user: user }))
      .catch(err => this.setState({ user: null }));
  }

  onHubCapsule(capsule) {
    this.loadUser(); // Triggered every time user sign in / out
  }

Next updated code is added to src/components/Main.jsx, and  src/pages/Login.jsx so check my example:

import React, { Component } from 'react';
import { Container } from 'bootstrap-4-react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { Auth, Hub, Logger } from 'aws-amplify';

import { Home, Login } from '../pages';

const logger = new Logger('Main');

export default class Main extends Component {
  constructor(props) {
    super(props);

    this.loadUser = this.loadUser.bind(this);

    Hub.listen('auth', this, 'main');

    this.state = { user: null }
  }

  componentDidMount() {
    this.loadUser();
  }

  onHubCapsule(capsule) {
    logger.info('on Auth event', capsule);
    this.loadUser();
  }

  loadUser() {
    Auth.currentAuthenticatedUser()
      .then(user => this.setState({ user: user }))
      .catch(err => this.setState({ user: null }));
  }

  render() {
    const { user } = this.state;

    return (
      <Container as="main" role="main">
        <div className="starter-template">
          <HashRouter>
            <Switch>
              <Route
                exact
                path="/"
                render={(props) => <Home user={user} />}
              />
              <Route
                exact
                path="/login"
                render={(props) => <Login user={user} />}
              />
            </Switch>
          </HashRouter>
        </div>
      </Container>
    )
  }
}
import React, { Component } from 'react';
import { Navbar, Nav, BSpan } from 'bootstrap-4-react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import { Auth, Hub, Logger } from 'aws-amplify';
import { JSignOut } from './auth';

const HomeItems = props => (
  <React.Fragment>
    <Nav.ItemLink href="#/" active>
      Home
      <BSpan srOnly>(current}</BSpan>
    </Nav.ItemLink>
    <Nav.ItemLink href="#/login">
      Login
    </Nav.ItemLink>
  </React.Fragment>
)

const LoginItems = props => (
  <React.Fragment>
    <Nav.ItemLink href="#/">
      Home
    </Nav.ItemLink>
    <Nav.ItemLink href="#/login" active>
      Login
      <BSpan srOnly>(current}</BSpan>
    </Nav.ItemLink>
  </React.Fragment>
)

const logger = new Logger('Navigator');

export default class Navigator extends Component {
  constructor(props) {
    super(props);

    this.loadUser = this.loadUser.bind(this);

    Hub.listen('auth', this, 'navigator'); // Add this component as a listener of auth events.

    this.state = { user: null }
  }

  componentDidMount() {
    this.loadUser(); // The first check
  }

  onHubCapsule(capsule) {
    logger.info('on Auth event', capsule);
    this.loadUser(); // Triggered every time user sign in / out.
  }

  loadUser() {
    Auth.currentAuthenticatedUser()
      .then(user => this.setState({ user: user }))
      .catch(err => this.setState({ user: null }));
  }

  render() {
    const { user } = this.state;

    return (
      <Navbar expand="md" dark bg="dark" fixed="top">
        <Navbar.Brand href="#">ChrisVz Amplify Example</Navbar.Brand>
        <Navbar.Toggler target="#navbarsExampleDefault" />

        <Navbar.Collapse id="navbarsExampleDefault">
          <Navbar.Nav mr="auto">
            <HashRouter>
              <Switch>
                <Route exact path="/" component={HomeItems} />
                <Route exact path="/login" component={LoginItems} />
              </Switch>
            </HashRouter>
          </Navbar.Nav>
          <Navbar.Text mr="2">
            { user? 'Hi ' + user.username : 'Please sign in' }
          </Navbar.Text>
          { user && <JSignOut /> }
        </Navbar.Collapse>
      </Navbar>
    )
  }
}

This is basically it. You can test app with npm start. You can go to my Github Repositories and you can find similar examples with AWS Cognito and React.

There are many more advanced features in AWS Amplify CLI and AWS Cognito so check tutorials and examples on their official websites and I’m sure you can build better apps in future using this tools.

 

 

 

 

 

Tags: , ,

Copyright by Kristijan Klepač 2018