AWS Amplify is both a CLI toolchain and suite of methods that enable frontend developers to quickly create cloud-based backends while having the scaffolding and best-practices taken care of for them.
For example, if wanting to add a user signup flow to an app, entering the following command will provide guided prompts to accept a default configuration. You can even configure advanced settings.
amplify add auth
Behind the scenes, this sets up an AWS Cognito Pool, as well as an Identity Pool so that user attributes can be stored.
Additionally, if wanting to do the same, but for a RESTful API the following command will trigger a guided prompt to get that configured in your project as well.
amplify add auth
However, for many frontend developers, these resources may be already configured ahead of time by yourself or backend members of your team. Fortunately, the Amplify package allows us to bring in those resources as well.
In this post, let's explore how we can configure a Cognito Pool for user authorization, a RESTful API with ApiGateway, and finally connect those in our application using the AWS Amplify library.
Configuring our Cognito Pool
🚨 I'll be showing off actual secret keys during this post for the sake of learning, however I'll be sure to destroy these resources by the time this post goes live 😉
Let's first create our Cognito Pool by logging into the AWS Console and navigating to the AWS Cognito homepage. From there, we'll select the option "Manage User Pools", followed by "Create a user pool" in the top-right corner.
The next screen will ask us to give our user pool a name. I'm going to name mine demopool
and select "Review defaults".
From here, we can select any of the items we'd like to tweak before creating our user pool.
For this example, I'll select the email configuration section, and change the email message that my users will be sent when verifying their signup.
🚨When done, remember to click "Save Changes" at the bottom of the page.
Additionally, feel free to add tags, modify the password settings, etc. However, before selecting "Create pool" at the bottom of the page, we'll need to add and configure an App Client so that Amplify can successfully authenticate our frontend. So either from the left panel, or the main area, select "App Clients", and next select "Add an app client".
All we'll need to do here is give our app client a name, and uncheck "Generate client secret". Afterwards, we can select "Create app client" at the bottom.
When done, note that we'll need the generated pool id, as well as the app client id that we created earlier, so copy those over for easy access for the time being, and we'll switch over to our project
Setting up an Amplify Project
Chances are, you have the Amplify CLI already installed and configured on your machine, if not, please refer to this video on how to get it setup.
From there, let's open up our terminal, and create a new react application and after changing into the project directory, we'll add a few extra dependencies:
npx create-react-app custom-amplify-demo --use-npm
cd custom-amplify-demo && npm i aws-amplify @aws-amplify/ui-react
With our project scaffolded, and dependencies installed, let's configure Amplify to use our custom auth resource.
In src/App.js
, replace the current code with the following:
import React from "react";
import "./App.css";
import Amplify from "aws-amplify";
import { withAuthenticator, AmplifySignOut } from "@aws-amplify/ui-react";
Amplify.configure({
Auth: {
region: "us-east-1",
userPoolId: "us-east-1_T2ZfRqx59",
userPoolWebClientId: "61l5lp494qnb60ek6h0ohonspp",
},
});
function App() {
return (
<div className="App">
<header className="App-header">
<p>welcome!</p>
<AmplifySignOut />
</header>
</div>
);
}
export default withAuthenticator(App);
Starting with our import
s, we're first bringing in the Amplify
library so that we can talk to our backend resource. Next, we import a higher-ordered-component called withAuthenticator
as well as a component to let us sign out of the app.
From there, we are configuring the Amplify library with the credentials needed for our Cognito Pool. Lastly, we are creating a component that simply renders out a "welcome!" message along with our sign out button. Save the app, and run npm run start
so that our app starts on localhost:3000
.
Just like that, you should now see the application showing a fully-featured signup form that is absolute positioned on the webpage.
Go ahead and sign up for an account. After signing up, check your email for for the verification code. If all went well, your message should contain the content we provided in our Cognito Pool.
Go ahead and take the verification code at the bottom of the email, and use it as input for the signup form. Once done, you should be automatically routed to the application.
🎉We did it🎉
It's worth noting that everything we did in this example could have been done using the CLI that Amplify provides, however the flexibility to use custom resources is a great feature to have regardless!
Configuring Our API Gateway
Our next task is go create a custom REST endpoint. To do this, we'll first create a cloud function (AWS lambda) and assign it to a GET method in API Gateway. This can be particularly useful when wanting to separate your frontend with your infrastructure-as-code, where it's common to create this with Cloudformation, Terraform, or Serverless Framework.
To begin, we'll go back into the AWS Console, and navigate to the AWS Lambda creation page.
🚨 Make sure you are in the same region as your Cognito pool
On this page, we'll give our lambda (cloud function) a name and select create function. Optionally, feel free to adjust the runtime and associated policy if that better suits your use case.
On the following page, we can adjust the response that we'd like to send back to our users when they hit our to-be-created endpoint, and select save.
At this point, we have our lambda created, but we have no way to invoke it. Let's change that by adding API Gateway as a trigger. On the same page, select the Designer dropdown arrow located above the Function code section. Next select add trigger, and choose API Gateway from the select field.
From here, we'll have to configure our API. For this example, we're going to make this a REST API, that will require an API key to use with no additional settings.
If all went well, we should get a success message at the top of our page, along with our endpoint and API key at the bottom.
As is, our API would work as long as we pass in the API Key, however, because we're going to calling our API from the browser on a different domain, we have to enable CORS. Also, API Gateway setup a generic ANY
method, but Amplify only supports GET
,PUT
,DELETE
,POST
. Let's click the resource name to be taken to API Gateway, where we will do he following:
- Configure our API with a single
GET
method. - Assign the lambda we created earlier to that method.
- Protect this route with the API key we created earlier.
- Enable CORS on our endpoint
- Deploy the API
A step-by-step gif of this process can be found by clicking here
🗒️ Given our full API URL:
https://n4hgk2fh3h.execute-api.us-east-1.amazonaws.com/default/custom-resource-demo-function
Note that
https://n4hgk2fh3h.execute-api.us-east-1.amazonaws.com
is our base URLdefault
is the stage namecustom-resource-demo-function
is the specific endpoint we are hitting
🎉We did it🎉
Now comes the simple part where we update our frontend code to use our newly created API!
We'll keep it simple where the data from the API will display on the page when a user clicks a button. Go ahead and update your code with the following, bearing in mind to use your own resource credentials.
import React from "react";
import "./App.css";
import Amplify, { API } from "aws-amplify";
import { withAuthenticator, AmplifySignOut } from "@aws-amplify/ui-react";
Amplify.configure({
//Auth is the same as before
Auth: {
region: "us-east-1",
userPoolId: "us-east-1_T2ZfRqx59",
userPoolWebClientId: "61l5lp494qnb60ek6h0ohonspp",
},
// Add in our new API, "name" can be whatever we want
API: {
endpoints: [
{
name: "demo",
endpoint:
"https://n4hgk2fh3h.execute-api.us-east-1.amazonaws.com/default",
},
],
},
});
function App() {
const [apiData, setApiData] = React.useState("");
const handleClick = async () => {
const data = await API.get("demo", "/custom-resource-demo-function", {
headers: {
//🚨 don't embed api keys in real life!!
"x-api-key": "grC9ZygLZuaek3hS8Uh6I9rtC5IgYvwd36EAjaba",
},
});
setApiData(data.body);
};
return (
<div className="App">
<header className="App-header">
<p>Welcome! My favorite quote is: {apiData}</p>
<button onClick={handleClick}>Click me!</button>
<AmplifySignOut />
</header>
</div>
);
}
export default withAuthenticator(App);
If you were logged out of your application, go ahead and log back in. Now click the button and bear the fruits of all your hard work!🐻🍒
It's worth reiterating, that AWS Amplify's selling point is that it does all of this configuration for us, so that we can focus on our specific business logic✨
However, I've personally used Amplify on projects where the frontend team was a bit silo'd from the backend team, and we relied on the backend to create our resources as a compromise to us using a lot of Amplify's built-in components and methods.
Hope this helps! Be sure to follow for more content on AWS Amplify!
Top comments (4)
Thanks for this, it served me well on a project I was working where the userPool was preconfigured, question, are there any security considerations regarding exposing the userPoolID and userPoolWebClientId in the client code?
Thanks! Regarding security considerations, no--there's nothing inherently bad about exposing it. Of course, less is better 😉 but a malicious actor would still need proper AWS credentials (secret key/access token), or IAM Role to perform operations. Awesome question!
Can you speak to the aspect of changes that Amplify performs depending on additional frontend logic? Specifically, I'm thinking DataSore. Every time I modify the schema for GraphQL (with Auth tags), it wants to update Cognito. This has made me nervous about ejecting it from Amplify, but I need some of the manual controls that Amplify hasn't implemented (email domain, in your example).
You're right that Amplify likes to take control of cognito to perform what it thinks is a best practice and that sometimes that may conflict with what our business logic is trying to do. In my experience, it's best to try and get as far as you can with the CLI and if you find something isn't supported, then to open an issue on the repo to see if it can be put on the roadmap.
Regarding your manual control. If by email domain you meant having a presignup hook that verifes a domain (an allowed, or blocked list for example) the amplify cli does have that built it.
Happy to help out, and if you'd like some more feedback on how it could help, feel free to post any Q's you have in the Amplify discord channel: github.com/aws-amplify/amplify-js/...