🔑 使用 Next.js、Prisma 和 next-auth 实现无密码身份验证
使用 Next.js、Prisma 和 next-auth 实现无密码身份验证
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
使用 Next.js、Prisma 和 next-auth 实现无密码身份验证
本文将介绍如何使用Prisma和next-auth为Next.js应用添加无密码身份验证。完成本教程后,用户将能够使用 GitHub 帐户或直接发送到其邮箱的Slack 式“魔法链接”登录您的应用。
如果你想跟着做,请克隆这个仓库并切换到start-here相应的分支!😃
如果你想观看本教程的实时编码演示版本,请查看下面的录像!👇
步骤 0:依赖项和数据库设置
在开始之前,让我们先将 Prisma 安装next-auth到 Next.js 项目中。
npm i next-auth
npm i -D @prisma/cli @types/next-auth
本教程中使用的是 TypeScript,因此我也会安装相应的类型定义。next-auth
您还需要一个 PostgreSQL 数据库来存储所有用户数据和活动令牌。
如果你还没有数据库,Heroku 可以免费托管 PostgreSQL 数据库,非常方便!你可以看看Nikolas Burk的这篇文章,了解如何设置。
如果你是 Docker 的粉丝,并且希望在开发过程中将所有内容都放在本地,你也可以看看我制作的这个关于如何使用 Docker Compose 实现此操作的视频。
在进行下一步之前,请确保您拥有以下格式的 PostgreSQL URI:
postgresql://<USER>:<PASSWORD>@<HOST_NAME>:<PORT>/<DB_NAME>
步骤 1:初始化 Prisma
太棒了!让我们生成一个Prisma 初始模式和一个@prisma/client模块到项目中。
npx prisma init
请注意,您的项目下会创建一个新目录prisma。所有数据库操作都在这里进行。🧙♂️
现在,将虚拟数据库 URI 替换/prisma/.env为您自己的 URI。
npx prisma init
步骤 2:定义用于身份验证的数据库模式
next-auth为了使其无缝运行,我们需要在数据库中包含特定的表。在我们的项目中,模式文件位于/prisma/schema.prisma……
我们暂时使用默认模式,但要知道,您始终可以自行扩展或自定义数据模型。
注意:如果您已有数据库,在替换虚拟数据库 URI 后,您可以运行命令生成数据库
npx prisma introspect配置文件schema.prisma,并在此基础上进行操作。然后,您应该将以下数据模型添加到生成的配置schema.prisma文件中。
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Account {
id Int @default(autoincrement()) @id
compoundId String @unique @map(name: "compound_id")
userId Int @map(name: "user_id")
providerType String @map(name: "provider_type")
providerId String @map(name: "provider_id")
providerAccountId String @map(name: "provider_account_id")
refreshToken String? @map(name: "refresh_token")
accessToken String? @map(name: "access_token")
accessTokenExpires DateTime? @map(name: "access_token_expires")
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
@@index([providerAccountId], name: "providerAccountId")
@@index([providerId], name: "providerId")
@@index([userId], name: "userId")
@@map(name: "accounts")
}
model Session {
id Int @default(autoincrement()) @id
userId Int @map(name: "user_id")
expires DateTime
sessionToken String @unique @map(name: "session_token")
accessToken String @unique @map(name: "access_token")
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
@@map(name: "sessions")
}
model User {
id Int @default(autoincrement()) @id
name String?
email String? @unique
emailVerified DateTime? @map(name: "email_verified")
image String?
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
@@map(name: "users")
}
model VerificationRequest {
id Int @default(autoincrement()) @id
identifier String
token String @unique
expires DateTime
createdAt DateTime @default(now()) @map(name: "created_at")
updatedAt DateTime @default(now()) @map(name: "updated_at")
@@map(name: "verification_requests")
}
让我们来详细分析一下:
在模式文件中,我们定义了 4 个数据模型—— 、、Account和。和模型用于存储用户信息,模型用于管理用户的活动会话,而用于存储为魔法链接电子邮件登录生成的有效令牌。SessionUserVerificationRequestUserAccountSessionVerificationRequest
该@map属性用于将 Prisma 字段名称映射到数据库列名称,例如compoundId映射到compound_id,这是next-auth使其正常工作所必需的。
snake_case常用于数据库环境的命名约定,但驼峰命名法才是 JavaScript 和 TypeScript 中我们通常使用的命名方式。虽然用snake_case命名 Prisma 字段完全没问题,但看起来不太美观。:)
接下来,让我们运行这些命令,将我们需要的表填充到数据库中。
npx prisma migrate save --experimental
npx prisma migrate up --experimental
然后,运行此命令以生成针对数据库架构定制的 Prisma 客户端。
npx prisma generate
现在,如果您使用以下命令打开Prisma Studio,您将能够检查我们刚刚在数据库中创建的所有表。
npx prisma studio
步骤 3:配置next-auth
在开始配置之前next-auth,让我们.env在项目根目录中创建另一个文件来存储将要使用的密钥next-auth(.env.example如果您克隆了教程仓库,则可以从模板重命名该文件)。
SECRET=RAMDOM_STRING
SMTP_HOST=YOUR_SMTP_HOST
SMTP_PORT=YOUR_SMTP_PORT
SMTP_USER=YOUR_SMTP_USERNAME
SMTP_PASSWORD=YOUR_SMTP_PASSWORD
SMTP_FROM=YOUR_REPLY_TO_EMAIL_ADDRESS
GITHUB_SECRET=YOUR_GITHUB_API_CLIENT_SECRET
GITHUB_ID=YOUR_GITHUB_API_CLIENT_ID
DATABASE_URL=postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public
现在,让我们创建一个新文件,作为发送到(例如)的所有请求的/pages/api/auth/[...nextauth].ts“兜底” Next.js API 路由。your-app-url-root/api/authlocalhost:3000/api/auth
在文件中,首先从 `<module>` 导入必要的模块next-auth,然后定义一个 API 处理程序,该程序将请求传递给一个NextAuth函数,该函数会返回一个响应,该响应可以是完全生成的登录表单页面,也可以是回调重定向。要使用next-authPrisma 连接到数据库,您还需要导入PrismaClient并初始化一个Prisma 客户端实例。
import { NextApiHandler } from "next";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import Adapters from "next-auth/adapters";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// we will define `options` up next
const authHandler: NextApiHandler = (req, res) => NextAuth(req, res, options);
export default authHandler;
现在我们来创建options对象。这里,您可以从多种内置身份验证提供程序中进行选择。在本教程中,我们将使用 GitHub OAuth 和“魔法链接”电子邮件来验证访问者的身份。
步骤 3.1:设置 GitHub OAuth
对于像 GitHub 这样的内置 OAuth 提供程序,您需要一个clientId和一个clientSecret,这两个都可以通过在 Github 上注册一个新的 OAuth 应用来获得。
首先,登录您的 GitHub 帐户,转到“设置”,然后导航到“开发者设置”,然后切换到“OAuth 应用”。
点击“注册新应用”按钮将跳转到注册表单,填写一些应用信息。授权回调 URL/api/auth应该是我们之前定义的Next.js路由( http://localhost:3000/api/auth)。
需要注意的是,授权回调 URL字段仅支持一个 URL,这与 Auth0 不同,Auth0 允许您添加多个回调 URL,并用逗号分隔。这意味着如果您以后想使用生产环境 URL 部署应用,则需要创建一个新的 GitHub OAuth 应用。
点击“注册应用程序”按钮,然后您将看到新生成的客户端 ID 和客户端密钥。将这些信息复制到.env根目录下的文件中。
现在,让我们回到/api/auth/[...nextauth].ts并创建一个名为 的新对象options,并像下面这样获取 GitHub OAuth 凭据。
const options = {
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
};
OAuth 提供程序通常工作方式相同,因此,如果您选择的提供程序受支持next-auth,您可以像我们在这里配置 GitHub 一样进行配置。如果没有内置支持,您仍然可以定义自定义提供程序。
步骤 3.2:设置无密码电子邮件身份验证
要允许用户通过魔法链接邮件进行身份验证,您需要拥有 SMTP 服务器的访问权限。这类邮件属于事务性邮件。如果您没有自己的 SMTP 服务器,或者您的邮件服务提供商对发送邮件有严格的限制,我建议您使用SendGrid,或者也可以选择Amazon SES、Mailgun等其他服务。
准备好 SMTP 凭据后,您可以将该信息放入.env文件中,将其添加Providers.Email({})到提供商列表中,并按如下方式加载环境变量。
const options = {
providers: [
// Providers.GitHub ...
Providers.Email({
server: {
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
},
from: process.env.SMTP_FROM, // The "from" address that you want to use
}),
],
};
步骤 3.3:连接 Prisma
设置的最后一步next-auth是指定使用 Prisma 与数据库通信。为此,我们将使用 Prisma 适配器并将其添加到对象中。此外,为了确保安全,options我们还需要一个密钥来签名和加密令牌及 cookie——该密钥也应从环境变量中获取。next-auth
const options = {
providers: [
// ...
],
adapter: Adapters.Prisma.Adapter({ prisma }),
secret: process.env.SECRET,
};
总而言之,你的pages/api/auth/[...nextauth].ts文件应该如下所示:
import { NextApiHandler } from "next";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import Adapters from "next-auth/adapters";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const authHandler: NextApiHandler = (req, res) => NextAuth(req, res, options);
export default authHandler;
const options = {
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
Providers.Email({
server: {
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
},
},
from: process.env.SMTP_FROM,
}),
],
adapter: Adapters.Prisma.Adapter({
prisma,
}),
secret: process.env.SECRET,
};
第四步:在前端实现身份验证
在应用程序中,您可以使用此功能next-auth检查访客是否拥有与有效会话对应的 cookie/令牌。如果找不到会话,则表示用户未登录。
使用next-auth,您有两种方法可以检查会话 - 可以在 React 组件中使用 hook 完成useSession(),也可以在后端(getServerSideProps或在 API 路由中)使用辅助函数完成getSession()。
我们来看看它是如何运作的。
useSession()步骤 4.1:使用钩子检查用户会话
要使用这个钩子,你需要将组件包裹在一个next-auth 提供程序中。为了让身份验证流程在整个 Next.js 应用中的任何位置都能正常工作,请创建一个名为 `.js.js` 的新文件/pages/_app.tsx。
import { Provider } from "next-auth/client";
import { AppProps } from "next/app";
const App = ({ Component, pageProps }: AppProps) => {
return (
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>
);
};
export default App;
现在,您可以前往/pages/index.tsx并导入模块useSession中的钩子next-auth/client。您还需要 ` signInand`signOut函数来实现身份验证交互。该signIn函数会将用户重定向到登录表单,该表单由自动生成next-auth。
import { signIn, signOut, useSession } from "next-auth/client";
该useSession()钩子返回一个数组,其中第一个元素是用户会话,第二个元素是一个布尔值,表示加载状态。
// ...
const IndexPage = () => {
const [session, loading] = useSession();
if (loading) {
return <div>Loading...</div>;
}
};
如果session对象为空null,则表示用户未登录。此外,我们可以从中获取用户信息session.user。
// ...
if (session) {
return (
<div>
Hello, {session.user.email ?? session.user.name} <br />
<button onClick={() => signOut()}>Sign out</button>
</div>
);
} else {
return (
<div>
You are not logged in! <br />
<button onClick={() => signIn()}>Sign in</button>
</div>
);
}
最终/pages/index.tsx文件应如下所示。
import { signIn, signOut, useSession } from "next-auth/client";
const IndexPage = () => {
const [session, loading] = useSession();
if (loading) {
return <div>Loading...</div>;
}
if (session) {
return (
<div>
Hello, {session.user.email ?? session.user.name} <br />
<button onClick={() => signOut()}>Sign out</button>
</div>
);
} else {
return (
<div>
You are not logged in! <br />
<button onClick={() => signIn()}>Sign in</button>
</div>
);
}
};
export default IndexPage;
现在,您可以使用以下命令启动 Next.js 开发服务器npm run dev,并体验身份验证流程!
getSession()步骤 4.2:在后端检查用户会话
要从后端代码获取用户会话,无论getServerSideProps()是在 API 请求处理程序中,都需要使用getSession()异步函数。
现在我们创建一个新/pages/api/secret.ts文件,如下所示。前端的原则同样适用于这里——如果用户没有有效的会话,则表示他们未登录,在这种情况下,我们将返回一个状态码为 403 的消息。
import { NextApiHandler } from "next";
import { getSession } from "next-auth/client";
const secretHandler: NextApiHandler = async (req, res) => {
const session = await getSession({ req });
if (session) {
res.end(
`Welcome to the VIP club, ${session.user.name || session.user.email}!`
);
} else {
res.statusCode = 403;
res.end("Hold on, you're not allowed in here!");
}
};
export default secretHandler;
localhost:3000/api/secret无需登录即可访问,您将看到类似下图的内容。
结论
就是这样,有了它,身份验证就容易多了next-auth!
希望您喜欢这篇教程,并学到了一些有用的东西!您可以在此 GitHub 仓库中找到初始代码和完整的项目。
此外,还可以查看Awesome Prisma 列表,获取更多 Prisma 生态系统中的教程和入门项目!
文章来源:https://dev.to/prisma/passwordless-authentication-with-next-js-prisma-and-next-auth-5g8g
