Secure your NextJS Apps with Azure AD and NextAuth (Part 6)
Deploying with Serverless Stack
Disclaimer: This is part 6 of a 7 part tutorial. Click the below link to go back to Part 1 to learn more about the intent of this tutorial.
Part 1 — Introduction and “What is an Identity Provider?”
Or go back to Part 5 — Securing your API Routes with NextAuth
Serverless Stack (SST) Refresher
In Part 3 we briefly described Serverless Stack (SST).
SST is an abstraction layer sitting on top of AWS CDK that offers excellent, intuitive constructs for deploying full stack web applications to AWS.
For this tutorial we will be deploying the NextJS application, AppSync + GraphQL, and using Open ID Connect (OIDC) as an authorizer for AppSync.
The OIDC authorizer will be covered in Part 7.
Before we get started
Remember in Part 3 where you were asked to create an AWS account? Well, if you haven’t done that, now’s the perfect time. Here’s the bit from Part 3 in case you don’t want to go back
To setup the required credentials you can follow the offical AWS instructions for setting up an account and user and storing AWS credentials.
To make things easier for you, setup the default profile as the one you want to deploy with for this tutorial. Otherwise if you set up an alternative profile, you can prefix the npm scripts in the root package.json
with AWS_PROFILE=<your_profile_here>
Configuring your SST Constructs — AppSync API
We are going to use AppSync as a Proof of Concept for authenticating a user through a third-party service. This will be covered in Part 7.
In stacks/MyStack.ts
add the following imports to the top of the file
import { AppSyncApi, Function, NextJsSite, StackContext } from "sst/constructs"
import * as appsync from "@aws-cdk/aws-appsync-alpha"
Now let’s setup AppSync and add a simple Notes
table in DynamoDB with a Lambda resolver to return any notes in the table.
In stacks/MyStack.ts
add the following code
export function MyStack({ stack, app }: StackContext) {
// Create the notes table
const notesTable = new Table(stack, "Notes", {
fields: {
id: "string",
},
primaryIndex: { partitionKey: "id" },
});
// Create the AppSync GraphQL API
const api = new AppSyncApi(stack, "AppSyncApi", {
schema: "graphql/schema.graphql",
defaults: {
function: {
// Bind lambda function to table
bind: [notesTable],
},
},
// Add lambda as data source
dataSources: {
notes: "functions/lambda.handler",
},
resolvers: {
"Query listNotes": "notes",
},
});
}
The Table
construct configures a DynamoDB instance with a table called Notes
. The AppSyncApi
construct will spin up an AppSync instance with a lambda resolver which has permission to access the notes table.
In the services
directory create a graphql
folder and add a schema.graphql
file with the following content
# services/graphql/schema.graphql
type Note {
id: ID!
content: String!
}
type Query {
listNotes: [Note]
}
Update the code in services/functions/lambda.ts
to connect to our DynamoDB table and list all the notes
// services/functions/lambda.ts
import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";
type Note = {
id: string;
content: string;
};
type AppSyncEvent = any;
const dynamoDb = new DynamoDB.DocumentClient();
export async function handler(
event: AppSyncEvent
): Promise<Record<string, unknown>[] | Note> {
const params = {
TableName: Table.Notes.tableName,
};
const data = await dynamoDb.scan(params).promise();
return data.Items;
}
Configuring your SST Constructs — Next.js Site
Next let’s set up our Next.js application deployment.
In the stacks/MyStacks.ts
file add the following code just below the AppSyncApi
code.
// Create a Next.js site
const site = new NextjsSite(stack, "Site", {
path: "packages/frontend",
environment: {
REGION: app.region,
API_URL: api.url,
},
});
// Allow the Next.js API to access the table
site.attachPermissions([table]);
// Show the site URL in the output
stack.addOutputs({
URL: site.url,
});
The NextJsSite
construct will handle deploying our NextJS application and the required infrastructure into AWS. SST will add the environment variables to the Next.js application for us. This means we will be able to access the URL to our AppSync GraphQL endpoint by using process.env.API_URL
in client/server components and API routes.
Local Deployment
Before moving on to a full deployment we should test a local dev deployment.
Add the sst env
command to the frontend package.json dev
script. This will allow you to pass environment variables defined in your SST constructs (like API_URL, etc…) to your local Next.js dev server.
Replace the dev
script in your frontend/package.json
.
"dev": "next dev",
With the following:
"dev": "sst env next dev",
At the root of the project run npm run dev
to deploy your personal dev stack to AWS via SST. I won’t go into detail about how this works, but just know that SST does some cool stuff to give you a development experience that is incredibly close to working with a live environment.
Open a separate terminal and run npm run dev
in the frontend
directory.
You should be able to console.log
the API_URL
in your hello.ts
API route like so
console.log("API_URL", process.env.API_URL)
Deploying for real!
To deploy the application for real, run npm run deploy
at the root of the project. This will deploy your infrastructure to AWS. There will be some output in the terminal that displays your site URL. It might look something like
URL: https://d111111abcdef8.cloudfront.net
In order to test auth in your live site, you will need to take that Site URL
and add it to the Redirect URIs
in Azure AD.
To do this, navigate to your App Registration in Azure AD, click on your App, and then select Authentication
on the left-side navigation menu. From there, click Add URI
under the Platform Configurations
Redirect URIs
section.
Click the save button at the bottom of the screen and you should be good to navigate to your site, via the url, and sign in!
Next up we’re going to make sure that only authenticated users can access our AppSync API.
Head over to Part 7 to get started.
What’s next?
Part 7 — Third-Party Authentication and Authorization with AWS AppSync
Tools used in this tutorial
Core Concepts
My goal is to provide content that leaves readers with a bit more knowledge than they started with.
If this tutorial helped you in anyway please leave a clap or two! If you have any questions or I got something wrong, leave a comment and let me know how I can improve!