我正在使用 nextjs 14 应用程序路由器。我的整个后端是 firebase,其中包括应用程序托管、身份验证和 firestore。
在 nextjs 中,据我所知,如果我使用服务器端渲染(SSR),我需要使用会话 cookie 进行身份验证。我正在使用
auth.createSessionCookie()
创建会话 cookie,它是 firebase sdk 的一部分。
使用
auth.creationSessionCookie()
并不能让我登录客户端,即。 auth.currentUser === null
。这是一个问题,因为任何检查身份验证的 Firestore 安全规则都不再起作用,因为 request.auth
也始终是 null
。
我想要某种类似
signInWithSessionCookie()
的功能,一种将我的会话cookie传递到我的请求中的firestore的方法,或者一些解决方法。但经过研究,就像这个other SO question和firebase文档一样,我找不到办法做到这一点。但这确实令人惊讶,因为这种身份验证模式本质上是每个 nextjs 应用程序的运行方式,而 firebase 是一个试图支持 nextjs 的庞大服务。我在这里错过了什么吗?
当您使用 Cookie 进行身份验证时,您无法使用 Firebase 客户端 SDK 来管理用户的身份验证状态,而必须完全使用 Cookie 进行管理。 它类似于创建您自己的基于 cookie 的身份验证的传统方法,只不过 Firebase 负责处理令牌/cookie 并根据需要撤销它们。
您必须实现一个 API,一旦用户使用客户端 SDK 登录,该 API 就会获取用户的 Firebase ID 令牌,然后设置会话 cookie。
您最好将 Firebase auth persistence 设置为
NONE
,以便用户稍后从 Firebase 客户端 SDK 中注销。您可以按如下方式实现:
import "server-only";
import { NextResponse } from "next/server";
import { authAdmin } from "@/utils/firebase-admin";
import { cookies } from "next/headers";
// /api/auth/login
export async function POST(request: Request) {
const authorization = request.headers.get("authorization")?.split(" ")[1];
const sessionCookie = await authAdmin.createSessionCookie(authorization, {
expiresIn: 14 * 24 * 60 * 60 * 1000, // 14 days in ms (maximum)
});
// setting the session cookie
cookies().set("session", sessionCookie, {
httpOnly: true,
secure: true,
domain: "localhost",
});
return NextResponse.json({ message: "Logged in" });
}
// Sample login page
"use client";
import { auth } from "../../utils/firebase";
import { createUserWithEmailAndPassword, getIdToken } from "firebase/auth";
import { useState } from "react";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const signUpUser = async (event: any) => {
event.preventDefault();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
// call POST /api/auth/login with user's token
const token = await getIdToken(userCredential.user);
const response = await fetch("/api/auth/login", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
const data = await response.json();
// redirect user
} catch (error) {
console.error(error);
}
};
return (
<form className="max-w-sm mx-auto" onSubmit={signUpUser}>
<div className="mb-5">
<label>Your email</label>
<input
type="email"
id="email"
onChange={(event) => setEmail(event.target.value)}
value={email}
required
/>
</div>
<div className="mb-5">
<label>Your password</label>
<input
type="password"
id="password"
onChange={(event) => setPassword(event.target.value)}
value={password}
required
/>
</div>
<button type="submit">Sign Up</button>
</form>
);
}
用户登录后,您必须使用 cookie 检查他们的身份验证状态。您可以使用 Next Middlewares 来执行此操作,如下所示:
// middleware.ts
import { NextResponse } from "next/server";
import { NextRequest } from "next/server";
import { authAdmin } from "./utils/firebase-admin";
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith("/_next") || pathname.startsWith("/favicon.ico")) {
return NextResponse.next();
}
const authorization = request.headers.get("authorization")?.split(" ")[1];
if (!authorization) {
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
}
try {
const { uid } = await authAdmin.verifyIdToken(authorization);
console.log("Current user:", uid)
return response;
} catch (e) {
console.log("failed to decode session cookie")
// Unauthorized user..
}
}
我建议创建另一个 API 来获取用户的详细信息,您可以使用它在导航栏等位置呈现当前用户的信息。
总而言之,您必须在服务器端(最好是在中间件中)检查用户的身份验证状态,然后有条件地将用户重定向到登录/仪表板页面。