From 641a6e99faec3414c6775f353bb049e5901c30b2 Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Fri, 13 Dec 2024 13:27:36 +0100 Subject: [PATCH] chore: polish app router example --- examples/nextjs-app-router/app/globals.css | 2 +- examples/nextjs-app-router/app/layout.tsx | 7 +- examples/nextjs-app-router/app/logo.svg | 8 ++ examples/nextjs-app-router/app/page.tsx | 96 ++++++++++++++++--- .../nextjs-app-router/app/settings/page.tsx | 32 +++++++ examples/nextjs-app-router/ory.config.ts | 4 +- examples/nextjs-pages-router/README.md | 68 ++++--------- .../nextjs-pages-router/pages/api/hello.ts | 16 ---- packages/nextjs/src/app/index.ts | 1 + packages/nextjs/src/app/logout.ts | 46 +++++++++ .../nextjs/src/middleware/middleware.test.ts | 20 ++++ packages/nextjs/src/middleware/middleware.ts | 3 + 12 files changed, 221 insertions(+), 82 deletions(-) create mode 100644 examples/nextjs-app-router/app/logo.svg create mode 100644 examples/nextjs-app-router/app/settings/page.tsx delete mode 100644 examples/nextjs-pages-router/pages/api/hello.ts create mode 100644 packages/nextjs/src/app/logout.ts diff --git a/examples/nextjs-app-router/app/globals.css b/examples/nextjs-app-router/app/globals.css index 89160e6e..5c75f0d9 100644 --- a/examples/nextjs-app-router/app/globals.css +++ b/examples/nextjs-app-router/app/globals.css @@ -8,7 +8,7 @@ body { color: var(--foreground); background: var(--background); - font-family: Arial, Helvetica, sans-serif; + font-family: Inter, Helvetica, sans-serif; } @layer utilities { diff --git a/examples/nextjs-app-router/app/layout.tsx b/examples/nextjs-app-router/app/layout.tsx index e21eb551..a9ddd6c5 100644 --- a/examples/nextjs-app-router/app/layout.tsx +++ b/examples/nextjs-app-router/app/layout.tsx @@ -3,6 +3,9 @@ import "./globals.css" import React, { Suspense, ReactNode } from "react" +import { Inter } from "next/font/google" + +const inter = Inter({ subsets: ["latin"] }) export default function RootLayout({ children, @@ -10,8 +13,8 @@ export default function RootLayout({ children: ReactNode }>) { return ( - - + + {children} diff --git a/examples/nextjs-app-router/app/logo.svg b/examples/nextjs-app-router/app/logo.svg new file mode 100644 index 00000000..aa232910 --- /dev/null +++ b/examples/nextjs-app-router/app/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/examples/nextjs-app-router/app/page.tsx b/examples/nextjs-app-router/app/page.tsx index f5dfd24a..42b30331 100644 --- a/examples/nextjs-app-router/app/page.tsx +++ b/examples/nextjs-app-router/app/page.tsx @@ -1,19 +1,93 @@ // Copyright © 2024 Ory Corp // SPDX-License-Identifier: Apache-2.0 -"use client" -import { useSession } from "@ory/elements-react/client" +import { SessionProvider } from "@ory/elements-react/client" +import { getLogoutFlow, getServerSession } from "@ory/nextjs/app" +import { Metadata } from "next" +import Image from "next/image" import Link from "next/link" +import OryLogo from "./logo.svg" + +export const metadata: Metadata = { + title: "Ory Next.js App router Example", +} + +export default async function RootLayout() { + const session = await getServerSession() + + return ( + +
+
+ Ory Logo +

Ory Next.js App Router Example

+ {!session && ( +
+ + Registration + + + Login + + + Account Recovery + + + Account Verification + +
+ )} + {session && ( +
+

+ Hi,{" "} + {session.identity?.traits.email ?? + session.identity?.traits.username ?? + session.identity?.traits.phone} + ! +

+ + Settings + + +
+ )} +
+ + App Router Example + + + Page Router Example + +
+
+
+
+ ) +} + +async function LogoutLink() { + const flow = await getLogoutFlow({}) -export default function RootLayout() { - const { session } = useSession() - if (session) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return "Hello: " + session.identity?.traits.email - } return ( -

- Not authenticated, please log in. -

+ + Logout + ) } diff --git a/examples/nextjs-app-router/app/settings/page.tsx b/examples/nextjs-app-router/app/settings/page.tsx new file mode 100644 index 00000000..89dc9070 --- /dev/null +++ b/examples/nextjs-app-router/app/settings/page.tsx @@ -0,0 +1,32 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import { Settings } from "@ory/elements-react/theme" +import { SessionProvider } from "@ory/elements-react/client" +import { enhanceOryConfig } from "@ory/nextjs" +import { getSettingsFlow, OryPageParams } from "@ory/nextjs/app" +import "@ory/elements-react/theme/styles.css" + +import config from "@/ory.config" + +export default async function SettingsPage(props: OryPageParams) { + const flow = await getSettingsFlow(props.searchParams) + + if (!flow) { + return null + } + + return ( +
+ + + +
+ ) +} diff --git a/examples/nextjs-app-router/ory.config.ts b/examples/nextjs-app-router/ory.config.ts index 30dbd2fd..95bd016f 100644 --- a/examples/nextjs-app-router/ory.config.ts +++ b/examples/nextjs-app-router/ory.config.ts @@ -5,11 +5,13 @@ import type { OryConfig } from "@ory/nextjs" const config: OryConfig = { override: { - applicationName: "NextJS app router example", + applicationName: "Ory Next.js App Router Example", loginUiPath: "/auth/login", registrationUiPath: "/auth/registration", recoveryUiPath: "/auth/recovery", verificationUiPath: "/auth/verification", + settingsUiPath: "/settings", + defaultRedirectUri: "/", }, } diff --git a/examples/nextjs-pages-router/README.md b/examples/nextjs-pages-router/README.md index 643db97b..fb1f29fc 100644 --- a/examples/nextjs-pages-router/README.md +++ b/examples/nextjs-pages-router/README.md @@ -1,56 +1,22 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with -[`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +This is a simple example application using @ory/elements-react & Next.js app +router. -## Getting Started +## Getting started -First, run the development server: +1. Sign up for an account at https://console.ory.sh +2. Create a new or use an existing project +3. Go to https://console.ory.sh/projects/current/settings and copy the **API + endpoints** URL +4. Set the `NEXT_PUBLIC_ORY_SDK_URL` to your project's **API endpoints** URL +5. Run `npm install` +6. Run `npm run dev` and open navigate to https://localhost:3000 -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` +> [!WARNING] For convinience Ory provides a default "playground" project, that +> can be used to interact with Ory's APIs. It is a public project, that can be +> used by anyone and data can be deleted at any time. Make sure to use a +> dedicated project. -Open [http://localhost:3000](http://localhost:3000) with your browser to see the -result. +## Need help? -You can start editing the page by modifying `pages/index.tsx`. The page -auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on -[http://localhost:3000/api/hello](http://localhost:3000/api/hello). This -endpoint can be edited in `pages/api/hello.ts`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are -treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead -of React pages. - -This project uses -[`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to -automatically optimize and load Inter, a custom Google Font. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js - features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out -[the Next.js GitHub repository](https://github.com/vercel/next.js/) - your -feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the -[Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) -from the creators of Next.js. - -Check out our -[Next.js deployment documentation](https://nextjs.org/docs/deployment) for more -details. +If you have any issues using this examples, or Ory's products, don't hesitate to +reach out via the [Ory Community Slack](https://slack.ory.sh). diff --git a/examples/nextjs-pages-router/pages/api/hello.ts b/examples/nextjs-pages-router/pages/api/hello.ts deleted file mode 100644 index c4ea8558..00000000 --- a/examples/nextjs-pages-router/pages/api/hello.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2024 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -// Next.js API route support: https://nextjs.org/docs/api-routes/introduction -import type { NextApiRequest, NextApiResponse } from "next" - -interface Data { - name: string -} - -export default function handler( - _req: NextApiRequest, - res: NextApiResponse, -) { - res.status(200).json({ name: "John Doe" }) -} diff --git a/packages/nextjs/src/app/index.ts b/packages/nextjs/src/app/index.ts index 72de6a2d..5ad1fb39 100644 --- a/packages/nextjs/src/app/index.ts +++ b/packages/nextjs/src/app/index.ts @@ -7,6 +7,7 @@ export { getRegistrationFlow } from "./registration" export { getRecoveryFlow } from "./recovery" export { getVerificationFlow } from "./verification" export { getSettingsFlow } from "./settings" +export { getLogoutFlow } from "./logout" export { getServerSession } from "./session" export type { OryPageParams } from "./utils" diff --git a/packages/nextjs/src/app/logout.ts b/packages/nextjs/src/app/logout.ts new file mode 100644 index 00000000..4c0dcdf4 --- /dev/null +++ b/packages/nextjs/src/app/logout.ts @@ -0,0 +1,46 @@ +// Copyright © 2024 Ory Corp +// SPDX-License-Identifier: Apache-2.0 +import { LogoutFlow } from "@ory/client-fetch" + +import { headers } from "next/headers" +import { rewriteJsonResponse } from "../utils/rewrite" +import { guessPotentiallyProxiedOrySdkUrl } from "../utils/sdk" +import { serverSideFrontendClient } from "./client" +import { getPublicUrl } from "./utils" + +/** + * Use this method in an app router page to create a new logout flow. This method works with server-side rendering. + * + * ``` + * import { getLogoutFlow } from "@ory/nextjs/app" + * + * async function LogoutLink() { + * const flow = await getLogoutFlow() + * + * return ( + * + * Logout + * + * ) + * } + * + * ``` + * + * @param params - The query parameters of the request. + */ +export async function getLogoutFlow({ + returnTo, +}: { returnTo?: string } = {}): Promise { + const h = await headers() + + const knownProxiedUrl = await getPublicUrl() + const url = guessPotentiallyProxiedOrySdkUrl({ + knownProxiedUrl, + }) + return serverSideFrontendClient + .createBrowserLogoutFlow({ + cookie: h.get("cookie") ?? "", + returnTo, + }) + .then((v: LogoutFlow): LogoutFlow => rewriteJsonResponse(v, url)) +} diff --git a/packages/nextjs/src/middleware/middleware.test.ts b/packages/nextjs/src/middleware/middleware.test.ts index 890d877f..1ba0bd04 100644 --- a/packages/nextjs/src/middleware/middleware.test.ts +++ b/packages/nextjs/src/middleware/middleware.test.ts @@ -56,6 +56,7 @@ const createOptions = (): OryConfig => ({ forwardAdditionalHeaders: ["x-custom-header"], override: { loginUiPath: "/custom-login", + defaultRedirectUri: "/custom-redirect", }, }) @@ -175,6 +176,25 @@ describe("proxyRequest", () => { expect(response?.status).toBe(302) }) + it("modifies relative location header for redirects", async () => { + const request = createMockLoginRequest() + const upstreamResponseHeaders = new Headers({ + location: "/ui/welcome", + }) + + mockFetch({ + headers: upstreamResponseHeaders, + status: 302, + }) + + const response = await proxyRequest(request, createOptions()) + + expect(response?.headers.get("location")).toBe( + "http://localhost/custom-redirect", + ) + expect(response?.status).toBe(302) + }) + const createTestCase = ( part: "login" | "registration" | "recovery" | "verification" | "settings", ) => ({ diff --git a/packages/nextjs/src/middleware/middleware.ts b/packages/nextjs/src/middleware/middleware.ts index cfc7344d..31343fdf 100644 --- a/packages/nextjs/src/middleware/middleware.ts +++ b/packages/nextjs/src/middleware/middleware.ts @@ -94,6 +94,9 @@ export async function proxyRequest(request: NextRequest, options: OryConfig) { // This is not needed with the "new" account experience based on this SDK. if (location.startsWith("../self-service")) { location = location.replace("../self-service", "/self-service") + } else if (!location.startsWith("http")) { + // If the location header is not an absolute URL, we need to make it one for rewriteUrls to properly rewrite it. + location = new URL(location, matchBaseUrl).toString() } location = rewriteUrls(location, matchBaseUrl.toString(), selfUrl, options)