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
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
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
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
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
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;
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
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);
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.
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>
);
}
}
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"];
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;
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;
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.
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.