In my previous post I covered how to implement authentication in depth using Next.js with AWS.
In this post, we'll take the next steps to talk about the data story, how it fits in to the picture, and how to implement various access patterns with and without authentication and authorization.
During this post you'll be building a blogging app that will enable both public and private data access – fetching data on both the client as well as server and API routes.
The final code for this app is located here
Overview
When making requests to an API you often need to deal with security - managing ID tokens, access tokens, and refresh tokens as well as maintaining application and UI state based on the user session (or lack thereof). You also often need to have a combination of public and private API access for your data layer.
Combining access control, authentication, and authorization is often difficult to get right and do so in a secure manner.
Understanding how to enable and mix authorization modes allows you to have flexibility when building modern applications – most of which require multiple authorization modes as well as data access patterns.
When working with Next.js, you will typically be making a combination of API calls from client, server, and API routes. With the recent release of SSR Support covered in the last post, one of the things that Amplify now enables is the seamless integration of all of these mechanisms on both the client and the server.
When making API calls via REST or GraphQL, Amplify now automatically configures and sends the proper authorization headers on both the client and the server (when necessary) when SSR mode is enabled.
This tutorial is meant to show how all of this works and provide a step-by-step guide for implementing data fetching for the following use cases:
- Making a public client-side API call
- Making an authenticated client-side API call
- Hydrating a statically generated page with a public API call (via
getStaticPaths
andgetStaticProps
) - Making an authenticated API call from an SSR or API route
- Creating an API route for a public API endpoint into your data layer
Amplify data fetching
When creating or configuring an AppSync GraphQL API using Amplify, you have the ability to enable multiple authorization modes (a default mode as well as additional modes). This allows your app to incorporate private, public, or combined public and private access. In this tutorial we'll be covering how to implement a combination of public and private access using a single GraphQL API.
Once the API is created, you can make calls to the API using either the default authorization mode or by specifying the authorization mode.
Here are a few examples
Public API calls using default authorization mode (client-side, static, SSR, & API routes):
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries';
const data = await API.graphql({
query: listPosts
});
Specifying a custom authorization mode (client-side):
import { API } from 'aws-amplify';
import { listPosts } from './graphql/queries'
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
Making authenticated request with authorization headers (SSR):
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'
export async function getServerSideProps(context) {
const { API } = withSSRContext(context);
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
// do stuff with data
}
Making authenticated request with authorization headers (API routes):
import { withSSRContext } from 'aws-amplify';
import { listPosts } from './graphql/queries'
export default function handler(req, res) {
const { API } = withSSRContext({ req });
const data = await API.graphql({
query: listPosts,
authMode: "AMAZON_COGNITO_USER_POOLS"
});
// do stuff with data
}
About the app
In this tutorial we'll be building a basic blogging app. Users will be able to sign up, create posts, and comment on posts. Users who are not signed in will only be able to view posts.
To demonstrate public and private access, we'll only allow users who are signed in to be able to create or view post comments.
Getting started
If you've already completed building the app from part 1, continue to creating the API.
If not, follow these steps to deploy the Next app with authentication enabled:
1. Clone the repo
git clone https://github.com/dabit3/next.js-authentication-aws.git
2. Change into the directory and install the dependencies
cd next.js-authentication-aws
npm install
3. Initialize the Amplify project
amplify init
4. Deploy the authentication service
amplify push --y
5. Run the app locally
npm run dev
Creating the API
Next, create a new GraphQL API using the api
category:
amplify add api
? Please select from one of the below mentioned services: GraphQL
? Provide API name: nextapi
? Choose the default authorization type for the API: API key
? Enter a description for the API key: public
? After how many days from now the API key should expire: 365
? Do you want to configure advanced settings for the GraphQL API: Yes
? Configure additional auth types? Yes
? Choose the additional authorization types you want to configure for the API: Amazon Cognito User Pool
? Configure conflict detection? No
? Do you have an annotated GraphQL schema? N
? Choose a schema template: Single object with fields
? Do you want to edit the schema now? Y
When prompted, use the following GraphQL schema.
amplify/backend/api/nextapi/schema.graphql
The API created from this schema will allow us to save and query for two different types of data:
- Posts that can be viewed publicly but only edited or deleted by the creator of the post.
- Comments that can be viewed publicly but only edited or deleted by the creator of the comment.
The schema leverages the GraphQL Transform library of Amplify to generate queries and mutations for create, read, update, delete, and list operations for Post
s and Comment
s as well as creating subscriptions for each mutation and a database for each type (DynamoDB).
We also specify a custom data access pattern that allows us to query comments by post ID (commentsByPostId
).
To deploy the API, run the push
command:
amplify push --y
Once your API is deployed you should now be able to use it in your app.
Creating the blog app
The first thing we'll do is create a reusable Amplify configuration that enables SSR (Note - this is only needed for some SSR or API routes, not client routes). Create a file at the root of the app called configureAmplify.js.
We can now just import this wherever we need to configure Amplify.
helpers/checkUser.js
Next we'll create a reusable React hook that will allow us to easily manage user state across all of the components and pages.
Create a folder called helpers in the root of the project and create file called checkUser.js within the new folder.
This hook will automatically keep track of signed in users and allow us to manage our UI based on this user state (to show and hide UI).
pages/index.js
Now we'll update the main entry-point of the app to show the list of posts fetched from the API.
This page will be making a client-side API call to fetch the posts from the GraphQL backend and rendering them when the component loads using public API data access. We use the Link
component from next/link
and the ID of the post to enable navigation to the route /posts/${post.id}
.
Update pages/index.js with the following code.
pages/_app.js
Next let's update the navigation with the new configuration that we'd like to use.
The new navigation will use the user
state to show and hide the link for creating a new post (/create-post
), as only signed in users should be able to do so.
pages/posts/[id].js
Next, we'll need to have a way to render each individual post using a dynamic route.
To do so, create a new folder called pages/posts and create file called [id].js within the new folder.
This page will be taking advantage of getStaticPaths
as well as getStaticProps
to fetch data at build time and programmatically build the pages in our app based on the posts.
We'll also be using the fallback
flag to enable the pre-rendering of the available paths at build time while still allowing dynamic creation of pages on the fly as they are created by users at runtime.
pages/create-post.js
Finally, we'll create a new file called create-post.js in the pages directory that will allow a signed in user to create new posts.
Once a new post is created, the component will programmatically navigate to the new route.
Testing it out
We should now be able to test it out.
npm run dev
You should be able to create posts, view posts, and view the list of posts.
Adding comments
components/Comments.js
Next, let's enable the ability to add comments.
To do so, create a new folder called components at the root of the project and a file in that directory called Comments.js with the following code.
This component will render the list of associated comments and let users comment on a post.
pages/posts/[id].js
Next, we'll update the post component to render the Comments
component if the user is authenticated.
Testing it out
We should now be able to test out the new comment functionality.
Dev mode
npm run dev
Run a build
npm run build
npm start
Deploying to AWS
Be sure you have created a serverless.yml
file at the root of your project with the following configuration:
myNextApp:
component: "@sls-next/serverless-component@1.17.0"
Then run the following command:
npx serverless
Note - It can often take up to a couple of minutes for your URL to be live because of how Cloudfront works
Exposing a public API
Let's see how we might enable a public API to allow other developers to consume via their apps. To do so, we'll create a new API route at pages/api/posts.js with the following code:
You should now be able to navigate to http://localhost:3000/api/posts
and see a JSON response with your list of posts.
The final code for this app is located here
Top comments (9)
Thanks a lot for the post, it's amazing and a lot of new learning for me. I have a question around Amplify and Next.js. How would you manage for example a flow of a shopping cart? Imagine I have 1 model for products, 1 model for shopping carta and 1 model for orders.
What would be the correct way of:
Excellent Nader! Thanks...
I checked out the project from gitlab and adding a comment fails for me.
To solve the problem I needed to add in
components/Comments.js
:and
Nader, thank you so much for all the content. Great learning resources! Appreciated.
Just what I've been waiting for! Good stuff putting this all together!
Hello .. Anyone can tell me why it keeps show me
No current user
when I use getStaticPaths and getStaticProps .. Please!!@dabit3 @szymonkochanski
THANKS!
Great content, as always!
Great Post
Really like it