Skip to content

Commit cb9552e

Browse files
authored
feat: add Next.js server-side auth and HTTP-only cookie content (#8224)
* feat: add Next.js server-side auth and HTTP-only cookie content * chore: add required callouts * chore: resolve comments * chore: resolve comments * chore: resolve comments * chore: resolve comments * chore: resolve comments * chore: rearrange content sectioins * chore: resolve comments
1 parent 75144e7 commit cb9552e

File tree

2 files changed

+418
-41
lines changed
  • src/pages
    • [platform]/build-a-backend/server-side-rendering
    • gen1/[platform]/build-a-backend/server-side-rendering/nextjs

2 files changed

+418
-41
lines changed

src/pages/[platform]/build-a-backend/server-side-rendering/index.mdx

Lines changed: 217 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,32 @@ Before you begin:
3838

3939
## Install the Amplify Next.js adapter
4040

41+
<Callout warning>
42+
43+
**Note:** Amplify JS v6 supports Next.js with the version range: `>=13.5.0 <16.0.0`.
44+
Ensure you have the correct version to integrate with Amplify.
45+
46+
</Callout>
47+
4148
To use Amplify APIs server-side, you need to install the Amplify Next.js adapter in addition to the Amplify libraries:
4249

4350
```bash title="Terminal" showLineNumbers={false}
4451
npm add aws-amplify @aws-amplify/adapter-nextjs
4552
```
4653

47-
## Configure Amplify APIs for server-side usage
54+
## Configure Amplify in Next.js
55+
56+
<BlockSwitcher>
57+
58+
<Block name="Configure Amplify for server-side usage">
4859

49-
You will need to create a `runWithAmplifyServerContextRunner` function to use Amplify APIs on the server-side of your Next.js app.
60+
You will need to create a `runWithAmplifyServerContext` function to use Amplify APIs on the server-side of your Next.js app.
5061

51-
You can create an `amplifyServerUtils.ts` file under a `utils` folder in your codebase. In this file, you will import the Amplify backend outputs from the `amplify_outputs.json` file that is generated by the Amplify CLI, and use the `createServerRunner` function to create the `runWithAmplifyServerContextRunner` function.
62+
You can create an `amplifyServerUtils.ts` file under a `utils` folder in your codebase. In this file, you will import the Amplify backend outputs from the `amplify_outputs.json` file that is generated by the Amplify CLI, and use the `createServerRunner` function to create the `runWithAmplifyServerContext` function.
5263

5364
For example, the `utils/amplifyServerUtils.ts` file may contain the following content:
5465

55-
```typescript
66+
```typescript title="src/utils/amplifyServerUtils.ts"
5667
import { createServerRunner } from '@aws-amplify/adapter-nextjs';
5768
import outputs from '@/amplify_outputs.json';
5869

@@ -64,16 +75,21 @@ export const { runWithAmplifyServerContext } = createServerRunner({
6475
You can use the exported `runWithAmplifyServerContext` function to call Amplify APIs within isolated request contexts. You can review examples under the [Calling Amplify category APIs on the server side](#calling-amplify-category-apis-on-the-server-side) section.
6576

6677
<Callout>
67-
**TIP:** You only need to call the `createServerRunner` function once and reuse the `runWithAmplifyServerContext` function throughout.
78+
**Tip:** You only need to call the `createServerRunner` function once and reuse the `runWithAmplifyServerContext` function throughout.
6879
</Callout>
6980

70-
## Configure Amplify library for client-side usage
81+
</Block>
82+
<Block name="Configure Amplify for client-side usage">
83+
84+
<Callout>
85+
**Tip**: You only need do this step if you are using Amplify APIs on the client side of your Next.js app, for example, calling Amplify Auth `signIn` API to sign in a user, or use GraphQL subscriptions on the client side.
86+
</Callout>
7187

7288
When you use the Amplify library on the client-side of your Next.js app, you will need to configure Amplify by calling the `Amplify.configure` as you would to use Amplify in a single-page application.
7389

7490
<Callout>
7591

76-
**NOTE:** To use the Amplify library on the client side in a Next.js app, you will need to set `ssr` to `true` when calling `Amplify.configure`. This instructs the Amplify library to store tokens in the cookie store of a browser. Cookies will be sent along with requests to your Next.js server for authentication.
92+
**Note:** To use the Amplify library on the client side in a Next.js app, you will need to set `ssr` to `true` when calling `Amplify.configure`. This instructs the Amplify library to store tokens in the cookie store of a browser. Cookies will be sent along with requests to your Next.js server for authentication.
7793

7894
</Callout>
7995

@@ -96,6 +112,12 @@ export default function RootLayoutThatConfiguresAmplifyOnTheClient({
96112
}
97113
```
98114

115+
<Callout warning>
116+
117+
Make sure you call `Amplify.configure` as early as possible in your application’s life-cycle. A missing configuration or `NoCredentials` error is thrown if `Amplify.configure` has not been called before other Amplify JavaScript APIs. Review the [Library Not Configured Troubleshooting guide](/gen1/[platform]/build-a-backend/troubleshooting/library-not-configured/) for possible causes of this issue.
118+
119+
</Callout>
120+
99121
<Callout>
100122

101123
To avoid repetitive calls to `Amplify.configure`, you can call it once in a top-level client-side rendered layout component.
@@ -108,7 +130,7 @@ If you're using the Next.js App Router, you can create a client component to con
108130

109131
`ConfigureAmplifyClientSide.ts`:
110132

111-
```typescript
133+
```tsx title="src/components/ConfigureAmplifyClientSide.tsx"
112134
'use client';
113135

114136
import { Amplify } from 'aws-amplify';
@@ -123,7 +145,7 @@ export default function ConfigureAmplifyClientSide() {
123145

124146
`layout.tsx`:
125147

126-
```jsx
148+
```tsx title="src/app/layout.tsx"
127149
import ConfigureAmplifyClientSide from '@/components/ConfigureAmplifyClientSide';
128150
import './globals.css';
129151

@@ -154,15 +176,194 @@ export default function RootLayout({
154176

155177
</Accordion>
156178

179+
</Block>
180+
181+
</BlockSwitcher>
182+
183+
184+
157185
## Authentication with Next.js server-side runtime
158186

159-
You can use the Amplify Auth category APIs to sign up and sign in your end users on the client side. When you set `ssr: true` when calling `Amplify.configure`, the Amplify library uses cookies to store tokens which will be sent along with HTTP requests to your Next.js app server.
187+
### (Experimental) Perform authentication on the server side and enable HttpOnly cookies
188+
189+
<Callout warning>
190+
191+
**Warning:** This feature is experimental and may change in future releases.
192+
193+
Once you enable the server-side sign-in feature, auth tokens are stored in HttpOnly cookies and you may not change the HttpOnly attribute. Since these cookies are inaccessible from client-side scripts, you won’t be able to use any Amplify JS APIs on the client side. Therefore, you don’t need to configure Amplify on the client side. You can keep using [these Amplify JS server-side APIs](/[platform]/build-a-backend/server-side-rendering/#supported-apis-for-nextjs-server-side-usage) on the server side.
194+
195+
</Callout>
196+
197+
Additional setup is required to enable server-side authentication flows in your Next.js app.
198+
199+
#### Step 1 - Specify the origin of your app in environment variables
200+
201+
Add the following environment variable to your Next.js app. For example in a `.env` file:
202+
203+
```shell title=".env" showLineNumbers={false}
204+
AMPLIFY_APP_ORIGIN=https://myapp.com
205+
```
206+
207+
Ensure this environment variable is accessible in your Next.js app's server runtime.
208+
209+
<Callout info>
210+
211+
**Note:** Token cookies are transmitted via server-side authentication flows. In production environments, it is recommended to use HTTPS as the origin for enhanced security.
212+
213+
</Callout>
214+
215+
#### Step 2 - Export the `createAuthRouteHandlers` function
216+
217+
The `createAuthRouteHandlers` function is created by the `createServerRunner` function call when you configure Amplify for server-side usage. You can export this function from your `amplifyServerUtils.ts` file. You can also configure cookie attributes with the `runtimeOptions` parameter.
218+
219+
```typescript title="src/utils/amplifyServerUtils.ts"
220+
import { createServerRunner } from '@aws-amplify/adapter-nextjs';
221+
import outputs from '@/amplify_outputs.json';
222+
223+
export const {
224+
runWithAmplifyServerContext,
225+
// highlight-start
226+
createAuthRouteHandlers,
227+
// highlight-end
228+
} = createServerRunner({
229+
config: outputs,
230+
// highlight-start
231+
runtimeOptions: {
232+
cookies: {
233+
domain: '.myapp.com', // making cookies available to all subdomains
234+
sameSite: 'strict',
235+
maxAge: 60 * 60 * 24 * 7 // 7 days
236+
}
237+
}
238+
// highlight-end
239+
});
240+
```
241+
242+
#### Step 3 - Set up the Auth API routes
243+
244+
Create an API route using the `createAuthRouteHandlers` function. For example:
245+
246+
<BlockSwitcher>
247+
<Block name="App router">
248+
```typescript title="src/app/api/auth/[slug]/route.ts"
249+
import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils";
250+
251+
export const GET = createAuthRouteHandlers({
252+
redirectOnSignInComplete: "/home",
253+
redirectOnSignOutComplete: "/sign-in",
254+
});
255+
```
256+
</Block>
257+
<Block name="Pages router">
258+
```typescript title="src/pages/api/auth/[slug].ts"
259+
import { createAuthRouteHandlers } from "@/utils/amplifyServerUtils";
260+
261+
export default createAuthRouteHandlers({
262+
redirectOnSignInComplete: "/home",
263+
redirectOnSignOutComplete: "/sign-in",
264+
});
265+
```
266+
</Block>
267+
</BlockSwitcher>
268+
269+
With the above example, Amplify generates the following API routes:
270+
271+
| API Routes | What it does |
272+
| --------------------------------------------------- | ------------------------------------------------------------ |
273+
| `/api/auth/sign-up` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-up form. After sign-up and sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. |
274+
| `/api/auth/sign-in` | Upon navigating an end user to this route, they’ll be redirected to the Amazon Cognito Managed Login sign-in form. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. |
275+
| `/api/auth/sign-in?provider=<social-provider-name>` | Upon navigating an end user to this route, they’ll be redirected first to the Amazon Cognito Managed Login and then the specified social provider sign-in page. After sign-in, they’ll be redirected back to the route `/api/auth/sign-in-callback`. |
276+
| `/api/auth/sign-out` | Upon navigating an end user to this route, the end user will be signed out and redirected to the route `/api/auth/sign-out-callback`. |
277+
| `/api/auth/sign-in-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing in. Amplify exchanges auth tokens and stores them as HttpOnly cookies in the browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignInComplete` parameter. |
278+
| `/api/auth/sign-out-callback` | Amazon Cognito Managed Login redirects an end user back to this route after signing out, Amplify revokes access token and refresh token and removes token cookies from browser cookie store, then redirects the end user back to the route specified by the `redirectOnSignOutComplete` parameter. |
279+
280+
<Callout info>
281+
282+
**Note:** A signing-out call involves multiple steps, including signing out from Amazon Cognito Managed Login, revoking tokens, and removing cookies. If the user closes the browser during the process, the following may occur:
283+
284+
1. auth token have not been revoked - user remains signed in
285+
2. auth token have been revoked but cookies have not been removed - cookies will be removed when the user visits the app again
286+
287+
</Callout>
288+
289+
#### Step 4 - Provide the redirect URLs to the Auth Resource in Amplify
290+
291+
You can provide the callback API routes as the redirect URLs in the Auth resource configuration. For example:
292+
293+
```ts title="amplify/auth/resource.ts"
294+
export const auth = defineAuth({
295+
loginWith: {
296+
email: true,
297+
// highlight-start
298+
externalProviders: {
299+
callbackUrls: ["https://myapp.com/api/auth/sign-in-callback"],
300+
logoutUrls: ["https://myapp.com/api/auth/sign-out-callback"],
301+
},
302+
// highlight-end
303+
},
304+
});
305+
```
306+
307+
This enables Amazon Cognito Hosted UI to support the server-side authentication flows. You may upgrade to the latest Amazon Cognito Managed Login Branding to customize the sign-in and sign-up pages. See [Amazon Cognito user pool managed login](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html) for more information.
308+
309+
#### Step 5 - Use Anchor link for initiating server-side authentication flows
160310

161-
### Manage Auth session with the Next.js Middleware
311+
Use HTML anchor links to navigate users to the sign-in and sign-up routes. For example:
312+
313+
<BlockSwitcher>
314+
<Block name="Sign in button">
315+
```tsx title="src/components/SignInButton.tsx"
316+
export const SignInButton() {
317+
return (
318+
<a href="/api/auth/sign-in">
319+
Sign In
320+
</a>
321+
);
322+
}
323+
```
324+
</Block>
325+
<Block name="Sign in with Google button">
326+
```tsx title="src/components/SignInWithGoogleButton.tsx"
327+
export const SignInWithGoogleButton() {
328+
return (
329+
<a href="/api/auth/sign-in?provider=Google">
330+
Sign In with Google
331+
</a>
332+
);
333+
}
334+
```
335+
</Block>
336+
<Block name="Sign up button">
337+
```tsx title="src/components/SignUpButton.tsx"
338+
export const SignUpButton() {
339+
return (
340+
<a href="/api/auth/sign-up">
341+
Sign Up
342+
</a>
343+
);
344+
}
345+
```
346+
</Block>
347+
<Block name="Sign out button">
348+
```tsx title="src/components/SignOutButton.tsx"
349+
export const SignOutButton() {
350+
return (
351+
<a href="/api/auth/sign-out">
352+
Sign Out
353+
</a>
354+
);
355+
}
356+
```
357+
</Block>
358+
</BlockSwitcher>
359+
360+
When an end user clicks on the buttons above, a corresponding server-side authentication flow will be initiated.
361+
362+
### Validate user session with the Next.js Middleware
162363

163364
You can use the `fetchAuthSession` API to check the auth sessions that are attached to the incoming requests in the middleware of your Next.js app to protect your routes. For example:
164365

165-
```typescript
366+
```typescript title="src/middleware.ts"
166367
import { fetchAuthSession } from 'aws-amplify/auth/server';
167368
import { NextRequest, NextResponse } from 'next/server';
168369
import { runWithAmplifyServerContext } from '@/utils/amplifyServerUtils';
@@ -211,7 +412,7 @@ In this example, if the incoming request is not associated with a valid user ses
211412

212413
<Callout>
213414

214-
**NOTE:** When calling `fetchAuthSession` with a `response` context, it will send the refreshed tokens (if any) back to the client via the `Set-Cookie` header in the response.
415+
**Note:** When calling `fetchAuthSession` with a `response` context, it will send the refreshed tokens (if any) back to the client via the `Set-Cookie` header in the response.
215416

216417
</Callout>
217418

@@ -226,7 +427,7 @@ For the **GraphQL API** category, review [Connect to data from Server-side Runti
226427

227428
<Callout>
228429

229-
**NOTE:** A subset of Amplify APIs can now be called on the server side of a Next.js app. These APIs are exported from the `/server` sub paths. See [the full list](#supported-apis-for-nextjs-server-side-usage) of supported APIs.
430+
**Note:** A subset of Amplify APIs can now be called on the server side of a Next.js app. These APIs are exported from the `/server` sub paths. See [the full list](#supported-apis-for-nextjs-server-side-usage) of supported APIs.
230431

231432
</Callout>
232433

@@ -274,10 +475,7 @@ export default async function AuthGetCurrentUserServer() {
274475
});
275476

276477
return (
277-
<AuthFetchResult
278-
description="The API is called on the server side."
279-
data={currentUser}
280-
/>
478+
<p>{`Hello, ${currentUser.username}`}</p>
281479
);
282480
} catch (error) {
283481
console.error(error);
@@ -325,7 +523,7 @@ export default async function StaticallyRenderedPage() {
325523

326524
<Callout>
327525

328-
**NOTE:** The URL returned by the `getUrl` API expires in the above example. You may want to specify the `revalidate` parameter to rerender the page as required to ensure the URL gets regenerated.
526+
**Note:** The URL returned by the `getUrl` API expires in the above example. You may want to specify the `revalidate` parameter to rerender the page as required to ensure the URL gets regenerated.
329527

330528
</Callout>
331529

0 commit comments

Comments
 (0)