Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b496866
feat(middleware): introduce Middleware API to Next.js
balazsorban44 Jan 18, 2022
13ce4de
chore(app): upgrade Next.js in dev app
balazsorban44 Jan 18, 2022
d1337a5
chore(dev): add Middleware protected page to dev app
balazsorban44 Jan 18, 2022
f9be36b
chore(middleware): add `next/middleware` to `exports`
balazsorban44 Jan 18, 2022
061e7ba
fix(middleware): bail out redirect on custom pages
balazsorban44 Jan 18, 2022
f4b7733
fix(middleware): allow one-line export
balazsorban44 Jan 18, 2022
ea16ed8
chore(middleware): simplify code
balazsorban44 Jan 18, 2022
96d07ab
fix(middleware): redirect back to page after succesful login
balazsorban44 Jan 18, 2022
6c27a82
feat(middleware): re-export `withAuth` as `default`
balazsorban44 Jan 18, 2022
a7ca9e3
chore: export middleware from `next-auth/middleware`
balazsorban44 Jan 19, 2022
9c44d13
chore: add `middleware` files to npm
balazsorban44 Jan 19, 2022
e32e782
feat(middleware): handle chaining, fix some bugs
balazsorban44 Jan 21, 2022
e767e0e
chore(dev): showcase different middlewares
balazsorban44 Jan 21, 2022
c3d58cb
chore(middleware): remove `@ts-expect-error` comments
balazsorban44 Jan 21, 2022
2976116
chore: update build clean script
balazsorban44 Jan 21, 2022
4fb5cf7
Merge branch 'main' into feat/with-auth-middleware
balazsorban44 Feb 1, 2022
eb36ed5
fix: bail out when NextAuth.js paths
balazsorban44 Feb 2, 2022
cb0ba9e
refactor: be more explicit about `initConfig` result
balazsorban44 Feb 2, 2022
12002aa
Merge branch 'main' into feat/with-auth-middleware
balazsorban44 Feb 2, 2022
1a3aac5
refactor: simplify
balazsorban44 Feb 2, 2022
5db7cc7
refactor: use `callbacks` similarily to `NextAuthOptions`
balazsorban44 Feb 2, 2022
f28ad9f
refactor: use `nextauth` namespace when setting `token` on `req`
balazsorban44 Feb 2, 2022
0f23b3d
Merge branch 'main' into feat/with-auth-middleware
balazsorban44 Feb 2, 2022
f51be7c
Merge branch 'main' into feat/with-auth-middleware
balazsorban44 Feb 3, 2022
1b99fba
refactor: don't allow passing `secret`
balazsorban44 Feb 3, 2022
817b2cf
Merge branch 'feat/with-auth-middleware' of github.com:nextauthjs/nex…
balazsorban44 Feb 3, 2022
4a95e1c
addressing review
balazsorban44 Feb 3, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ node_modules
/index.d.ts
/index.js
/next
/middleware.d.ts
/middleware.js

# Development app
app/src/css
Expand Down
5 changes: 5 additions & 0 deletions app/components/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ export default function Header() {
<a>Email</a>
</Link>
</li>
<li className={styles.navItem}>
<Link href="/middleware-protected">
<a>Middleware protected</a>
</Link>
</li>
</ul>
</nav>
</header>
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@prisma/client": "^3.7.0",
"fake-smtp-server": "^0.8.0",
"faunadb": "^4.4.1",
"next": "^12.0.7",
"next": "^12.0.8",
"nodemailer": "^6.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2"
Expand Down
44 changes: 44 additions & 0 deletions app/pages/middleware-protected/_middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export { default } from "next-auth/middleware"

// Other ways to use this middleware

// import withAuth from "next-auth/middleware"
// import { withAuth } from "next-auth/middleware"

// export function middleware(req, ev) {
// return withAuth(req)
// }

// export function middleware(req, ev) {
// return withAuth(req, ev)
// }

// export function middleware(req, ev) {
// return withAuth(req, {
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })
// }

// export default withAuth(function middleware(req, ev) {
// console.log(req.nextauth.token)
// })

// export default withAuth(
// function middleware(req, ev) {
// console.log(req, ev)
// return undefined // NOTE: `NextMiddleware` should allow returning `void`
// },
// {
// callbacks: {
// authorized: ({ token }) => token.name === "Balázs Orbán",
// }
// }
// )

// export default withAuth({
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })
9 changes: 9 additions & 0 deletions app/pages/middleware-protected/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Layout from "components/layout"

export default function Page() {
return (
<Layout>
<h1>Page protected by Middleware</h1>
</Layout>
)
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@
"./react": "./react/index.js",
"./core": "./core/index.js",
"./next": "./next/index.js",
"./middleware": "./middleware.js",
"./client/_utils": "./client/_utils.js",
"./providers/*": "./providers/*.js"
},
"scripts": {
"build": "npm run build:js && npm run build:css",
"clean": "rm -rf client css lib providers core jwt react next index.d.ts index.js adapters.d.ts",
"clean": "rm -rf client css lib providers core jwt react next index.d.ts index.js adapters.d.ts middleware.d.ts middleware.js",
"build:js": "npm run clean && npm run generate-providers && tsc && babel --config-file ./config/babel.config.js src --out-dir . --extensions \".tsx,.ts,.js,.jsx\"",
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir . && node config/wrap-css.js",
"dev:setup": "npm i && npm run generate-providers && npm run build:css && cd app && npm i",
Expand All @@ -61,7 +62,9 @@
"core",
"index.d.ts",
"index.js",
"adapters.d.ts"
"adapters.d.ts",
"middleware.d.ts",
"middleware.js"
],
"license": "ISC",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/jwt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export async function decode(params: JWTDecodeParams): Promise<JWT | null> {

export interface GetTokenParams<R extends boolean = false> {
/** The request containing the JWT either in the cookies or in the `Authorization` header. */
req: NextApiRequest
req: NextApiRequest | Pick<NextApiRequest, "cookies" | "headers">
/**
* Use secure prefix for cookie name, unless URL in `NEXTAUTH_URL` is http://
* or not set (e.g. development or test instance) case use unprefixed name
Expand Down
1 change: 1 addition & 0 deletions src/lib/parse-url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface InternalUrl {
toString: () => string
}

/** Returns an `URL` like object to make requests/redirects from server-side */
export default function parseUrl(url?: string): InternalUrl {
const defaultUrl = new URL("http://localhost:3000/api/auth")

Expand Down
2 changes: 2 additions & 0 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from "./next/middleware"
export * from "./next/middleware"
141 changes: 141 additions & 0 deletions src/next/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { NextMiddleware, NextFetchEvent } from "next/server"
import type { Awaitable, NextAuthOptions } from ".."
import type { JWT } from "../jwt"

import { NextResponse, NextRequest } from "next/server"

import { getToken } from "../jwt"
import parseUrl from "../lib/parse-url"

type AuthorizedCallback = (params: {
token: JWT | null
req: NextRequest
}) => Awaitable<boolean>

export interface NextAuthMiddlewareOptions {
/**
* Where to redirect the user in case of an error if they weren't logged in.
* Similar to `pages` in `NextAuth`.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/pages)
*/
pages?: NextAuthOptions["pages"]
callbacks?: {
/**
* Callback that receives the user's JWT payload
* and returns `true` to allow the user to continue.
*
* This is similar to the `signIn` callback in `NextAuthOptions`.
*
* If it returns `false`, the user is redirected to the sign-in page instead
*
* The default is to let the user continue if they have a valid JWT (basic authentication).
*
* How to restrict a page and all of it's subpages for admins-only:
* @example
*
* ```js
* // `pages/admin/_middleware.js`
* import { withAuth } from "next-auth/middleware"
*
* export default withAuth({
* callbacks: {
* authorized: ({ token }) => token?.user.isAdmin
* }
* })
* ```
*
* ---
* [Documentation](https://next-auth.js.org/getting-started/nextjs/middleware#api) | [`signIn` callback](configuration/callbacks#sign-in-callback)
*/
authorized?: AuthorizedCallback
}
}

async function handleMiddleware(
req: NextRequest,
options: NextAuthMiddlewareOptions | undefined,
onSuccess?: (token: JWT | null) => Promise<any>
) {
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
const errorPage = options?.pages?.error ?? "/api/auth/error"
const basePath = parseUrl(process.env.NEXTAUTH_URL).path
// Avoid infinite redirect loop
if (
req.nextUrl.pathname.startsWith(basePath) ||
[signInPage, errorPage].includes(req.nextUrl.pathname)
) {
return
}

if (!process.env.NEXTAUTH_SECRET) {
console.error(
`[next-auth][error][NO_SECRET]`,
`\nhttps://next-auth.js.org/errors#no_secret`
)

return {
redirect: NextResponse.redirect(`${errorPage}?error=Configuration`),
}
}

const token = await getToken({ req: req as any })

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@balazsorban44 Does this middleware will work with strategy: 'database'?

I have similar middleware, but with getSession request, because 'getToken' was null if i remember correct...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Only supports the "jwt" session strategy. We need to wait until databases at the Edge become mature enough to ensure a fast experience. (If you know of an Edge-compatible database, we would like if you proposed a new Adapter)"

caveats

mind sharing your sessions middleware function?  👀

Copy link

@sergeyshaykhullin sergeyshaykhullin Feb 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is pretty simple

export async function middleware(request: NextRequest) {

  const cookie = request.headers.get('cookie')

  const session = cookie
    ? await getSession({ req: { headers: { cookie } } as any })
    : null

  if (!session) {
    return NextResponse.redirect(new URL('/api/auth/signin', request.url))
  }
}


const isAuthorized =
(await options?.callbacks?.authorized?.({ req, token })) ?? !!token

// the user is authorized, let the middleware handle the rest
if (isAuthorized) return await onSuccess?.(token)

// the user is not logged in, re-direct to the sign-in page
return NextResponse.redirect(
`${signInPage}?${new URLSearchParams({ callbackUrl: req.url })}`
)
}

export type WithAuthArgs =
| [NextRequest]
| [NextRequest, NextFetchEvent]
| [NextRequest, NextAuthMiddlewareOptions]
| [NextMiddleware]
| [NextMiddleware, NextAuthMiddlewareOptions]
| [NextAuthMiddlewareOptions]

/**
* Middleware that checks if the user is authenticated/authorized.
* If if they aren't, they will be redirected to the login page.
* Otherwise, continue.
*
* @example
*
* ```js
* // `pages/_middleware.js`
* export { default } from "next-auth/middleware"
* ```
*
* ---
* [Documentation](https://next-auth.js.org/getting-started/middleware)
*/
export function withAuth(...args: WithAuthArgs) {
if (args[0] instanceof NextRequest) {
// @ts-expect-error
return handleMiddleware(...args)
}

if (typeof args[0] === "function") {
const middleware = args[0]
const options = args[1] as NextAuthMiddlewareOptions | undefined
return async (...args: Parameters<NextMiddleware>) =>
await handleMiddleware(args[0], options, async (token) => {
;(args[0] as any).nextauth = { token }
return await middleware(...args)
})
}

const options = args[0]
return async (...args: Parameters<NextMiddleware>) =>
await handleMiddleware(args[0], options)
}

export default withAuth