firebase-auth
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFirebase Authentication
Firebase Authentication
Status: Production Ready
Last Updated: 2026-01-25
Dependencies: None (standalone skill)
Latest Versions: firebase@12.8.0, firebase-admin@13.6.0
状态:已就绪可用于生产环境
最后更新时间:2026-01-25
依赖项:无(独立技能)
最新版本:firebase@12.8.0,firebase-admin@13.6.0
Quick Start (5 Minutes)
快速入门(5分钟)
1. Enable Auth Providers in Firebase Console
1. 在Firebase控制台中启用认证提供商
- Go to Firebase Console > Authentication > Sign-in method
- Enable desired providers (Email/Password, Google, etc.)
- Configure OAuth providers with client ID/secret
- 进入Firebase控制台 > 身份认证 > 登录方法
- 启用所需的提供商(邮箱/密码、Google等)
- 使用客户端ID/密钥配置OAuth提供商
2. Initialize Firebase Auth (Client)
2. 初始化Firebase Auth(客户端)
typescript
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
// ... other config
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);typescript
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
// ... 其他配置
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);3. Initialize Firebase Admin (Server)
3. 初始化Firebase Admin(服务端)
typescript
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export const adminAuth = getAuth();typescript
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getAuth } from 'firebase-admin/auth';
if (!getApps().length) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
});
}
export const adminAuth = getAuth();Email/Password Authentication
邮箱/密码认证
Sign Up
注册
typescript
import { createUserWithEmailAndPassword, sendEmailVerification, updateProfile } from 'firebase/auth';
import { auth } from './firebase';
async function signUp(email: string, password: string, displayName: string) {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Update display name
await updateProfile(user, { displayName });
// Send verification email
await sendEmailVerification(user);
return user;
} catch (error: any) {
switch (error.code) {
case 'auth/email-already-in-use':
throw new Error('Email already registered');
case 'auth/invalid-email':
throw new Error('Invalid email address');
case 'auth/weak-password':
throw new Error('Password must be at least 6 characters');
default:
throw new Error('Sign up failed');
}
}
}typescript
import { createUserWithEmailAndPassword, sendEmailVerification, updateProfile } from 'firebase/auth';
import { auth } from './firebase';
async function signUp(email: string, password: string, displayName: string) {
try {
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// 更新显示名称
await updateProfile(user, { displayName });
// 发送验证邮件
await sendEmailVerification(user);
return user;
} catch (error: any) {
switch (error.code) {
case 'auth/email-already-in-use':
throw new Error('该邮箱已注册');
case 'auth/invalid-email':
throw new Error('无效的邮箱地址');
case 'auth/weak-password':
throw new Error('密码长度至少为6位');
default:
throw new Error('注册失败');
}
}
}Sign In
登录
typescript
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from './firebase';
async function signIn(email: string, password: string) {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
return userCredential.user;
} catch (error: any) {
switch (error.code) {
case 'auth/user-not-found':
case 'auth/wrong-password':
case 'auth/invalid-credential':
throw new Error('Invalid email or password');
case 'auth/user-disabled':
throw new Error('Account has been disabled');
case 'auth/too-many-requests':
throw new Error('Too many attempts. Try again later.');
default:
throw new Error('Sign in failed');
}
}
}typescript
import { signInWithEmailAndPassword } from 'firebase/auth';
import { auth } from './firebase';
async function signIn(email: string, password: string) {
try {
const userCredential = await signInWithEmailAndPassword(auth, email, password);
return userCredential.user;
} catch (error: any) {
switch (error.code) {
case 'auth/user-not-found':
case 'auth/wrong-password':
case 'auth/invalid-credential':
throw new Error('邮箱或密码无效');
case 'auth/user-disabled':
throw new Error('账户已被禁用');
case 'auth/too-many-requests':
throw new Error('尝试次数过多,请稍后再试');
default:
throw new Error('登录失败');
}
}
}Sign Out
登出
typescript
import { signOut } from 'firebase/auth';
import { auth } from './firebase';
async function handleSignOut() {
await signOut(auth);
// Redirect to login page
}typescript
import { signOut } from 'firebase/auth';
import { auth } from './firebase';
async function handleSignOut() {
await signOut(auth);
// 重定向到登录页面
}Password Reset
重置密码
typescript
import { sendPasswordResetEmail, confirmPasswordReset } from 'firebase/auth';
import { auth } from './firebase';
// Send reset email
async function resetPassword(email: string) {
await sendPasswordResetEmail(auth, email);
}
// Confirm reset (from email link)
async function confirmReset(oobCode: string, newPassword: string) {
await confirmPasswordReset(auth, oobCode, newPassword);
}typescript
import { sendPasswordResetEmail, confirmPasswordReset } from 'firebase/auth';
import { auth } from './firebase';
// 发送重置邮件
async function resetPassword(email: string) {
await sendPasswordResetEmail(auth, email);
}
// 确认重置(通过邮件链接)
async function confirmReset(oobCode: string, newPassword: string) {
await confirmPasswordReset(auth, oobCode, newPassword);
}OAuth Providers (Google, GitHub, etc.)
OAuth提供商(Google、GitHub等)
Google Sign-In
Google登录
typescript
import { signInWithPopup, signInWithRedirect, GoogleAuthProvider } from 'firebase/auth';
import { auth } from './firebase';
const googleProvider = new GoogleAuthProvider();
googleProvider.addScope('email');
googleProvider.addScope('profile');
// Popup method (recommended for desktop)
async function signInWithGoogle() {
try {
const result = await signInWithPopup(auth, googleProvider);
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential?.accessToken;
return result.user;
} catch (error: any) {
if (error.code === 'auth/popup-closed-by-user') {
// User closed popup - not an error
return null;
}
if (error.code === 'auth/popup-blocked') {
// Fallback to redirect
await signInWithRedirect(auth, googleProvider);
return null;
}
throw error;
}
}
// Redirect method (for mobile or popup-blocked)
async function signInWithGoogleRedirect() {
await signInWithRedirect(auth, googleProvider);
}typescript
import { signInWithPopup, signInWithRedirect, GoogleAuthProvider } from 'firebase/auth';
import { auth } from './firebase';
const googleProvider = new GoogleAuthProvider();
googleProvider.addScope('email');
googleProvider.addScope('profile');
// 弹窗方式(推荐桌面端使用)
async function signInWithGoogle() {
try {
const result = await signInWithPopup(auth, googleProvider);
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential?.accessToken;
return result.user;
} catch (error: any) {
if (error.code === 'auth/popup-closed-by-user') {
// 用户关闭弹窗 - 不属于错误
return null;
}
if (error.code === 'auth/popup-blocked') {
// 回退到重定向方式
await signInWithRedirect(auth, googleProvider);
return null;
}
throw error;
}
}
// 重定向方式(适用于移动端或弹窗被拦截的情况)
async function signInWithGoogleRedirect() {
await signInWithRedirect(auth, googleProvider);
}Handle Redirect Result
处理重定向结果
typescript
import { getRedirectResult, GoogleAuthProvider } from 'firebase/auth';
import { auth } from './firebase';
// Call on page load
async function handleRedirectResult() {
try {
const result = await getRedirectResult(auth);
if (result) {
const credential = GoogleAuthProvider.credentialFromResult(result);
return result.user;
}
} catch (error) {
console.error('Redirect sign-in error:', error);
}
return null;
}typescript
import { getRedirectResult, GoogleAuthProvider } from 'firebase/auth';
import { auth } from './firebase';
// 在页面加载时调用
async function handleRedirectResult() {
try {
const result = await getRedirectResult(auth);
if (result) {
const credential = GoogleAuthProvider.credentialFromResult(result);
return result.user;
}
} catch (error) {
console.error('重定向登录错误:', error);
}
return null;
}Other OAuth Providers
其他OAuth提供商
typescript
import {
GithubAuthProvider,
TwitterAuthProvider,
FacebookAuthProvider,
OAuthProvider,
} from 'firebase/auth';
// GitHub
const githubProvider = new GithubAuthProvider();
githubProvider.addScope('read:user');
// Microsoft
const microsoftProvider = new OAuthProvider('microsoft.com');
microsoftProvider.addScope('email');
microsoftProvider.addScope('profile');
// Apple
const appleProvider = new OAuthProvider('apple.com');
appleProvider.addScope('email');
appleProvider.addScope('name');typescript
import {
GithubAuthProvider,
TwitterAuthProvider,
FacebookAuthProvider,
OAuthProvider,
} from 'firebase/auth';
// GitHub
const githubProvider = new GithubAuthProvider();
githubProvider.addScope('read:user');
// Microsoft
const microsoftProvider = new OAuthProvider('microsoft.com');
microsoftProvider.addScope('email');
microsoftProvider.addScope('profile');
// Apple
const appleProvider = new OAuthProvider('apple.com');
appleProvider.addScope('email');
appleProvider.addScope('name');Auth State Management
认证状态管理
Listen to Auth State Changes
监听认证状态变化
typescript
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from './firebase';
// React hook example
function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return { user, loading };
}typescript
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from './firebase';
// React Hook示例
function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return { user, loading };
}Auth Context Provider (React)
认证上下文提供者(React)
typescript
// src/contexts/AuthContext.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from '@/lib/firebase';
interface AuthContextType {
user: User | null;
loading: boolean;
}
const AuthContext = createContext<AuthContextType>({ user: null, loading: true });
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);typescript
// src/contexts/AuthContext.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { onAuthStateChanged, User } from 'firebase/auth';
import { auth } from '@/lib/firebase';
interface AuthContextType {
user: User | null;
loading: boolean;
}
const AuthContext = createContext<AuthContextType>({ user: null, loading: true });
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setUser(user);
setLoading(false);
});
return () => unsubscribe();
}, []);
return (
<AuthContext.Provider value={{ user, loading }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => useContext(AuthContext);Token Management
令牌管理
Get ID Token (for API calls)
获取ID令牌(用于API调用)
typescript
import { auth } from './firebase';
async function getIdToken() {
const user = auth.currentUser;
if (!user) throw new Error('Not authenticated');
// Force refresh to get fresh token
const token = await user.getIdToken(/* forceRefresh */ true);
return token;
}
// Use in API calls
async function callProtectedAPI() {
const token = await getIdToken();
const response = await fetch('/api/protected', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
}typescript
import { auth } from './firebase';
async function getIdToken() {
const user = auth.currentUser;
if (!user) throw new Error('未认证');
// 强制刷新以获取最新令牌
const token = await user.getIdToken(/* forceRefresh */ true);
return token;
}
// 在API调用中使用
async function callProtectedAPI() {
const token = await getIdToken();
const response = await fetch('/api/protected', {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
}Verify ID Token (Server-side)
验证ID令牌(服务端)
typescript
// API route (Next.js example)
import { adminAuth } from '@/lib/firebase-admin';
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = authHeader.split('Bearer ')[1];
try {
const decodedToken = await adminAuth.verifyIdToken(token);
const uid = decodedToken.uid;
// User is authenticated, proceed with request
return Response.json({ uid, message: 'Authenticated' });
} catch (error) {
return Response.json({ error: 'Invalid token' }, { status: 401 });
}
}typescript
// API路由(Next.js示例)
import { adminAuth } from '@/lib/firebase-admin';
export async function GET(request: Request) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return Response.json({ error: '未授权' }, { status: 401 });
}
const token = authHeader.split('Bearer ')[1];
try {
const decodedToken = await adminAuth.verifyIdToken(token);
const uid = decodedToken.uid;
// 用户已认证,继续处理请求
return Response.json({ uid, message: '已认证' });
} catch (error) {
return Response.json({ error: '无效令牌' }, { status: 401 });
}
}Session Cookies (Server-Side Rendering)
会话Cookie(服务端渲染)
typescript
import { adminAuth } from '@/lib/firebase-admin';
// Create session cookie
async function createSessionCookie(idToken: string) {
const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5 days
const sessionCookie = await adminAuth.createSessionCookie(idToken, {
expiresIn,
});
return sessionCookie;
}
// Verify session cookie
async function verifySessionCookie(sessionCookie: string) {
try {
const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true);
return decodedClaims;
} catch (error) {
return null;
}
}
// Revoke session
async function revokeSession(uid: string) {
await adminAuth.revokeRefreshTokens(uid);
}typescript
import { adminAuth } from '@/lib/firebase-admin';
// 创建会话Cookie
async function createSessionCookie(idToken: string) {
const expiresIn = 60 * 60 * 24 * 5 * 1000; // 5天
const sessionCookie = await adminAuth.createSessionCookie(idToken, {
expiresIn,
});
return sessionCookie;
}
// 验证会话Cookie
async function verifySessionCookie(sessionCookie: string) {
try {
const decodedClaims = await adminAuth.verifySessionCookie(sessionCookie, true);
return decodedClaims;
} catch (error) {
return null;
}
}
// 撤销会话
async function revokeSession(uid: string) {
await adminAuth.revokeRefreshTokens(uid);
}Custom Claims & Roles
自定义声明与角色
Set Custom Claims (Admin SDK)
设置自定义声明(Admin SDK)
typescript
import { adminAuth } from '@/lib/firebase-admin';
// Set admin role
async function setAdminRole(uid: string) {
await adminAuth.setCustomUserClaims(uid, {
admin: true,
role: 'admin',
});
}
// Set custom permissions
async function setUserPermissions(uid: string, permissions: string[]) {
await adminAuth.setCustomUserClaims(uid, {
permissions,
});
}typescript
import { adminAuth } from '@/lib/firebase-admin';
// 设置管理员角色
async function setAdminRole(uid: string) {
await adminAuth.setCustomUserClaims(uid, {
admin: true,
role: 'admin',
});
}
// 设置自定义权限
async function setUserPermissions(uid: string, permissions: string[]) {
await adminAuth.setCustomUserClaims(uid, {
permissions,
});
}Check Custom Claims (Client)
检查自定义声明(客户端)
typescript
import { auth } from './firebase';
async function checkAdminStatus() {
const user = auth.currentUser;
if (!user) return false;
// Force token refresh to get latest claims
const tokenResult = await user.getIdTokenResult(true);
return tokenResult.claims.admin === true;
}
// In component
const isAdmin = await checkAdminStatus();CRITICAL: Custom claims are cached in the ID token. After setting claims, the user must:
- Sign out and sign in again, OR
- Force refresh the token with
getIdTokenResult(true)
typescript
import { auth } from './firebase';
async function checkAdminStatus() {
const user = auth.currentUser;
if (!user) return false;
// 强制刷新令牌以获取最新声明
const tokenResult = await user.getIdTokenResult(true);
return tokenResult.claims.admin === true;
}
// 在组件中使用
const isAdmin = await checkAdminStatus();重要提示: 自定义声明会被缓存到ID令牌中。设置声明后,用户必须:
- 登出后重新登录,或者
- 使用强制刷新令牌
getIdTokenResult(true)
Phone Authentication
电话认证
typescript
import {
signInWithPhoneNumber,
RecaptchaVerifier,
ConfirmationResult,
} from 'firebase/auth';
import { auth } from './firebase';
let confirmationResult: ConfirmationResult;
// Step 1: Setup reCAPTCHA (required)
function setupRecaptcha() {
window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
size: 'normal',
callback: () => {
// reCAPTCHA solved
},
});
}
// Step 2: Send verification code
async function sendVerificationCode(phoneNumber: string) {
const appVerifier = window.recaptchaVerifier;
confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, appVerifier);
}
// Step 3: Verify code
async function verifyCode(code: string) {
const result = await confirmationResult.confirm(code);
return result.user;
}typescript
import {
signInWithPhoneNumber,
RecaptchaVerifier,
ConfirmationResult,
} from 'firebase/auth';
import { auth } from './firebase';
let confirmationResult: ConfirmationResult;
// 步骤1:设置reCAPTCHA(必填)
function setupRecaptcha() {
window.recaptchaVerifier = new RecaptchaVerifier(auth, 'recaptcha-container', {
size: 'normal',
callback: () => {
// reCAPTCHA验证通过
},
});
}
// 步骤2:发送验证码
async function sendVerificationCode(phoneNumber: string) {
const appVerifier = window.recaptchaVerifier;
confirmationResult = await signInWithPhoneNumber(auth, phoneNumber, appVerifier);
}
// 步骤3:验证验证码
async function verifyCode(code: string) {
const result = await confirmationResult.confirm(code);
return result.user;
}Multi-Factor Authentication (MFA)
多因素认证(MFA)
typescript
import {
multiFactor,
PhoneAuthProvider,
PhoneMultiFactorGenerator,
getMultiFactorResolver,
} from 'firebase/auth';
import { auth } from './firebase';
// Enroll phone as second factor
async function enrollPhoneMFA(phoneNumber: string) {
const user = auth.currentUser;
if (!user) throw new Error('Not authenticated');
const multiFactorSession = await multiFactor(user).getSession();
const phoneAuthProvider = new PhoneAuthProvider(auth);
const verificationId = await phoneAuthProvider.verifyPhoneNumber(
{ phoneNumber, session: multiFactorSession },
window.recaptchaVerifier
);
// Return verificationId to complete enrollment after user enters code
return verificationId;
}
// Complete enrollment
async function completeEnrollment(verificationId: string, verificationCode: string) {
const user = auth.currentUser;
if (!user) throw new Error('Not authenticated');
const credential = PhoneAuthProvider.credential(verificationId, verificationCode);
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(credential);
await multiFactor(user).enroll(multiFactorAssertion, 'Phone Number');
}
// Handle MFA during sign-in
async function handleMFASignIn(error: any) {
if (error.code !== 'auth/multi-factor-auth-required') {
throw error;
}
const resolver = getMultiFactorResolver(auth, error);
// Show UI to select MFA method and enter code
return resolver;
}typescript
import {
multiFactor,
PhoneAuthProvider,
PhoneMultiFactorGenerator,
getMultiFactorResolver,
} from 'firebase/auth';
import { auth } from './firebase';
// 注册电话作为第二因素
async function enrollPhoneMFA(phoneNumber: string) {
const user = auth.currentUser;
if (!user) throw new Error('未认证');
const multiFactorSession = await multiFactor(user).getSession();
const phoneAuthProvider = new PhoneAuthProvider(auth);
const verificationId = await phoneAuthProvider.verifyPhoneNumber(
{ phoneNumber, session: multiFactorSession },
window.recaptchaVerifier
);
// 返回verificationId,供用户输入验证码后完成注册
return verificationId;
}
// 完成注册
async function completeEnrollment(verificationId: string, verificationCode: string) {
const user = auth.currentUser;
if (!user) throw new Error('未认证');
const credential = PhoneAuthProvider.credential(verificationId, verificationCode);
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(credential);
await multiFactor(user).enroll(multiFactorAssertion, '电话号码');
}
// 登录时处理多因素认证
async function handleMFASignIn(error: any) {
if (error.code !== 'auth/multi-factor-auth-required') {
throw error;
}
const resolver = getMultiFactorResolver(auth, error);
// 显示UI让用户选择多因素认证方式并输入验证码
return resolver;
}Protected Routes (Next.js)
受保护路由(Next.js)
Middleware Protection
中间件保护
typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const sessionCookie = request.cookies.get('session')?.value;
// Protect /dashboard routes
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!sessionCookie) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Redirect authenticated users away from /login
if (request.nextUrl.pathname === '/login') {
if (sessionCookie) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const sessionCookie = request.cookies.get('session')?.value;
// 保护/dashboard路由
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!sessionCookie) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// 已认证用户重定向离开/login页面
if (request.nextUrl.pathname === '/login') {
if (sessionCookie) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};Client-Side Route Guard
客户端路由守卫
typescript
// components/ProtectedRoute.tsx
import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/login');
}
}, [user, loading, router]);
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return null;
}
return <>{children}</>;
}typescript
// components/ProtectedRoute.tsx
import { useAuth } from '@/contexts/AuthContext';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push('/login');
}
}, [user, loading, router]);
if (loading) {
return <div>加载中...</div>;
}
if (!user) {
return null;
}
return <>{children}</>;
}Error Handling
错误处理
Common Auth Errors
常见认证错误
| Error Code | Description | User-Friendly Message |
|---|---|---|
| Wrong email/password | Invalid email or password |
| Email not registered | Invalid email or password |
| Incorrect password | Invalid email or password |
| Email registered | This email is already registered |
| Password < 6 chars | Password must be at least 6 characters |
| Malformed email | Please enter a valid email |
| Account disabled | Your account has been disabled |
| Rate limited | Too many attempts. Try again later |
| User closed popup | Sign-in cancelled |
| Browser blocked popup | Please allow popups |
| Sensitive operation | Please sign in again |
| Network error | Please check your connection |
| 错误代码 | 描述 | 用户友好提示 |
|---|---|---|
| 邮箱/密码错误 | 邮箱或密码无效 |
| 邮箱未注册 | 邮箱或密码无效 |
| 密码错误 | 邮箱或密码无效 |
| 邮箱已注册 | 该邮箱已被注册 |
| 密码长度不足6位 | 密码长度至少为6位 |
| 邮箱格式错误 | 请输入有效的邮箱地址 |
| 账户已被禁用 | 您的账户已被禁用 |
| 请求频率受限 | 尝试次数过多,请稍后再试 |
| 用户关闭弹窗 | 登录已取消 |
| 浏览器拦截弹窗 | 请允许弹窗显示 |
| 敏感操作需要重新登录 | 请重新登录 |
| 网络错误 | 请检查您的网络连接 |
Error Handler Utility
错误处理工具函数
typescript
export function getAuthErrorMessage(error: any): string {
const errorMessages: Record<string, string> = {
'auth/invalid-credential': 'Invalid email or password',
'auth/user-not-found': 'Invalid email or password',
'auth/wrong-password': 'Invalid email or password',
'auth/email-already-in-use': 'This email is already registered',
'auth/weak-password': 'Password must be at least 6 characters',
'auth/invalid-email': 'Please enter a valid email address',
'auth/user-disabled': 'Your account has been disabled',
'auth/too-many-requests': 'Too many attempts. Please try again later',
'auth/popup-closed-by-user': 'Sign-in was cancelled',
'auth/network-request-failed': 'Network error. Please check your connection',
};
return errorMessages[error.code] || 'An unexpected error occurred';
}typescript
export function getAuthErrorMessage(error: any): string {
const errorMessages: Record<string, string> = {
'auth/invalid-credential': '邮箱或密码无效',
'auth/user-not-found': '邮箱或密码无效',
'auth/wrong-password': '邮箱或密码无效',
'auth/email-already-in-use': '该邮箱已被注册',
'auth/weak-password': '密码长度至少为6位',
'auth/invalid-email': '请输入有效的邮箱地址',
'auth/user-disabled': '您的账户已被禁用',
'auth/too-many-requests': '尝试次数过多,请稍后再试',
'auth/popup-closed-by-user': '登录已取消',
'auth/network-request-failed': '网络错误,请检查您的网络连接',
};
return errorMessages[error.code] || '发生了意外错误';
}Known Issues Prevention
已知问题预防
This skill prevents 12 documented Firebase Auth errors:
| Issue # | Error/Issue | Description | How to Avoid | Source |
|---|---|---|---|---|
| #1 | | Generic error for wrong email/password | Show generic "invalid email or password" message | Common |
| #2 | | Browser blocks OAuth popup | Implement redirect fallback | Docs |
| #3 | | Sensitive operations need fresh login | Re-authenticate before password change, delete | Docs |
| #4 | Custom claims not updating | Claims cached in ID token | Force token refresh after setting claims | Docs |
| #5 | Token expiration | ID tokens expire after 1 hour | Always call | Common |
| #6 | Memory leak from auth listener | Not unsubscribing from | Return cleanup function in useEffect | Common |
| #7 | CORS errors on localhost | Auth domain mismatch | Add localhost to authorized domains in console | Common |
| #8 | Private key newline issue | Escaped | Use | Common |
| #9 | Session cookie not persisting | Cookie settings wrong | Set | Common |
| #10 | OAuth state mismatch | User navigates away during OAuth | Handle | Common |
| #11 | Rate limiting | Too many auth attempts | Implement exponential backoff | Docs |
| #12 | Email enumeration | Different errors for existing/non-existing emails | Use same message for both | Security best practice |
本技能可预防12种已记录的Firebase Auth错误:
| 问题编号 | 错误/问题 | 描述 | 避免方法 | 来源 |
|---|---|---|---|---|
| #1 | | 邮箱/密码错误的通用提示 | 显示通用的“邮箱或密码无效”提示 | 常见问题 |
| #2 | | 浏览器拦截OAuth弹窗 | 实现重定向回退方案 | 文档 |
| #3 | | 敏感操作需要重新登录 | 在修改密码、删除账户等操作前重新认证用户 | 文档 |
| #4 | 自定义声明未更新 | 声明被缓存到ID令牌中 | 设置声明后强制刷新令牌 | 文档 |
| #5 | 令牌过期 | ID令牌1小时后过期 | 在API调用前始终调用 | 常见问题 |
| #6 | 认证监听器内存泄漏 | 未取消订阅 | 在useEffect中返回清理函数 | 常见问题 |
| #7 | 本地主机CORS错误 | 认证域名不匹配 | 在控制台中将localhost添加到授权域名 | 常见问题 |
| #8 | 私钥换行问题 | 环境变量中的 | 使用 | 常见问题 |
| #9 | 会话Cookie未持久化 | Cookie设置错误 | 生产环境中设置 | 常见问题 |
| #10 | OAuth状态不匹配 | 用户在OAuth过程中导航离开 | 优雅处理 | 常见问题 |
| #11 | 请求频率限制 | 认证尝试次数过多 | 实现指数退避机制 | 文档 |
| #12 | 邮箱枚举 | 针对存在/不存在的邮箱返回不同错误 | 对两种情况使用相同提示 | 安全最佳实践 |
Security Best Practices
安全最佳实践
Always Do
始终遵循
- Use HTTPS in production - Required for secure cookies
- Validate tokens server-side - Never trust client claims alone
- Handle token expiration - Refresh before API calls
- Implement rate limiting - Prevent brute force attacks
- Use same error message for wrong email/password - Prevent enumeration
- Enable email verification - Verify user owns email
- Use session cookies for SSR - More secure than ID tokens in cookies
- Revoke tokens on password change - Invalidate old sessions
- 生产环境使用HTTPS - 安全Cookie的必要条件
- 服务端验证令牌 - 绝不单独信任客户端声明
- 处理令牌过期 - API调用前刷新令牌
- 实现请求频率限制 - 防止暴力破解攻击
- 邮箱/密码错误使用相同提示 - 防止邮箱枚举
- 启用邮箱验证 - 验证用户拥有该邮箱
- SSR使用会话Cookie - 比Cookie中的ID令牌更安全
- 修改密码时撤销令牌 - 使旧会话失效
Never Do
绝不做
- Never expose private key in client code
- Never trust client-provided claims - Always verify server-side
- Never store ID tokens in localStorage - Use httpOnly cookies
- Never disable email enumeration protection in production
- Never skip CORS configuration for your domains
- 绝不暴露私钥在客户端代码中
- 绝不信任客户端提供的声明 - 始终在服务端验证
- 绝不将ID令牌存储在localStorage中 - 使用HttpOnly Cookie
- 生产环境绝不禁用邮箱枚举保护
- 绝不跳过域名的CORS配置
Firebase CLI Commands
Firebase CLI命令
bash
undefinedbash
undefinedInitialize Firebase project
初始化Firebase项目
firebase init
firebase init
Enable auth emulator
启用认证模拟器
firebase emulators:start --only auth
firebase emulators:start --only auth
Export auth users
导出认证用户
firebase auth:export users.json --format=json
firebase auth:export users.json --format=json
Import auth users
导入认证用户
firebase auth:import users.json
---firebase auth:import users.json
---Package Versions (Verified 2026-01-25)
包版本(2026-01-25已验证)
json
{
"dependencies": {
"firebase": "^12.8.0"
},
"devDependencies": {
"firebase-admin": "^13.6.0"
}
}json
{
"dependencies": {
"firebase": "^12.8.0"
},
"devDependencies": {
"firebase-admin": "^13.6.0"
}
}Official Documentation
官方文档
- Firebase Auth Overview: https://firebase.google.com/docs/auth
- Web Get Started: https://firebase.google.com/docs/auth/web/start
- Admin Auth: https://firebase.google.com/docs/auth/admin
- Custom Claims: https://firebase.google.com/docs/auth/admin/custom-claims
- Session Cookies: https://firebase.google.com/docs/auth/admin/manage-cookies
- Security Rules: https://firebase.google.com/docs/rules
Last verified: 2026-01-25 | Skill version: 1.0.0
- Firebase Auth概述: https://firebase.google.com/docs/auth
- Web快速入门: https://firebase.google.com/docs/auth/web/start
- Admin Auth: https://firebase.google.com/docs/auth/admin
- 自定义声明: https://firebase.google.com/docs/auth/admin/custom-claims
- 会话Cookie: https://firebase.google.com/docs/auth/admin/manage-cookies
- 安全规则: https://firebase.google.com/docs/rules
最后验证时间: 2026-01-25 | 技能版本: 1.0.0