一、next-auth 身份验证凭据-使用电子邮件和密码注册登录

简介: 本文是关于如何在Next.js应用中使用next-auth库实现基于电子邮件和密码的注册和登录功能的详细教程,包括环境配置、项目初始化、前后端页面开发、数据库交互以及用户状态管理等方面的步骤和代码示例。

一、next-auth 身份验证凭据-使用电子邮件和密码注册登录

文章目录

  • 一、next-auth 身份验证凭据-使用电子邮件和密码注册登录
  • 一、前言
  • 二、前置准备
    • 1、环境配置
    • 2、相关库安装
      • (1)vercel 配置
      • (2)Yarn 包管理配置
    • 3、next项目初始化与创建
  • 三、具体实现
    • 1、github 仓库创建与链接
    • 2、Vercel项目链接Vercel账户并部署
      • (1)项目上传vercel
      • (2)项目创建并链接postgre 数据库
      • (3)本地化项目链接数据库准备
    • 3、登录页面与注册页面的前端
      • (1)登录页面
      • (1)注册页面
      • (1)register 页面
  • 四、数据库交互
    • 1、数据库创建
    • 2、vercel项目链接数据库并插入
      • (1)vercel postgre准备
      • (2) 项目注册并写sql插入数据库
      • (3) 项目查询sql数据库并登录
  • 五、状态增加
    • 1、查询到登录之后登录自动跳转状态增加
    • 2、登出与登录状态转换
    • 3、重定向
    • 4、root保护
    • 5、root保护登录页面转为自定义登录页面

一、前言

近些时间需要在next 里面加入登录注册的功能遂整理了一下学习视频:
Next auth 身份验证凭据 - 使用电子邮件和密码注册和登录(NextJS app 路由)

二、前置准备

1、环境配置

  • Vscode

  • node环境配置

  • vercel 账户注册

  • github账户注册

2、相关库安装

(1)vercel 配置

  1. npm i -g vercel //安装vercel CLI

(2)Yarn 包管理配置

  1. npm i -g yarn //安装yarn

3、next项目初始化与创建

  1. yarn create next-app

在这里插入图片描述

  1. cd nextauth-review (这里面放你写的项目名)

  2. yarn dev //运行程序,在http://localhost:3000可以查看next项目的项目名![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a4888b853191aef95a05e6515217eded.png)

到这里我们就基本完成了前置的准备工作,下面继续。

三、具体实现

1、github 仓库创建与链接

在这里插入图片描述

将新建的项目上传github

在这里插入图片描述

2、Vercel项目链接Vercel账户并部署

(1)项目上传vercel

  • vercel login //vercel 登录

  • Vercel //链接与上传

在这里插入图片描述

第三行输入n。最开始的时候不连接vercel项目。

注:后续如果项目更新了,要推送到vercel部署,可以通过输入vercel ,然后在第三行输入y,并输入第一步创建的项目名。

(2)项目创建并链接postgre 数据库

在这里插入图片描述

(3)本地化项目链接数据库准备

  1. vercel env pull .env.development.local

在这里插入图片描述
注释掉VERCEL,不注释掉就会强制使用https,但我们在本地调试所以说http会报错。正式运行再取消注释。

  1. openssl rand -base64 32 ,生成32位密码,并修改.env.development.local如下:

在这里插入图片描述

  1. yarn add @types/bcrypt 安装加密工具bcrypt

  2. yarn add @vercel/postgres 安装vercel postgres

  3. yarn add next-auth 安装next-auth

到这里该项目的数据库就配置好了,下面我们开始页面开发。

3、登录页面与注册页面的前端

(1)登录页面

新建login文件夹,并在其中新建page.tsx下面是具体的内容

export default function LoginPage(){
  return (
    <form className="flex flex-col gap-2 mx-auto max-w-md mt-10">
    <input name='email' className ="border border-black text-black" type="email" />
    <input name='password' className ="border border-black text-black" type="password" />
    <button type="submit">Login</button>
  </form>
  )
}

(1)注册页面

新建register文件夹,并在其中新建page.tsx下面是具体的内容

export default function LoginPage(){
  return (
    <form className="flex flex-col gap-2 mx-auto max-w-md mt-10">
    <input name='email' className ="border border-black text-black" type="email" />
    <input name='password' className ="border border-black text-black" type="password" />
    <button type="submit">Register</button>
  </form>
  )
}

到这里前端就差不多了,大家可以在http://localhost:3000/loginhttp://localhost:3000/register看到你写的页面

nextAuth的官方文档:https://next-auth.js.org/providers/credentials![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b1e6ad62cedc07a521628621795f35a8.png)

基本就是用来nextAuth 官方文档中的credentials 字段,结尾加上export {handler as GET, handler as POST}; 就差不多了。

类似如下:

api/auth/[…nextauth]/route.ts

import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
const handler =NextAuth({
  providers: [
    CredentialsProvider({

      credentials: {
        email: {  },
        password: { }
      },
      async authorize(credentials, req) {
        // Add logic here to look up the user from the credentials supplied
        const user = { id: "1", name: "J Smith", email: "jsmith@example.com" }

        if (user) {
          // Any object returned will be saved in `user` property of the JWT
          return user
        } else {
          // If you return null then an error will be displayed advising the user to check their details.
          return null

          // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
        }
      }
    })
  ]
})

export {handler as GET, handler as POST};

api/auth/register/route.ts

import { NextResponse } from "next/server";

export  async function POST(request : Request){

  try{

    const {email,password} = await request.json();
    console.log({email,password});

  }catch(e){
    console.log({e});

  }
  return NextResponse.json({message:"success"});
}

(1)register 页面

register\page.tsx

import { log } from "console";
import { FormEvent } from "react"

export default function LoginPage(){
  const handleSubmit= async (e:FormEvent<HTMLFormElement>)=>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const response = await fetch(`/api/auth/register`,{
      method:"POST",
      body:JSON.stringify({
        email:formData.get('email'),
        password:formData.get('password'),
      }),
    })
    console.log({response});

  }
  return (
    <form onSubmit ={ handleSubmit}className="flex flex-col gap-2 mx-auto max-w-md mt-10">
    <input name='email' className ="border border-black text-black" type="email" />
    <input name='password' className ="border border-black text-black" type="password" />
    <button type="submit">register</button>
  </form>
  )
}

运行报错:
在这里插入图片描述

注:错误原因:需要把组件写到客户端部分,不能直接写进去Page.tsx

修改如下:

register/form.tsx

'use client'
import { FormEvent } from "react"

export default function Form(){
  const handleSubmit = async (e:FormEvent<HTMLFormElement>) =>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    console.log(formData.get('email'),formData.get('password'));
    const response = await fetch(`/api/auth/register`,{
      method:'POST',
      body:JSON.stringify({
        email:formData.get('email'),
        password:formData.get('password')
      }),
    });
    console.log({response});
  };

  return(
    <form onSubmit={handleSubmit} className="flex flex-col gap-2 mx-auto max-w-md mt-10">
      <input name='email' className ="border border-black text-black" type="email" />
      <input name='password' className ="border border-black text-black" type="password" />
      <button type="submit">Resgiter</button>
    </form>
  )
}

register/page.tsx

import Form from './form'

export default async function RegisterPage(){
  return <Form />;

}

到现在运行yarn dev 并到 http://localhost:3001/register 页面输入账户和密码,点击登录调用接口:fetch(/api/auth/register)然后打印{ email: ‘123@123’, password: ‘123’ }

在这里插入图片描述

到这里就完成简单的前后端编写,下面进入数据库交互部分。

四、数据库交互

1、数据库创建

在这里插入图片描述

依据需求创建表单users1 如上,一共有两个属性email 和password

2、vercel项目链接数据库并插入

确保Browse页面确实能查到这个新建的数据库在这里插入图片描述

(1)vercel postgre准备

yarn add @vercel/postgres

(2) 项目注册并写sql插入数据库

将API/auth/register/route.ts 的内容修改如下即可实现针对数据库的插入

import { NextResponse } from "next/server";
import {hash} from 'bcrypt'
import {sql} from '@vercel/postgres'
export  async function POST(request : Request){

  try{

    const {email,password} = await request.json();
    console.log({email,password});

    const hashedPassword = await hash(password,10); //将密码hash加密到10位

    const response = await sql`
    INSERT INTO users1 (email,password) 
    VALUES (${email},${password})
    `;
    //${参数} 参数化查询
  }catch(e){
    console.log({e});

  }
  return NextResponse.json({message:"success"});
}

前端页面register\form.tsx 如下,page.tsx 不变

'use client'
import { signIn } from "next-auth/react";
import { FormEvent } from "react"

export default function LoginForm() {
  const handleSubmit = async (e:FormEvent<HTMLFormElement>) =>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    signIn('credentials',{  //nextauth 登录模块
      email:formData.get('email'),
      password:formData.get('password'),
      redirect:false
    });
  };
  return(
    <form onSubmit={handleSubmit} className="flex flex-col gap-2 mx-auto max-w-md mt-10">
      <input name='email' className ="border border-black text-black" type="email" />
      <input name='password' className ="border border-black text-black" type="password" />
      <button type="submit">Login</button>
    </form>
  )
}

测试的时候可以发现可以显示登录信息了

问题:发现邮箱有时候同邮箱有多个,所以需要当相同的时候就不添加而是修改

在这里插入图片描述

在数据库中限制让email唯一

(3) 项目查询sql数据库并登录

将API/auth/[…nextauth]/route.ts 的内容修改如下即可实现针对数据库的插入

import { sql } from "@vercel/postgres";
import { compare } from "bcrypt";
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
const handler =NextAuth({
  session:{
    strategy:'jwt'
  },
  providers: [
    CredentialsProvider({

      credentials: {
        email: {  },
        password: { }
      },
      async authorize(credentials, req) {


        const response = await sql`
        SELECT * FROM users1 WHERE email=${credentials?.email}
        `;

        const user = response.rows[0];
        const passwordCorrect = await compare(
          credentials?.password ||'',
          user.password
        );
        const passworduser=user.password
        const passwordcre=credentials?.password
        console.log({passwordCorrect},{passwordcre},{passworduser});
        if (passwordCorrect){
          return {
            id:user.id,
            email:user.email
          }
        }
        //console.log({credentials}); //打印credentials信息
        return null

      }
    })
  ]
})

export {handler as GET, handler as POST};

前端页面login\form.tsx 如下,page.tsx 不变

'use client'
import { signIn } from "next-auth/react";
import { FormEvent } from "react"

export default function LoginForm() {
  const handleSubmit = async (e:FormEvent<HTMLFormElement>) =>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const response= await signIn('credentials',{  //nextauth 登录模块
      email:formData.get('email'),
      password:formData.get('password'),
      redirect:false
    });
    console.log({response});
  };


  return(
    <form onSubmit={handleSubmit} className="flex flex-col gap-2 mx-auto max-w-md mt-10">
      <input name='email' className ="border border-black text-black" type="email" />
      <input name='password' className ="border border-black text-black" type="password" />
      <button type="submit">Login</button>
    </form>
  )
}

这时候就可以写入数据库了

五、状态增加

1、查询到登录之后登录自动跳转状态增加

解析:
如果查询到登录模块,没有返回error。则自动导航到‘/’目录同时刷新。
核心修改:

export default function LoginForm() {
  const router=useRouter();
  const handleSubmit = async (e:FormEvent<HTMLFormElement>) =>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const response= await signIn('credentials',{  //nextauth 登录模块
      email:formData.get('email'),
      password:formData.get('password'),
      redirect:false
    });
    console.log({response});
    if(!response?.error){  //没有返回error
      router.push('/');    //跳转到‘/’
      router.refresh();    //刷新缓存
    }
  };

login/form.tsx全部内容如下

'use client'
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { FormEvent } from "react"

export default function LoginForm() {
  const router=useRouter();
  const handleSubmit = async (e:FormEvent<HTMLFormElement>) =>{
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const response= await signIn('credentials',{  //nextauth 登录模块
      email:formData.get('email'),
      password:formData.get('password'),
      redirect:false
    });
    console.log({response});
    if(!response?.error){
      router.push('/');
      router.refresh();
    }
  };


  return(
    <form onSubmit={handleSubmit} className="flex flex-col gap-2 mx-auto max-w-md mt-10">
      <input name='email' className ="border border-black text-black" type="email" />
      <input name='password' className ="border border-black text-black" type="password" />
      <button type="submit">Login</button>
    </form>
  )
}

2、登出与登录状态转换

登录之后,登出
登出之后,登录,并自动跳转到首页
功能解析
在全部页面都需要有这个跳转。
1、在app首页的layout.tsx页面进行编写。
2、自动跳转用next

该功能核心修改是增加:

 <nav>
          {!!session &&
          <Logout />
          }
           {!session &&
          <Link href='/login'>Login</Link>
          }
        </nav>

文件全部代码如下:
layout.tsx

mport type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { getServerSession } from "next-auth";
import Link from "next/link";
import Logout from "./logout";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  const session = await getServerSession();
  return (
    <html lang="en">
      <body className={inter.className}>
        <nav>
          {!!session &&
          <Logout />
          }
           {!session &&
          <Link href='/login'>Login</Link>
          }
        </nav>

        {children}</body>
    </html>
  );
}

由于这个里面也涉及到后端,所以需要重新编写一个的客户端API进行处理,在同目录编写
logout.tsx

'use client'

import { signOut } from "next-auth/react"
export default function Logout(){
  return (
    <span onClick={() => {
      signOut();
    }}>
      Logout
    </span>
  )
}

到这里,登出与登录按钮增加完毕

3、重定向

当你登录后不想再返回登录页面可以参考以下的操作。
其他的网址也可以这么操作。

import { getServerSession } from "next-auth";
import Form from "./form";
import { redirect } from "next/navigation";

export default async function LoginPage(){
  const session =await getServerSession();
  if(session){
    redirect("/")
  }
  return <Form />
}

4、root保护

一些页面需要你登录之后才能访问,在这里面我们借助中间件实现这个操作
在项目主目前增加中间件
middleware.ts

export {default } from 'next-auth/middleware'

export const conifg ={matcher:['/dashboard']} //加入你要登录保护的地址

这样子,这个功能就实现完毕了。

5、root保护登录页面转为自定义登录页面

在nextauth 的route页面增加signin路径
pages:{
signIn:‘/login’,
},在这里插入图片描述

相关文章
|
6月前
|
安全 API 数据安全/隐私保护
smtp用户名,验证身份的名称是什么?
SMTP用户名是验证身份的标识,用于证明有权使用SMTP服务器发送邮件。它通常与邮箱地址关联,如`example@example.com`。配合smtp密码,二者组成身份验证的钥匙。安全使用这些信息至关重要,避免在不安全环境中输入,以保障邮件发送的安全和顺利。AokSend提供安全的发信服务,支持smtp/api接口,确保高触达发信。
|
6月前
|
安全 算法 数据安全/隐私保护
什么是身份验证器应用?
【5月更文挑战第14天】什么是身份验证器应用?
270 0
|
6月前
|
XML 算法 数据安全/隐私保护
Shiro -认证凭据(密码)加密的那些事
Shiro -认证凭据(密码)加密的那些事
77 0
|
存储 安全 测试技术
单点登录SSO的身份账户不一致漏洞
由于良好的可用性和安全性,单点登录 (SSO) 已被广泛用于在线身份验证。但是,它也引入了单点故障,因为所有服务提供商都完全信任由 SSO 身份提供商创建的用户的身份。在本文中调查了身份帐户不一致威胁,这是一种新的 SSO 漏洞,可导致在线帐户遭到入侵。该漏洞的存在是因为当前的 SSO 系统高度依赖用户的电子邮件地址来绑定具有真实身份的帐户,而忽略了电子邮件地址可能被其他用户重复使用的事实在 SSO 身份验证下,这种不一致允许控制重复使用的电子邮件地址的攻击者在不知道任何凭据(如密码)的情况下接管关联的在线帐户。
268 1
|
存储 运维 安全
用户身份验证真的很简单吗
你现在要建立一个系统。无论系统的功能如何,用户身份验证都是始终存在的一个功能。实现它看起来应该很简单——只需“拖动”一些现成的身份验证模块,或使用一些基本选项(例如 Spring Security)对其进行配置,就完成了。
118 0
用户身份验证真的很简单吗
|
XML 存储 安全
【视频】甩开密码,SSO !|学习笔记(一)
快速学习【视频】甩开密码,SSO !
【视频】甩开密码,SSO !|学习笔记(一)
|
XML JSON 安全
【视频】甩开密码,SSO !|学习笔记(二)
快速学习【视频】甩开密码,SSO !
【视频】甩开密码,SSO !|学习笔记(二)
|
安全 Java 开发者
认证通过后显示当前认证用户名|学习笔记
快速学习认证通过后显示当前认证用户名
110 0
认证通过后显示当前认证用户名|学习笔记