diff --git a/docs/content/docs/integrations/next.mdx b/docs/content/docs/integrations/next.mdx index f0a20562bc..1ecf7d4569 100644 --- a/docs/content/docs/integrations/next.mdx +++ b/docs/content/docs/integrations/next.mdx @@ -154,4 +154,22 @@ export async function ServerComponent() { ) } -``` \ No newline at end of file +``` + + +## Middleware + +You can use the `authMiddleware` to protect your routes. It's a wrapper around the Next.js middleware. + +```ts twoslash title="middleware.ts" + +import { authMiddleware } from "better-auth/next-js" + +export default authMiddleware({ + redirectTo: "/sign-in" // redirect to this path if the user is not authenticated +}) + +export const config = { + matcher: ['/dashboard/:path*'], +} +``` diff --git a/examples/next-js/src/app/api/[...auth]/route.ts b/examples/next-js/src/app/api/[...auth]/route.ts index 0227d00022..7cbe91bb5a 100644 --- a/examples/next-js/src/app/api/[...auth]/route.ts +++ b/examples/next-js/src/app/api/[...auth]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@/lib/_auth"; +import { auth } from "@/lib/auth"; import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth); diff --git a/examples/next-js/src/app/page.tsx b/examples/next-js/src/app/page.tsx index b2550ce14c..fb025e9a82 100644 --- a/examples/next-js/src/app/page.tsx +++ b/examples/next-js/src/app/page.tsx @@ -1,6 +1,6 @@ import { Organization } from "@/components/organization"; import UserCard from "@/components/user-card"; -import { auth } from "@/lib/_auth"; +import { auth } from "@/lib/auth"; import { headers } from "next/headers"; export default async function TypewriterEffectSmoothDemo() { diff --git a/examples/next-js/src/components/organization.tsx b/examples/next-js/src/components/organization.tsx index afa47899f6..4539733428 100644 --- a/examples/next-js/src/components/organization.tsx +++ b/examples/next-js/src/components/organization.tsx @@ -212,7 +212,6 @@ export function Organization() { ))} -
diff --git a/examples/next-js/src/lib/auth-client.ts b/examples/next-js/src/lib/auth-client.ts index 919aa1353f..43eba668ba 100644 --- a/examples/next-js/src/lib/auth-client.ts +++ b/examples/next-js/src/lib/auth-client.ts @@ -5,12 +5,8 @@ import { passkeyClient, usernameClient, } from "better-auth/client/plugins"; -import { ac } from "./permissions"; export const authClient = createAuthClient({ - fetchOptions: { - baseURL: "http://localhost:3000/api/auth", - }, plugins: [ organizationClient(), twoFactorClient({ diff --git a/examples/next-js/src/lib/auth.config.ts b/examples/next-js/src/lib/auth.config.ts deleted file mode 100644 index 9cc78848da..0000000000 --- a/examples/next-js/src/lib/auth.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { betterAuth } from "better-auth"; -import { twoFactor, organization, passkey } from "better-auth/plugins"; - -export const auth = betterAuth({ - database: { - provider: "sqlite", - url: "./prisma/sqlite.db", - }, - plugins: [ - twoFactor({ - issuer: "my app", - }), - organization(), - passkey({ - rpID: "localhost", - rpName: "BetterAuth", - origin: "http://localhost:3000", - }), - ], -}); diff --git a/examples/next-js/src/lib/_auth.ts b/examples/next-js/src/lib/auth.ts similarity index 96% rename from examples/next-js/src/lib/_auth.ts rename to examples/next-js/src/lib/auth.ts index 30f1dfe190..a2537901d9 100644 --- a/examples/next-js/src/lib/_auth.ts +++ b/examples/next-js/src/lib/auth.ts @@ -9,7 +9,6 @@ import { github, google } from "better-auth/social-providers"; import { ac, admin } from "./permissions"; export const auth = betterAuth({ - basePath: "/api/auth", socialProvider: [ github({ clientId: process.env.GITHUB_CLIENT_ID as string, @@ -51,7 +50,7 @@ export const auth = betterAuth({ }), passkey({ rpID: "localhost", - rpName: "BetterAuth", + rpName: "better-auth", origin: "http://localhost:3000", }), username(), diff --git a/examples/next-js/src/lib/types.ts b/examples/next-js/src/lib/types.ts index 6b2ea06a51..d769534ad7 100644 --- a/examples/next-js/src/lib/types.ts +++ b/examples/next-js/src/lib/types.ts @@ -1,5 +1,5 @@ import type { InferSession, InferUser } from "better-auth/types"; -import type { auth } from "./_auth"; +import type { auth } from "./auth"; export type User = InferUser; export type Session = InferSession; diff --git a/examples/next-js/src/middleware.ts b/examples/next-js/src/middleware.ts index 5e0f43d9ee..6b585720c4 100644 --- a/examples/next-js/src/middleware.ts +++ b/examples/next-js/src/middleware.ts @@ -1,25 +1,10 @@ -import { NextResponse } from "next/server"; -import type { NextRequest } from "next/server"; -import { authClient } from "./lib/auth-client"; +import { authMiddleware } from "better-auth/next-js"; +import { NextRequest } from "next/server"; -export async function middleware(request: NextRequest) { - const session = await authClient.session({ - options: { - headers: request.headers, - }, - }); - if (!session.data) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - const canInvite = await authClient.organization.hasPermission({ - permission: { - invitation: ["create"], - }, - options: { - headers: request.headers, - }, - }); - return NextResponse.next(); +export default async function middleware(request: NextRequest) { + const res = await authMiddleware({ + redirectTo: "/sign-in", + })(request); } export const config = { diff --git a/packages/better-auth/src/auth.ts b/packages/better-auth/src/auth.ts index 2a72363658..948d6466b3 100644 --- a/packages/better-auth/src/auth.ts +++ b/packages/better-auth/src/auth.ts @@ -26,7 +26,7 @@ export const betterAuth = (options: O) => { return handler(request); }, api, - options: authContext.options, + options: authContext.options as O, }; }; diff --git a/packages/better-auth/src/integrations/next-js.ts b/packages/better-auth/src/integrations/next-js.ts index 58f002c19c..babb97cb36 100644 --- a/packages/better-auth/src/integrations/next-js.ts +++ b/packages/better-auth/src/integrations/next-js.ts @@ -17,51 +17,21 @@ export function toNextJsHandler(auth: Auth | Auth["handler"]) { * Middleware that checks if the user is authenticated. * If not, it redirects to the redirectTo URL. */ -export async function authMiddleware( - auth: T, - options: { - matcher: (request: NextRequest) => - | Array<{ - redirectTo: string; - shouldRedirect: boolean; - }> - | Promise< - Array<{ - redirectTo: string; - shouldRedirect: boolean; - }> - > - | { - redirectTo: string; - shouldRedirect: boolean; - } - | Promise<{ - redirectTo: string; - shouldRedirect: boolean; - }>; - }, -) { - return async (request: NextRequest) => { - let redirection = await options.matcher(request); - if (!Array.isArray(redirection)) { - redirection = [redirection]; - } - for (const { shouldRedirect, redirectTo } of redirection) { - console.log({ shouldRedirect, redirectTo }); - if (shouldRedirect) { - const url = new URL(request.url).origin; - const basePath = auth.options.basePath || "/api/auth"; - const fullURL = `${url}${basePath}/session`; - const res = await betterFetch<{ - session: Session; - }>(fullURL, { - headers: request.headers, - }); - - if (!res.data?.session) { - return NextResponse.redirect(new URL(redirectTo, request.url)); - } - } +export function authMiddleware(options: { + baePath?: string; + redirectTo: string; +}) { + return async (request: Request) => { + const url = new URL(request.url).origin; + const basePath = options?.baePath || "/api/auth"; + const fullURL = `${url}${basePath}/session`; + const res = await betterFetch<{ + session: Session; + }>(fullURL, { + headers: request.headers, + }); + if (!res.data) { + return NextResponse.redirect(new URL(options.redirectTo, url)); } return NextResponse.next(); };