登录
首页 >  文章 >  前端

在 Nextjs App Router 中使用 Authjs 进行用户身份验证

来源:dev.to

时间:2024-11-21 18:49:09 494浏览 收藏

golang学习网今天将给大家带来《在 Nextjs App Router 中使用 Authjs 进行用户身份验证》,感兴趣的朋友请继续看下去吧!以下内容将会涉及到等等知识点,如果你是正在学习文章或者已经是大佬级别了,都非常欢迎也希望大家都能给我建议评论哈~希望能帮助到大家!

目录

初始设置

  • 安装
  • 配置
    • nextauthconfig 设置
    • 路由处理程序设置
    • 中间件
    • 在服务器端组件中获取会话
    • 在客户端组件中获取会话
  • 文件夹结构

实施身份验证:凭据和 google oauth

  • 设置 prisma
  • 凭证
  • 添加 google oauth 提供商
    • 设置 google oauth 应用程序
    • 设置重定向 uri
    • 设置环境变量
    • 设置提供商
  • 创建登录和注册页面
  • 文件夹结构

初始设置

安装

npm install next-auth@beta
// env.local
auth_secret=generatetd_random_value

配置

nextauthconfig 设置

// src/auth.ts
import nextauth from "next-auth"

export const config = {
  providers: [],
}

export const { handlers, signin, signout, auth } = nextauth(config)

它应该放在src文件夹内

providers 在 auth.js 中表示是可用于登录用户的服务。用户可以通过四种方式登录。

  • 使用内置的 oauth 提供程序(例如 github、google 等...)
  • 使用自定义 oauth 提供程序
  • 使用电子邮件
  • 使用凭证

https://authjs.dev/reference/nextjs#providers

路由处理程序设置

// src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth" // referring to the auth.ts we just created
export const { get, post } = handlers

此文件用于使用 next.js app router 设置路由处理程序。

中间件

// src/middleware.ts
import { auth } from "@/auth"

export default auth((req) => {
  // add your logic here
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], //  it's default setting
}

在src文件夹内写入
如果写在 src 文件夹之外,中间件将无法工作。

中间件是一个允许您在请求完成之前运行代码的函数。它对于保护路由和处理整个应用程序的身份验证特别有用。

matcher一个配置选项,用于指定哪些路由中间件应应用于。它有助于仅在必要的路由上运行中间件来优化性能
示例匹配器: ['/dashboard/:path*'] 仅将中间件应用于仪表板路由。

https://authjs.dev/getting-started/session-management/protecting?framework=express#nextjs-middleware

在服务器端组件中获取会话

// src/app/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"

export default async function page() {
  const session = await auth()

  if (!session) {
    redirect('/login')
  }

  return (
    

hello world!

user avatar
) }

在客户端组件中获取会话

// src/app/page.tsx
"use client"
import { usesession } from "next-auth/react"
import { userouter } from "next/navigation"

export default async function page() {
  const { data: session } = usesession()
  const router = userouter()

  if (!session.user) {
    router.push('/login')
  }

  return (
    

hello world!

user avatar
) } // src/app/layout.tsx import type { metadata } from "next"; import "./globals.css"; import { sessionprovider } from "next-auth/react" export const metadata: metadata = { title: "create next app", description: "generated by create next app", }; export default function rootlayout({ children, }: readonly<{ children: react.reactnode; }>) { return ( {children} ); }

文件夹结构

/src
  /app
    /api
      /auth
        [...nextauth]
          /route.ts  // route handler
    layout.tsx
    page.tsx

  auth.ts  // provider, callback, logic etc
  middleware.ts  // a function before request

实施身份验证:凭据和 google oauth

设置棱镜

// prisma/schema.prisma

model user {
  id            string    @id @default(cuid())
  name          string?
  email         string?   @unique
  emailverified datetime?
  image         string?
  password      string?
  accounts      account[]
  sessions      session[]
}

model account {
  // ... (standard auth.js account model)
}

model session {
  // ... (standard auth.js session model)
}

// ... (other necessary models)

// src/lib/prisma.ts

import { prismaclient } from "@prisma/client"

const globalforprisma = globalthis as unknown as { prisma: prismaclient }

export const prisma = globalforprisma.prisma || new prismaclient()

if (process.env.node_env !== "production") globalforprisma.prisma = prisma

证书

凭证,在身份验证的上下文中,指的是使用用户提供的信息验证用户身份的方法,通常是用户名(或电子邮件)和密码。

我们可以在 src/auth.ts 中添加凭据。

// src/auth.ts

import nextauth from "next-auth";
import type { nextauthconfig } from "next-auth";
import credentials from "next-auth/providers/credentials"
import { prismaadapter } from "@auth/prisma-adapter"
import { prisma } from "@/lib/prisma"
import bcrypt from 'bcryptjs';

export const config = {
  adapter: prismaadapter(prisma),
  providers: [
    credentials({
      credentials: {
        email: { label: "email", type: "text" },
        password: { label: "password", type: "password" }
      },
      authorize: async (credentials): promise => {
        if (!credentials?.email || !credentials?.password) {
          return null;
        }

        try {
          const user = await prisma.user.findunique({
            where: {
              email: credentials.email as string
            }
          })

          if (!user || !user.hashedpassword) {
            return null
          }

          const ispasswordvalid = await bcrypt.compare(
            credentials.password as string,
            user.hashedpassword
          )

          if (!ispasswordvalid) {
            return null
          }

          return {
            id: user.id as string,
            email: user.email as string,
            name: user.name as string,
          }
        } catch (error) {
          console.error('error during authentication:', error)
          return null 
        }
      }
    })
  ],
  secret: process.env.auth_secret,
  pages: {
    signin: '/login',
  },
  session: {
    strategy: "jwt",
  },
  callbacks: {
    async jwt({ token, user }) {
      if (user) {
        token.id = user.id
        token.email = user.email
        token.name = user.name
      }
      return token
    },
    async session({ session, token }) {
      if (session.user) {
        session.user.id = token.id as string
        session.user.email = token.email as string
        session.user.name = token.name as string
      }
      return session
    },
  },
} satisfies nextauthconfig;

export const { handlers, auth, signin, signout } = nextauth(config);

适配器:

  • 将身份验证系统连接到数据库或数据存储解决方案的模块。

秘密:

  • 这是一个随机字符串,用于哈希令牌、签名/加密 cookie 以及生成加密密钥。
  • 这对于安全至关重要,应该保密。
  • 在本例中,它是使用环境变量 auth_secret 设置的。

页面:

  • 此对象允许您自定义身份验证页面的 url。
  • 在您的示例中,signin: '/login' 表示登录页面将位于 '/login' 路由,而不是默认的 '/api/auth/signin'。

会话:

  • 这配置了会话的处理方式。
  • 策略:“jwt”表示 json web token 将用于会话管理而不是数据库会话。

回调:

  • 这些是在身份验证流程中的各个点调用的函数,允许您自定义流程。

jwt 回调:

  • 它在创建或更新 jwt 时运行。
  • 在您的代码中,它将用户信息(id、电子邮件、姓名)添加到令牌中。

会话回调:

  • 每当检查会话时都会运行。
  • 您的代码正在将用户信息从令牌添加到会话对象。

添加 google oauth 提供商

设置 google oauth 应用程序

从 gcp console 创建新的 oauth 客户端 id > api 和服务 > 凭据

在 Nextjs App Router 中使用 Authjs 进行用户身份验证

创建后,保存您的客户端 id 和客户端密钥以供以后使用。

设置重定向 uri

当我们在本地工作时,设置http://localhost:3000/api/auth/callback/google

生产环境中,只需将 http://localhost:3000 替换为 https://-----即可。

在 Nextjs App Router 中使用 Authjs 进行用户身份验证

设置环境变量

// .env.local
google_client_id={client_id}
google_client_secret={client_secret}

设置提供商

// src/auth.ts

import googleprovider from "next-auth/providers/google"  // add this import.

export const { handlers, auth } = nextauth({
  adapter: prismaadapter(prisma),
  providers: [
    credentialsprovider({
      // ... (previous credentials configuration)
    }),
    googleprovider({
      clientid: process.env.google_client_id,
      clientsecret: process.env.google_client_secret,
    }),
  ],
  // ... other configurations
})

https://authjs.dev/getting-started/authentication/oauth

创建登录和注册页面

//// ui pages
// src/app/login/loginpage.tsx
import link from 'next/link'
import { loginform } from '@/components/auth/loginform'
import { separator } from '@/components/auth/separator'
import { authlayout } from '@/components/auth/authlayout'
import { googleauthbutton } from '@/components/auth/googleauthbutton'

export default function loginpage() {
  return (
    
      
      
      
      

do not have an account?{' '} sign up

) } // src/app/signup/signuppage.tsx import link from 'next/link' import { signupform } from '@/components/auth/signupform' import { separator } from '@/components/auth/separator' import { authlayout } from '@/components/auth/authlayout' import { googleauthbutton } from '@/components/auth/googleauthbutton' export default function signuppage() { return (

already have an account?{' '} sign in

) }
//// components
// src/components/auth/authlayout.tsx
import react from 'react'

interface authlayoutprops {
  children: react.reactnode
  title: string
}

export const authlayout: react.fc = ({ children, title }) => {
  return (
    

{title}

{children}
) } // src/components/auth/googleauthbutton.tsx import { signin } from "@/auth" import { button } from "@/components/ui/button" interface googleauthbuttonprops { text: string } export const googleauthbutton: react.fc = ({ text }) => { return (
{ "use server" await signin("google", { redirectto: '/' }) }} >
) } // src/components/auth/loginform.tsx 'use client' import { usetransition } from "react" import { useform } from "react-hook-form" import { form, formcontrol, formfield, formitem, formlabel, formmessage, } from "@/components/ui/form" import { input } from "@/components/ui/input" import { button } from "@/components/ui/button" import { loginresolver, loginschema } from "@/schema/login" import { usestate } from "react" import { userouter } from "next/navigation" import { formerror } from "@/components/auth/formerror" import { formsuccess } from "@/components/auth/formsuccess" import { login } from "@/app/actions/auth/login" import { loader2 } from "lucide-react" export const loginform = () => { const [error, seterror] = usestate('') const [success, setsuccess] = usestate('') const [ispending, starttransition] = usetransition() const router = userouter(); const form = useform({ defaultvalues: { email: '', password: ''}, resolver: loginresolver, }) const onsubmit = (formdata: loginschema) => { starttransition(() => { seterror('') setsuccess('') login(formdata) .then((data) => { if (data.success) { setsuccess(data.success) router.push('/setup') } else if (data.error) { seterror(data.error) } }) .catch((data) => { seterror(data.error) }) }) } return (
( email address )} /> ( password )} />
) } // src/components/auth/signupform.tsx 'use client' import { usetransition } from "react" import { useform } from "react-hook-form" import { form, formcontrol, formfield, formitem, formlabel, formmessage, } from "@/components/ui/form" import { input } from "@/components/ui/input" import { button } from "@/components/ui/button" import { signupresolver, signupschema } from "@/schema/signup" import { usestate } from "react" import { userouter } from "next/navigation" import { formerror } from "@/components/auth/formerror" import { formsuccess } from "@/components/auth/formsuccess" import { signup } from "@/app/actions/auth/signup" import { loader2 } from "lucide-react" export const signupform = () => { const [error, seterror] = usestate('') const [success, setsuccess] = usestate('') const [ispending, starttransition] = usetransition() const router = userouter(); const form = useform({ defaultvalues: { name: '', email: '', password: ''}, resolver: signupresolver, }) const onsubmit = async (formdata: signupschema) => { starttransition(() => { seterror('') setsuccess('') signup(formdata) .then((data) => { if (data.success) { setsuccess(data.success) router.push('/login') } else if (data.error) { seterror(data.error) } }) .catch((data) => { seterror(data.error) }) }) } return (
( username )} /> ( email address )} /> ( password )} />
) } // src/components/auth/formsuccess.tsx import { checkcircledicon } from "@radix-ui/react-icons"; interface formsuccessprops { message?: string; } export const formsuccess = ({ message }: formsuccessprops) => { if (!message) return null; return (

{message}

); }; // src/components/auth/formerror.tsx import { exclamationtriangleicon } from "@radix-ui/react-icons"; interface formerrorprops { message?: string; } export const formerror = ({ message }: formerrorprops) => { if (!message) return null; return (

{message}

); }; // src/components/auth/separator.tsx export const separator = () => { return (
or continue with
) }
//// actions
// src/app/actions/auth/login.ts
'use server'

import { loginschema, loginschema } from '@/schema/login'
import { signin } from '@/auth'


export const login = async (formdata: loginschema) => {
  const email = formdata['email'] as string
  const password = formdata['password'] as string

  const validatedfields = loginschema.safeparse({
    email: formdata.email as string,
    password: formdata.password as string,
  })

  if (!validatedfields.success) {
    return { 
      errors: validatedfields.error.flatten().fielderrors,
      message: 'login failed. please check your input.'
    }
  }

  try {
    const result = await signin('credentials', {
      redirect: false,
      callbackurl: '/setup',
      email,
      password
    })

    if (result?.error) {
      return { error : 'invalid email or password'}
    } else {
      return { success : 'login successfully'}
    }
  } catch {
    return { error : 'login failed'}
  }
}

// src/app/actions/auth/signup.ts
'use server'

import bcrypt from 'bcryptjs'
import { signupschema, signupschema } from "@/schema/signup"
import { prisma } from '@/lib/prisma';

export const signup = async (formdata: signupschema) => {
  const validatedfields = signupschema.safeparse({
    name: formdata.name as string,
    email: formdata.email as string,
    password: formdata.password as string,
  })

  if (!validatedfields.success) {
    return { 
      errors: validatedfields.error.flatten().fielderrors,
      message: 'sign up failed. please check your input.'
    }
  }

  try {
    const hashedpassword = await bcrypt.hash(validatedfields.data.password, 10);
    const existinguser = await prisma.user.findunique({
      where: { email: validatedfields.data.email }
    })

    if (existinguser) {
      return { error: 'user already exists!' }
    }

    await prisma.user.create({
      data: {
        name:  validatedfields.data.name,
        email:  validatedfields.data.email,
        hashedpassword: hashedpassword,
      },

    });

    return { success: 'user created successfully!' }
  } catch (error) {
    return { error : `sign up failed`}
  }
}
//// validations
// src/schema/login.ts
import * as z from 'zod';
import { zodresolver } from '@hookform/resolvers/zod'; 

export const loginschema = z.object({
  email: z.string().email('this is not valid email address'),
  password: z
    .string()
    .min(8, { message: 'password must contain at least 8 characters' }),
});

export type loginschema = z.infer;
export const loginresolver = zodresolver(loginschema);

// src/schema/signup.ts
import * as z from 'zod';
import { zodresolver } from '@hookform/resolvers/zod'; 

export const signupschema = z.object({
  name: z.string().min(1, {
    message: 'name is required'
  }),
  email: z.string().email('this is not valid email address'),
  password: z
    .string()
    .min(8, { message: 'password must contain at least 8 characters' }),
});

export type signupschema = z.infer;
export const signupresolver = zodresolver(signupschema);

// src/middleware.ts
import { nextresponse } from 'next/server'
import { auth } from "@/auth"

export default auth((req) => {
  const { nexturl, auth: session } = req
  const isloggedin = !!session
  const isloginpage = nexturl.pathname === "/login"
  const issignuppage = nexturl.pathname === "/signup"
  const issetuppage = nexturl.pathname === "/setup"

  // if trying to access /setup while not logged in
  if (!isloggedin && issetuppage) {
    const loginurl = new url("/login", nexturl.origin)
    return nextresponse.redirect(loginurl)
  }

  // if trying to access /login or /signup while already logged in
  if (isloggedin && (isloginpage || issignuppage)) {
    const dashboardurl = new url("/", nexturl.origin)
    return nextresponse.redirect(dashboardurl)
  }

  // for all other cases, allow the request to pass through
  return nextresponse.next()
})

export const config = {
  matcher: ["/login","/signup", "/setup", "/"],
};

文件夹结构

/src
  /app
    /actions
      /login.ts  // Login Action
      /signup.ts  // Signup Action
    /api
      /auth
        [...nextauth]
          /route.ts
    /login
      page.tsx  // Login Page
    /signup
      page.tsx  // Sign Up Page
    layout.tsx
    page.tsx

  /components
    /auth
      AuthLayout.tsx
      GoogleAuthButton.tsx
      LoginForm.tsx
      SignupForm.tsx
      FormSuccess.tsx
      FormError.tsx
      Separator.tsx

  /schema
    login.ts
    signup.ts

  auth.ts  // in src folder
  middleware.ts  // in src folder

今天关于《在 Nextjs App Router 中使用 Authjs 进行用户身份验证》的内容就介绍到这里了,是不是学起来一目了然!想要了解更多关于的内容请关注golang学习网公众号!

声明:本文转载于:dev.to 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>