next-auth
Version:
Authentication for Next.js
244 lines (209 loc) • 7.26 kB
text/typescript
import type { NextMiddleware, NextFetchEvent } from "next/server"
import type { Awaitable, CookieOption, AuthOptions } from ".."
import type { JWT, JWTOptions } from "../jwt"
import { NextResponse, NextRequest } from "next/server"
import { getToken } from "../jwt"
import parseUrl from "../utils/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?: AuthOptions["pages"]
/**
* You can override the default cookie names and options for any of the cookies
* by this middleware. Similar to `cookies` in `NextAuth`.
*
* Useful if the token is stored in not a default cookie.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/options#cookies)
*
* - ⚠ **This is an advanced option.** Advanced options are passed the same way as basic options,
* but **may have complex implications** or side effects.
* You should **try to avoid using advanced options** unless you are very comfortable using them.
*
*/
cookies?: Partial<
Record<
keyof Pick<keyof AuthOptions["cookies"], "sessionToken">,
Omit<CookieOption, "options">
>
>
/**
* If a custom jwt `decode` method is set in `[...nextauth].ts`, the same method should be set here also.
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#custom-jwt-decode-method)
*/
jwt?: Partial<Pick<JWTOptions, "decode">>
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
* // `middleware.js`
* import { withAuth } from "next-auth/middleware"
*
* export default withAuth({
* callbacks: {
* authorized: ({ token }) => token?.user.isAdmin
* }
* })
*
* export const config = { matcher: ["/admin"] }
*
* ```
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware) | [`signIn` callback](configuration/callbacks#sign-in-callback)
*/
authorized?: AuthorizedCallback
}
/**
* The same `secret` used in the `NextAuth` configuration.
* Defaults to the `NEXTAUTH_SECRET` environment variable.
*/
secret?: string
}
// TODO: `NextMiddleware` should allow returning `void`
// Simplify when https://github.com/vercel/next.js/pull/38625 is merged.
type NextMiddlewareResult = ReturnType<NextMiddleware> | void // eslint-disable-line @typescript-eslint/no-invalid-void-type
async function handleMiddleware(
req: NextRequest,
options: NextAuthMiddlewareOptions | undefined,
onSuccess?: (token: JWT | null) => Promise<NextMiddlewareResult>
) {
const { pathname, search, origin, basePath } = req.nextUrl
const signInPage = options?.pages?.signIn ?? "/api/auth/signin"
const errorPage = options?.pages?.error ?? "/api/auth/error"
const authPath = parseUrl(process.env.NEXTAUTH_URL).path
const publicPaths = ["/_next", "/favicon.ico"]
// Avoid infinite redirects/invalid response
// on paths that never require authentication
if (
`${basePath}${pathname}`.startsWith(authPath) ||
[signInPage, errorPage].includes(pathname) ||
publicPaths.some((p) => pathname.startsWith(p))
) {
return
}
const secret =
options?.secret ?? process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET
if (!secret) {
console.error(
`[next-auth][error][NO_SECRET]`,
`\nhttps://next-auth.js.org/errors#no_secret`
)
const errorUrl = new URL(`${basePath}${errorPage}`, origin)
errorUrl.searchParams.append("error", "Configuration")
return NextResponse.redirect(errorUrl)
}
const token = await getToken({
req,
decode: options?.jwt?.decode,
cookieName: options?.cookies?.sessionToken?.name,
secret,
})
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, redirect to the sign-in page
const signInUrl = new URL(`${basePath}${signInPage}`, origin)
signInUrl.searchParams.append(
"callbackUrl",
`${basePath}${pathname}${search}`
)
return NextResponse.redirect(signInUrl)
}
export interface NextRequestWithAuth extends NextRequest {
nextauth: { token: JWT | null }
}
export type NextMiddlewareWithAuth = (
request: NextRequestWithAuth,
event: NextFetchEvent
) => NextMiddlewareResult | Promise<NextMiddlewareResult>
export type WithAuthArgs =
| [NextRequestWithAuth]
| [NextRequestWithAuth, NextFetchEvent]
| [NextRequestWithAuth, NextAuthMiddlewareOptions]
| [NextMiddlewareWithAuth]
| [NextMiddlewareWithAuth, 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
* // `middleware.js`
* export { default } from "next-auth/middleware"
* ```
*
* ---
* [Documentation](https://next-auth.js.org/configuration/nextjs#middleware)
*/
export function withAuth(): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth,
event: NextFetchEvent
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
req: NextRequestWithAuth,
options: NextAuthMiddlewareOptions
): ReturnType<NextMiddlewareWithAuth>
export function withAuth(
middleware: NextMiddlewareWithAuth,
options: NextAuthMiddlewareOptions
): NextMiddlewareWithAuth
export function withAuth(
middleware: NextMiddlewareWithAuth
): NextMiddlewareWithAuth
export function withAuth(
options: NextAuthMiddlewareOptions
): NextMiddlewareWithAuth
export function withAuth(
...args: WithAuthArgs
): ReturnType<NextMiddlewareWithAuth> | NextMiddlewareWithAuth {
if (!args.length || args[0] instanceof Request) {
// @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<NextMiddlewareWithAuth>) =>
await handleMiddleware(args[0], options, async (token) => {
args[0].nextauth = { token }
return await middleware(...args)
})
}
const options = args[0]
return async (...args: Parameters<NextMiddleware>) =>
await handleMiddleware(args[0], options)
}
export default withAuth