diff --git a/examples/with-iron-session/.env.development b/examples/with-iron-session/.env.development deleted file mode 100644 index f6249ac677..0000000000 --- a/examples/with-iron-session/.env.development +++ /dev/null @@ -1,6 +0,0 @@ -# ⚠️ The SECRET_COOKIE_PASSWORD should never be inside your repository directly, it's here only to ease -# the example deployment -# For local development, you should store it inside a `.env.local` gitignored file -# See https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables - -SECRET_COOKIE_PASSWORD=2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8 \ No newline at end of file diff --git a/examples/with-iron-session/.env.production b/examples/with-iron-session/.env.production deleted file mode 100644 index 5726908841..0000000000 --- a/examples/with-iron-session/.env.production +++ /dev/null @@ -1,6 +0,0 @@ -# ⚠️ The SECRET_COOKIE_PASSWORD should never be inside your repository directly, it's here only to ease -# the example deployment -# For production you should use https://vercel.com/blog/environment-variables-ui if you're hosted on Vercel or -# any other secret environment variable mean - -SECRET_COOKIE_PASSWORD=2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8 \ No newline at end of file diff --git a/examples/with-iron-session/README.md b/examples/with-iron-session/README.md index dcd0738f08..3bec3b4796 100644 --- a/examples/with-iron-session/README.md +++ b/examples/with-iron-session/README.md @@ -1,46 +1,3 @@ -# Example application using [`iron-session`](https://github.com/vvo/iron-session) +# Next.js + iron-session -

👀 Online demo at https://iron-session-example.vercel.app

- ---- - -This example creates an authentication system that uses a **signed and encrypted cookie to store session data**. It relies on [`iron-session`](https://github.com/vvo/iron-session). - -It uses current best practices for authentication in the Next.js ecosystem and replicates parts of how the Vercel dashboard is built. - -**Features of the example:** - -- [API Routes](https://nextjs.org/docs/api-routes/dynamic-api-routes) and [getServerSideProps](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props) examples. -- The logged in status is synchronized between browser windows/tabs using **`useUser`** hook and the [`swr`](https://swr.vercel.app/). -- The layout is based on the user's logged-in/out status. -- The session data is signed and encrypted in a cookie (this is done automatically by `iron-session`). - -[`iron-session`](https://github.com/vvo/iron-session) also provides: - -- An Express middleware, which can be used in any Node.js HTTP framework. -- Multiple encryption keys (passwords) to allow for seamless updates or just password rotation. -- Full TypeScript support, including session data. - -## Deploy your own - -Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-iron-session) - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-iron-session&project-name=with-iron-session&repository-name=with-iron-session) - -## How to use - -Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: - -```bash -npx create-next-app --example with-iron-session with-iron-session-app -``` - -```bash -yarn create next-app --example with-iron-session with-iron-session-app -``` - -```bash -pnpm create next-app --example with-iron-session with-iron-session-app -``` - -Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). +You can find the [iron-session example here](https://github.com/vvo/iron-session/tree/main/examples/next). diff --git a/examples/with-iron-session/components/Form.tsx b/examples/with-iron-session/components/Form.tsx deleted file mode 100644 index bf9c179835..0000000000 --- a/examples/with-iron-session/components/Form.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FormEvent } from "react"; - -export default function Form({ - errorMessage, - onSubmit, -}: { - errorMessage: string; - onSubmit: (e: FormEvent) => void; -}) { - return ( -
- - - - - {errorMessage &&

{errorMessage}

} - - -
- ); -} diff --git a/examples/with-iron-session/components/Header.tsx b/examples/with-iron-session/components/Header.tsx deleted file mode 100644 index 53a6a87fa0..0000000000 --- a/examples/with-iron-session/components/Header.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import Link from "next/link"; -import useUser from "lib/useUser"; -import { useRouter } from "next/router"; -import Image from "next/image"; -import fetchJson from "lib/fetchJson"; - -export default function Header() { - const { user, mutateUser } = useUser(); - const router = useRouter(); - - return ( -
- - -
- ); -} diff --git a/examples/with-iron-session/components/Layout.tsx b/examples/with-iron-session/components/Layout.tsx deleted file mode 100644 index 4091058ac3..0000000000 --- a/examples/with-iron-session/components/Layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import Head from "next/head"; -import Header from "components/Header"; - -export default function Layout({ children }: { children: React.ReactNode }) { - return ( - <> - - With Iron Session - - -
- -
-
{children}
-
- - ); -} diff --git a/examples/with-iron-session/lib/fetchJson.ts b/examples/with-iron-session/lib/fetchJson.ts deleted file mode 100644 index 416ba18329..0000000000 --- a/examples/with-iron-session/lib/fetchJson.ts +++ /dev/null @@ -1,52 +0,0 @@ -export class FetchError extends Error { - response: Response; - data: { - message: string; - }; - constructor({ - message, - response, - data, - }: { - message: string; - response: Response; - data: { - message: string; - }; - }) { - // Pass remaining arguments (including vendor specific ones) to parent constructor - super(message); - - // Maintains proper stack trace for where our error was thrown (only available on V8) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, FetchError); - } - - this.name = "FetchError"; - this.response = response; - this.data = data ?? { message: message }; - } -} - -export default async function fetchJson( - input: RequestInfo, - init?: RequestInit, -): Promise { - const response = await fetch(input, init); - - // if the server replies, there's always some data in json - // if there's a network error, it will throw at the previous line - const data = await response.json(); - - // response.ok is true when res.status is 2xx - // https://developer.mozilla.org/docs/Web/API/Response/ok - if (response.ok) { - return data; - } - - throw new FetchError({ - message: response.statusText, - response, - data, - }); -} diff --git a/examples/with-iron-session/lib/session.ts b/examples/with-iron-session/lib/session.ts deleted file mode 100644 index 8b6cd75e90..0000000000 --- a/examples/with-iron-session/lib/session.ts +++ /dev/null @@ -1,19 +0,0 @@ -// this file is a wrapper with defaults to be used in both API routes and `getServerSideProps` functions -import type { IronSessionOptions } from "iron-session"; -import type { User } from "pages/api/user"; - -export const sessionOptions: IronSessionOptions = { - password: process.env.SECRET_COOKIE_PASSWORD as string, - cookieName: "iron-session/examples/next.js", - // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) - cookieOptions: { - secure: process.env.NODE_ENV === "production", - }, -}; - -// This is where we specify the typings of req.session.* -declare module "iron-session" { - interface IronSessionData { - user?: User; - } -} diff --git a/examples/with-iron-session/lib/useEvents.ts b/examples/with-iron-session/lib/useEvents.ts deleted file mode 100644 index 0cfccc5048..0000000000 --- a/examples/with-iron-session/lib/useEvents.ts +++ /dev/null @@ -1,12 +0,0 @@ -import useSWR from "swr"; -import type { User } from "pages/api/user"; -import type { Events } from "pages/api/events"; - -export default function useEvents(user: User | undefined) { - // We do a request to /api/events only if the user is logged in - const { data: events } = useSWR( - user?.isLoggedIn ? `/api/events` : null, - ); - - return { events }; -} diff --git a/examples/with-iron-session/lib/useUser.ts b/examples/with-iron-session/lib/useUser.ts deleted file mode 100644 index 7ae91b7d62..0000000000 --- a/examples/with-iron-session/lib/useUser.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect } from "react"; -import Router from "next/router"; -import useSWR from "swr"; -import { User } from "pages/api/user"; - -export default function useUser({ - redirectTo = "", - redirectIfFound = false, -} = {}) { - const { data: user, mutate: mutateUser } = useSWR("/api/user"); - - useEffect(() => { - // if no redirect needed, just return (example: already on /dashboard) - // if user data not yet there (fetch in progress, logged in or not) then don't do anything yet - if (!redirectTo || !user) return; - - if ( - // If redirectTo is set, redirect if the user was not found. - (redirectTo && !redirectIfFound && !user?.isLoggedIn) || - // If redirectIfFound is also set, redirect if the user was found - (redirectIfFound && user?.isLoggedIn) - ) { - Router.push(redirectTo); - } - }, [user, redirectIfFound, redirectTo]); - - return { user, mutateUser }; -} diff --git a/examples/with-iron-session/next-env.d.ts b/examples/with-iron-session/next-env.d.ts deleted file mode 100644 index 4f11a03dc6..0000000000 --- a/examples/with-iron-session/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/with-iron-session/next.config.js b/examples/with-iron-session/next.config.js deleted file mode 100644 index a08a554e84..0000000000 --- a/examples/with-iron-session/next.config.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('next').NextConfig} */ -module.exports = { - images: { - remotePatterns: [ - { - protocol: "https", - hostname: "avatars.githubusercontent.com", - port: "", - pathname: "/my-account/**", - }, - ], - }, -}; diff --git a/examples/with-iron-session/package.json b/examples/with-iron-session/package.json deleted file mode 100644 index 8476dadb55..0000000000 --- a/examples/with-iron-session/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "private": true, - "scripts": { - "build": "next build", - "dev": "next dev", - "start": "next start" - }, - "dependencies": { - "iron-session": "latest", - "next": "latest", - "octokit": "^1.7.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "swr": "^2.0.0" - }, - "devDependencies": { - "@octokit/types": "^6.34.0", - "@types/react": "^17.0.34", - "typescript": "^4.4.4" - } -} diff --git a/examples/with-iron-session/pages/_app.tsx b/examples/with-iron-session/pages/_app.tsx deleted file mode 100644 index dab871322c..0000000000 --- a/examples/with-iron-session/pages/_app.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { AppProps } from "next/app"; -import { SWRConfig } from "swr"; -import fetchJson from "lib/fetchJson"; - -function MyApp({ Component, pageProps }: AppProps) { - return ( - { - console.error(err); - }, - }} - > - - - ); -} - -export default MyApp; diff --git a/examples/with-iron-session/pages/api/events.ts b/examples/with-iron-session/pages/api/events.ts deleted file mode 100644 index 8f3a83ef30..0000000000 --- a/examples/with-iron-session/pages/api/events.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "lib/session"; -import { Octokit } from "octokit"; - -import type { Endpoints } from "@octokit/types"; -import { NextApiRequest, NextApiResponse } from "next"; - -export type Events = - Endpoints["GET /users/{username}/events"]["response"]["data"]; - -const octokit = new Octokit(); - -async function eventsRoute(req: NextApiRequest, res: NextApiResponse) { - const user = req.session.user; - - if (!user || user.isLoggedIn === false) { - res.status(401).end(); - return; - } - - try { - const { data: events } = - await octokit.rest.activity.listPublicEventsForUser({ - username: user.login, - }); - - res.json(events); - } catch (error) { - res.status(200).json([]); - } -} - -export default withIronSessionApiRoute(eventsRoute, sessionOptions); diff --git a/examples/with-iron-session/pages/api/login.ts b/examples/with-iron-session/pages/api/login.ts deleted file mode 100644 index bd13276a41..0000000000 --- a/examples/with-iron-session/pages/api/login.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { User } from "./user"; - -import { Octokit } from "octokit"; -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "lib/session"; -import { NextApiRequest, NextApiResponse } from "next"; -const octokit = new Octokit(); - -async function loginRoute(req: NextApiRequest, res: NextApiResponse) { - const { username } = await req.body; - - try { - const { - data: { login, avatar_url }, - } = await octokit.rest.users.getByUsername({ username }); - - const user = { isLoggedIn: true, login, avatarUrl: avatar_url } as User; - req.session.user = user; - await req.session.save(); - res.json(user); - } catch (error) { - res.status(500).json({ message: (error as Error).message }); - } -} - -export default withIronSessionApiRoute(loginRoute, sessionOptions); diff --git a/examples/with-iron-session/pages/api/logout.ts b/examples/with-iron-session/pages/api/logout.ts deleted file mode 100644 index e5e370dc57..0000000000 --- a/examples/with-iron-session/pages/api/logout.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "lib/session"; -import { NextApiRequest, NextApiResponse } from "next"; -import type { User } from "pages/api/user"; - -function logoutRoute(req: NextApiRequest, res: NextApiResponse) { - req.session.destroy(); - res.json({ isLoggedIn: false, login: "", avatarUrl: "" }); -} - -export default withIronSessionApiRoute(logoutRoute, sessionOptions); diff --git a/examples/with-iron-session/pages/api/user.ts b/examples/with-iron-session/pages/api/user.ts deleted file mode 100644 index aa9048a082..0000000000 --- a/examples/with-iron-session/pages/api/user.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "lib/session"; -import { NextApiRequest, NextApiResponse } from "next"; - -export type User = { - isLoggedIn: boolean; - login: string; - avatarUrl: string; -}; - -async function userRoute(req: NextApiRequest, res: NextApiResponse) { - if (req.session.user) { - // in a real world application you might read the user id from the session and then do a database request - // to get more information on the user if needed - res.json({ - ...req.session.user, - isLoggedIn: true, - }); - } else { - res.json({ - isLoggedIn: false, - login: "", - avatarUrl: "", - }); - } -} - -export default withIronSessionApiRoute(userRoute, sessionOptions); diff --git a/examples/with-iron-session/pages/index.tsx b/examples/with-iron-session/pages/index.tsx deleted file mode 100644 index e890905e6a..0000000000 --- a/examples/with-iron-session/pages/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Layout from "components/Layout"; -import Image from "next/image"; - -export default function Home() { - return ( - -

- - - - iron-session - - Authentication example -

- -

- This example creates an authentication system that uses a{" "} - signed and encrypted cookie to store session data. -

- -

- It uses current best practices as for authentication in the Next.js - ecosystem: -
- 1. no `getInitialProps` to ensure every page is static -
- 2. `useUser` hook together with ` - swr` for data fetching -

- -

Features

- -
    -
  • Logged in status synchronized between browser windows/tabs
  • -
  • Layout based on logged in status
  • -
  • All pages are static
  • -
  • Session data is signed and encrypted in a cookie
  • -
- -

Steps to test the functionality:

- -
    -
  1. Click login and enter your GitHub username.
  2. -
  3. - Click home and click profile again, notice how your session is being - used through a token stored in a cookie. -
  4. -
  5. - Click logout and try to go to profile again. You'll get - redirected to the `/login` route. -
  6. -
- -
- ); -} diff --git a/examples/with-iron-session/pages/login.tsx b/examples/with-iron-session/pages/login.tsx deleted file mode 100644 index 9ad8053672..0000000000 --- a/examples/with-iron-session/pages/login.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useState } from "react"; -import useUser from "lib/useUser"; -import Layout from "components/Layout"; -import Form from "components/Form"; -import fetchJson, { FetchError } from "lib/fetchJson"; - -export default function Login() { - // here we just check if user is already logged in and redirect to profile - const { mutateUser } = useUser({ - redirectTo: "/profile-sg", - redirectIfFound: true, - }); - - const [errorMsg, setErrorMsg] = useState(""); - - return ( - -
-
-
- -
- ); -} diff --git a/examples/with-iron-session/pages/profile-sg.tsx b/examples/with-iron-session/pages/profile-sg.tsx deleted file mode 100644 index fdd0525de3..0000000000 --- a/examples/with-iron-session/pages/profile-sg.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from "react"; -import Layout from "components/Layout"; -import useUser from "lib/useUser"; -import useEvents from "lib/useEvents"; - -// Make sure to check https://nextjs.org/docs/basic-features/layouts for more info on how to use layouts -export default function SgProfile() { - const { user } = useUser({ - redirectTo: "/login", - }); - const { events } = useEvents(user); - - return ( - -

Your GitHub profile

-

- This page uses{" "} - - Static Generation (SG) - {" "} - and the /api/user route (using{" "} - vercel/SWR) -

- {user && ( - <> -

- Public data, from{" "} - - https://github.com/{user.login} - - , reduced to `login` and `avatar_url`. -

- -
{JSON.stringify(user, null, 2)}
- - )} - - {events !== undefined && ( -

- Number of GitHub events for user: {events.length}.{" "} - {events.length > 0 && ( - <> - Last event type: {events[0].type} - - )} -

- )} -
- ); -} diff --git a/examples/with-iron-session/pages/profile-ssr.tsx b/examples/with-iron-session/pages/profile-ssr.tsx deleted file mode 100644 index 1bdd5732b5..0000000000 --- a/examples/with-iron-session/pages/profile-ssr.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React from "react"; -import Layout from "components/Layout"; -import { withIronSessionSsr } from "iron-session/next"; -import { sessionOptions } from "lib/session"; -import { User } from "pages/api/user"; - -import { InferGetServerSidePropsType } from "next"; - -export default function SsrProfile({ - user, -}: InferGetServerSidePropsType) { - return ( - -

Your GitHub profile

-

- This page uses{" "} - - Server-side Rendering (SSR) - {" "} - and{" "} - - getServerSideProps - -

- - {user?.isLoggedIn && ( - <> -

- Public data, from{" "} - - https://github.com/{user.login} - - , reduced to `login` and `avatar_url`. -

-
{JSON.stringify(user, null, 2)}
- - )} -
- ); -} - -export const getServerSideProps = withIronSessionSsr(async function ({ - req, - res, -}) { - const user = req.session.user; - - if (user === undefined) { - res.setHeader("location", "/login"); - res.statusCode = 302; - res.end(); - return { - props: { - user: { isLoggedIn: false, login: "", avatarUrl: "" } as User, - }, - }; - } - - return { - props: { user: req.session.user }, - }; -}, -sessionOptions); diff --git a/examples/with-iron-session/public/GitHub-Mark-32px.png b/examples/with-iron-session/public/GitHub-Mark-32px.png deleted file mode 100644 index 8b25551a97..0000000000 Binary files a/examples/with-iron-session/public/GitHub-Mark-32px.png and /dev/null differ diff --git a/examples/with-iron-session/public/GitHub-Mark-Light-32px.png b/examples/with-iron-session/public/GitHub-Mark-Light-32px.png deleted file mode 100644 index 628da97c70..0000000000 Binary files a/examples/with-iron-session/public/GitHub-Mark-Light-32px.png and /dev/null differ diff --git a/examples/with-iron-session/public/favicon.ico b/examples/with-iron-session/public/favicon.ico deleted file mode 100644 index 718d6fea48..0000000000 Binary files a/examples/with-iron-session/public/favicon.ico and /dev/null differ diff --git a/examples/with-iron-session/tsconfig.json b/examples/with-iron-session/tsconfig.json deleted file mode 100644 index 1b5d22086e..0000000000 --- a/examples/with-iron-session/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "baseUrl": ".", - "incremental": true - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "next.config.js"], - "exclude": ["node_modules"] -} diff --git a/examples/with-iron-session/vercel.json b/examples/with-iron-session/vercel.json deleted file mode 100644 index e2035badc4..0000000000 --- a/examples/with-iron-session/vercel.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "env": { - "SECRET_COOKIE_PASSWORD": "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8" - }, - "build": { - "env": { - "SECRET_COOKIE_PASSWORD": "2gyZ3GDw3LHZQKDhPmPDL3sjREVRXPr8" - } - }, - "github": { - "silent": true - } -}