DEV Community

Cover image for How to Easily Customize The AWS Amplify Authentication UI
Kyle Galbraith
Kyle Galbraith

Posted on • Originally published at blog.kylegalbraith.com

How to Easily Customize The AWS Amplify Authentication UI

For parler.io I have been experimenting with adding authentication to the project. This would allow conversions to be tied to users and enable a slew of other features as well.

During my experimenting I have been reading a lot about AWS Amplify. It is a library that wraps multiple AWS services and allows you to focus on building mobile and web applications at scale on Amazon Web Services.

It makes adding various categories of features much simpler. Need authentication? There is a module for that. What about storage? Yup, there is one for that as well.

Amplify is meant to make stitching together AWS services a seamless process. A simple command line call can provide all of the services you need in your AWS account to handle authentication.

The Amplify Framework makes creating scalable mobile and web applications in AWS a simplified process. In this post, I am going to walk through how I used AWS Amplify to add authentication to Parler and how I customized the user interface components to fit my needs.

Getting Started

Amplify is an AWS provided framework. To get started we must install and configure the CLI for Amplify.

$ npm install -g @aws-amplify/cli
Enter fullscreen mode Exit fullscreen mode

If you don't have the AWS CLI installed and configured you are going to need to configure the Amplify CLI. If you already have the AWS CLI configured you don't need to configure the Amplify one as well.

# only run this configure if you don't have the AWS CLI
$ amplify configure
Enter fullscreen mode Exit fullscreen mode

Once the Amplify CLI is installed we can begin adding modules to our mobile or web application.

For my project, I am using Gatsby to build out the web application. This is a modern static site generator that can be used to quickly create static websites, blogs, portfolios, and even web applications. Since Gatsby is built on top of React we can use all of the same ideas from React in Gatsby.

Let's initialize and configure our initial Amplify setup for a React web application.

Initializing Amplify

Now that we have the CLI installed globally we can initialize Amplify inside of our React app with one command line call.

# run this from the root directory of your application
$ amplify init
Enter fullscreen mode Exit fullscreen mode

This command will initialize our AWS configuration and create a configuration file at the root of our application. This command will not provision any services in our AWS account, but it lays the groundwork for us to do so.

Adding authentication to our application

Now that we have initialized the framework in our application we can start adding modules. For this blog post, we are going to add the authentication module to our application.

We can do this with another call on our command line.

$ amplify add auth
Enter fullscreen mode Exit fullscreen mode

This command will walk us through a series of questions. Each question is configuring the authentication for our application. If you are unsure what configuration you need, go ahead and select Yes, use the default configuration for the first question. You can always come back and reconfigure these settings by running the command amplify update auth.

We now have the authentication module configured for our application. But, we still need to deploy this configuration to our AWS account. Lucky for us, this is handled by the Amplify CLI as well.

$ amplify push
Enter fullscreen mode Exit fullscreen mode

This will create and deploy the necessary changes to our AWS account to support our authentication module. With the default settings, this will provision AWS Cognito to handle authentication into our application.

When the deployment is complete we will have a new file in our source directory, aws-exports.js. This file represents the infrastructure inside of our AWS account to support our Amplify project.

Using Amplify with React

The Amplify framework has been added, we configured authentication, and we provisioned the necessary AWS services to support our application. Now it's time we set up our React/Gatsby application to leverage the framework.

For the purpose of this blog post, we are going to assume we have an App component that is the main entry point for our application. We are also going to assume that you can't access the application without being authenticated first.

Here is what our initial App component is going to look like. It is served at the /app route via a Gatsby configuration. Right now it is wide open to the world, no authentication is needed.

import React from "react";

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    return (
      <div>
        <h1>Internal App</h1>
      </div>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

With me so far? Great. Now we want to put our application behind the authentication module we added via Amplify. To do that we install two more libraries in our project.

$ npm install aws-amplify aws-amplify-react
Enter fullscreen mode Exit fullscreen mode

Now that we have added these two libraries we can quickly add authentication to our application. First, we need to configure Amplify inside our App component. Then we can use a higher order component (HOC), withAuthenticator, specifically created for React applications. This component adds all of the logic to put our App component behind authentication. It also includes all of the UI pieces we need to log users in, sign up new users, and handle flows like confirming an account and resetting a password.

Let's take a look at what these changes look like in our App component.


import React from "react";
import Amplify from "aws-amplify";
import { withAuthenticator } from "aws-amplify-react";
import config from "../../aws-exports";
Amplify.configure(config);

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    return (
      <div>
        <h1>Internal App</h1>
      </div>
    );
  }
}

export default withAuthenticator(App, true);
Enter fullscreen mode Exit fullscreen mode

Just like that we now have authentication added to our React application that is built with Gatsby. If we run gatsby develop from our command line and check out our changes locally we should be able to see the default login prompt provided by Amplify.

default Amplify UI

Pretty slick right? With a few command line operations, we have authentication incorporated into our application. All of the AWS services needed to support our app are provisioned and continuously maintained by the Amplify Framework.

This is all fantastic, but for Parler, I also wanted the ability to customize the UI pieces that Amplify provides. These pre-configured UI components are great for getting started but I wanted to add my own style to them using Tailwind CSS.

So now let's explore how to customize the authentication UI of Amplify by overriding the default components like SignIn with our own CustomSignIn component.

Customizing the Amplify authentication UI

To customize the look and feel of the Amplify authentication module we need to define our own components for the UI pieces we want to change.

For example, the login UI is handled by a component inside of Amplify called SignIn, you can see the full source code of that module here.

What we are going to do next is define our own component, CustomSignIn, that is going to extend the SignIn component from Amplify. This allows us to use all of the logic already built into the parent component but define our own UI. Let's take a look at what CustomSignIn looks like.

import React from "react";
import { SignIn } from "aws-amplify-react";

export class CustomSignIn extends SignIn {
  constructor(props) {
    super(props);
    this._validAuthStates = ["signIn", "signedOut", "signedUp"];
  }

  showComponent(theme) {
    return (
      <div className="mx-auto w-full max-w-xs">
        <form className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
          <div className="mb-4">
            <label
              className="block text-grey-darker text-sm font-bold mb-2"
              htmlFor="username"
            >
              Username
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker leading-tight focus:outline-none focus:shadow-outline"
              id="username"
              key="username"
              name="username"
              onChange={this.handleInputChange}
              type="text"
              placeholder="Username"
            />
          </div>
          <div className="mb-6">
            <label
              className="block text-grey-darker text-sm font-bold mb-2"
              htmlFor="password"
            >
              Password
            </label>
            <input
              className="shadow appearance-none border rounded w-full py-2 px-3 text-grey-darker mb-3 leading-tight focus:outline-none focus:shadow-outline"
              id="password"
              key="password"
              name="password"
              onChange={this.handleInputChange}
              type="password"
              placeholder="******************"
            />
            <p className="text-grey-dark text-xs">
              Forgot your password?{" "}
              <a
                className="text-indigo cursor-pointer hover:text-indigo-darker"
                onClick={() => super.changeState("forgotPassword")}
              >
                Reset Password
              </a>
            </p>
          </div>
          <div className="flex items-center justify-between">
            <button
              className="bg-blue hover:bg-blue-dark text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
              type="button"
              onClick={() => super.signIn()}
            >
              Login
            </button>
            <p className="text-grey-dark text-xs">
              No Account?{" "}
              <a
                className="text-indigo cursor-pointer hover:text-indigo-darker"
                onClick={() => super.changeState("signUp")}
              >
                Create account
              </a>
            </p>
          </div>
        </form>
      </div>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

With CustomSignIn we are extending the SignIn component from aws-amplify-react. This is so that we can override the showComponent method but still use the parent class functions like changeState and signIn.

Notice that we are not overriding the render method but showComponent instead. This is because the parent SignIn component defines the UI inside of that function. Therefore, to show our UI we need to override it in our component.

Inside of our constructor we see the following statement.

this._validAuthStates = ["signIn", "signedOut", "signedUp"];
Enter fullscreen mode Exit fullscreen mode

Amplify uses authState to track which authentication state is currently active. The custom components we define can state which auth states are valid for this component. Since we are on the login/sign in view, we only want to render our custom UI if authState equals signIn, signedOut, or signedUp. That is all of the magic sauce happening to show our UI over the default Amplify provided UI.

We extend the SignIn component, override the showComponent function, check the authState and show our UI if the state is the one that we are looking for.

Pretty slick right?

Diving into the custom UI a bit we see the "Create Account" button makes a call to super.changeState("signUp") when its clicked. This is a function defined in the parent component we are extending. It updates the authState to signUp and the SignUp component is rendered. We could, of course, customize this component as well following the same process we used to create CustomSignIn.

The only other change we need to make now is back out in our App component. Instead of using the withAuthenticator HOC provided by Amplify we are going to use the Authenticator component directly.

To make things clearer we are going to define a new component, AppWithAuth, that wraps our App component and makes use of the Authenticator component directly.

import React from "react";
import { SignIn } from "aws-amplify-react";
import config from "../../aws-exports";
import { CustomSignIn } from "../Login";
import App from "../App";
import { Authenticator } from "aws-amplify-react/dist/Auth";

class AppWithAuth extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    return (
      <div>
        <Authenticator hide={[SignIn]} amplifyConfig={config}>
          <CustomSignIn />
          <App />
        </Authenticator>
      </div>
    );
  }
}

export default AppWithAuth;
Enter fullscreen mode Exit fullscreen mode

Now our App component will receive the authState, just like our other components, inside of its render method. If we check the state inside of that method we can show our App component only when we are signed in. Let's take a look at our new App component code.

import React from "react";

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  render() {
    if (this.props.authState == "signedIn") {
      return (
        <div>
          <h1>Internal App</h1>
        </div>
      );
    } else {
      return null;
    }
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Now our App component is very minimal. In fact, the only notion we have of Amplify here is checking our authState which determines whether or not we should render this component.

parler.io authentication with Amplify

Just like that, we have added authentication to our application using the Amplify Framework. We have also customized the components of Amplify to give our own look, feel, and logic if we need it.

Conclusion

The Amplify Framework is an awesome new tool in our AWS toolbox. We demonstrated here that we can add authentication to any web or mobile application with just a few CLI commands. We can then deploy the AWS services that back modules like authentication with a simple push call.

But sometimes we want to add our own style to these types of frameworks. Not a problem. We showed that we can extend the base components inside of Amplify to create our user interfaces as well as hide the ones we don't care about.

Amplify continues to evolve and consists of many more modules like hosting, api, auth, and even storage. All key modules and AWS services that are important to most web applications. In addition, they also just announced Amplify Console which contains a global CDN to host your applications as well as a CI/CD Pipeline.

If you have any questions about this post or Amplify, feel free to drop me a comment below.

Are you hungry to learn even more about Amazon Web Services?

If you are looking to begin your AWS journey but feel lost on where to start, consider checking out my course. We focus on hosting, securing, and deploying static websites on AWS. Allowing us to learn over 6 different AWS services as we are using them. After you have mastered the basics there we can then dive into two bonus chapters to cover more advanced topics like Infrastructure as Code and Continuous Deployment.

Top comments (18)

Collapse
 
georgebelanger profile image
George Belanger • Edited

Hey everyone, due to a recent change to the Amplify signIn function, you now have to pass the event into your super.signIn function on line 61-ish:

in customSignIn.js around line 61
onClick={() => super.signIn()} ---> onClick={(event) => super.signIn(event)}

or you will get this error: Uncaught (in promise) TypeError: Cannot read property 'preventDefault' of undefined .

Here is the changes they made: github.com/aws-amplify/amplify-js/...

Here is the article I used to figure out what was wrong: stackoverflow.com/questions/475077...

Collapse
 
jukkahuuskonen profile image
Jukka Huuskonen

Hi Kyle,

Was there some reason to not use the withAuthenticator()? As far as I understood from reading the docs, you can pass customized components to withAuthenticator too.

Here is a link to the part of docs I'm talking about:
aws-amplify.github.io/docs/js/auth...

If I understood the docs correctly, you could have just passed the CustomSignIn-component to the withAuthenticator.

Collapse
 
kylegalbraith profile image
Kyle Galbraith • Edited

Great question! I didn't use withAuthenticator because I always wanted to use hide=[] to hide a lot of the default Amplify screens that I didn't need.

Collapse
 
jukkahuuskonen profile image
Jukka Huuskonen • Edited

Kyle wrote:

'because I always wanted to use hide=[] to hide a lot of the default Amplify screens that I didn't need.'

Isn't that what the TRUE/FALSE tag in withAuthenticator is used for?

Set it to TRUE (hide=[NONE]) and all AWS-components are shown.

Set it to FALSE (hide=[ALL]) like this and add your custom component(s) into an array:

export default withAuthenticator(App, false, [<YOURCUSTOMSIGNIN />]);

And only component is shown (and all the other custom components in the list). Of course you can also add standard AWS-components to the list there.

Thread Thread
 
kylegalbraith profile image
Kyle Galbraith

No the true/false for the HOC is for showing greetings. You could probably get this setup to work with the HOC but I preferred to go this route in the event I have further customization I need to make.

Thread Thread
 
jukkahuuskonen profile image
Jukka Huuskonen

Ahh, thanks. Couldn't find that in docs, so jumped to a coclusions, since it seemed to work that way (I just wanted the Greeting away and thought it did it to other components too...)

Collapse
 
jukkahuuskonen profile image
Jukka Huuskonen

Ok, have to look into that myself a bit more. Great article and helped me a lot :)

Collapse
 
david_j_eddy profile image
David J Eddy

Nice article Kyle! Easy to follow and explains the steps but not overly verbose, nice pacing.

I saw a AWS team demo this a Neilson a couple months back during a MeetUp. Makes me want to get into mobile dev. :)

Collapse
 
kylegalbraith profile image
Kyle Galbraith

Thank you for the kind comments David! It is a very slick framework and it is continuously being improved, definitely worth checking out.

Collapse
 
kayis profile image
K

Inheritance? In my React?! xD

Collapse
 
kylegalbraith profile image
Kyle Galbraith

React is certainly not my strongest area of expertise. Certainly, composition would be better here, but getting this customized to fit my needs was a jaunt in itself. Maybe I can circle back to this in the future.

Collapse
 
kayis profile image
K

Yes, the clean way would probably be to use the auth function calls directly, but I guess this is also more work.

Collapse
 
firelordozai01 profile image
Alejandro Ozai • Edited

I'm new to amplify and react as well. Is there an article that guides you in building a custom login page using amplify and modern react with functional components and hooks?

Collapse
 
ivashka007 profile image
Ivan Krasulya

Good article, but I do not quite understand how App and AppWithAuth are interrelated. What component do we include in index.js? And how do we generally connect AppWithAuth?

Collapse
 
kylegalbraith profile image
Kyle Galbraith

At the highest level, you would include AppWithAuth as the entry point for your app. This allows you to wrap your entire application with an authentication layer as demoed here.

Collapse
 
seth1993 profile image
Seth Bailey

It would be cool to add an article with examples for react-native. The library aws-amplify-react-native is slightly different and I'm having difficulty with changeState for forgotPassword and signUp

Collapse
 
petergreekbase profile image
peter-greekbase • Edited

Any idea of how to circumvent:
TS2339: Property 'authState' does not exist on type 'Readonly<{}> & Readonly<{ children?: ReactNode; }>'.

in App, at:
if (this.props.authState === 'signedIn')

?

Some comments may only be visible to logged-in visitors. Sign in to view all comments.