Firebase Authentication integration with email/password and Google OAuth for secure user management.
Overview
ShipSafe uses Firebase Authentication to handle user authentication securely. It supports multiple authentication methods and provides both client-side and server-side utilities for managing user sessions.
Features:
- Email/password authentication (Firebase client →
{ idToken }→ session cookie) - Google OAuth (same session path;
GoogleSignInButtonon/auth) - Password reset flow
- Session management with Firebase httpOnly session cookies (never raw UID)
- Protected routes and API endpoints (
requireAuthverifies session/Bearer) - Server-side user verification
Default auth recipe: password + Google. Optional magic-link is a separate additive pack (not mounted on /auth by default).
Setup
1. Configure Firebase
See Firebase Setup Guide for complete instructions.
Required steps:
- Create Firebase project
- Enable Authentication methods (Email/Password, Google)
- Add Firebase config to
.env.local - Configure OAuth redirect URLs
2. Environment Variables
Add to .env.local:
# Firebase Client Config
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
# Firebase Admin (Server-side)
FIREBASE_CLIENT_EMAIL=your-service-account-email
FIREBASE_PRIVATE_KEY=your-private-key
Authentication Methods
Email/Password
Users can sign up and log in with email and password.
Login Form:
- Located at
/authpage - Email and password fields
- Validation and error handling
- Redirects to dashboard on success
Signup Form:
- Located at
/authpage - Email, password, and confirm password
- Password strength validation
- Creates new user account
Google OAuth
Users can sign in with their Google account.
Setup:
- Enable Google provider in Firebase Console
- Add authorized domains
- Configure OAuth consent screen (if needed)
- Test sign-in flow
User Experience:
- Click "Sign in with Google" button
- Redirects to Google sign-in
- Returns with authentication token
- Creates account automatically if new user
Password Reset
Users request a reset email via /auth/reset → POST /api/auth/reset.
Server mints a Firebase reset link, emails a branded /auth/reset?oobCode=… URL via Resend, and the client confirms with confirmPasswordReset (Admin SDK cannot verify oob codes).
Requires RESEND_API_KEY.
Flow:
- User clicks "Forgot password?" on
/auth - Enters email on
/auth/reset - Receives Resend email with branded link
- Sets new password via
confirmPasswordReset
Optional: Magic link (Recipe B)
Additive pack — not on the default /auth page:
| Piece | Path |
|---|---|
| Form | src/components/forms/MagicLinkForm.tsx |
| Server | src/features/auth/magic-link.ts |
| Client complete | src/features/auth/complete-magic-link.ts |
| APIs | /api/auth/magic-link, /api/auth/magic-link/verify |
| Callback | /auth/callback |
To opt in: mount MagicLinkForm on /auth (replace or add alongside password forms). Still ends at POST /api/auth/login { idToken } for the session cookie.
Client-Side Usage
useAuth Hook
Use the useAuth hook in client components:
"use client";
import { useAuth } from "@/lib/firebase/client";
export default function MyComponent() {
const { user, loading, error, login, logout, signup } = useAuth();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!user) return <div>Not logged in</div>;
return (
<div>
<p>Welcome, {user.email}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
Available Methods
user- Current user object (null if not authenticated)loading- Boolean indicating auth state loadingerror- Error message if authentication failslogin(email, password)- Sign in with email/passwordlogout()- Sign out current usersignup(email, password)- Create new accountresetPassword(email)- Send password reset email
Server-Side Usage
Get Current User
Use getCurrentUserServer() in API routes or server components:
import { getCurrentUserServer } from "@/lib/firebase/auth";
import { NextRequest } from "next/server";
export async function GET(req: NextRequest) {
const user = await getCurrentUserServer(req);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
// User is authenticated
return Response.json({ userId: user.uid });
}
Require Authentication
Use requireAuth() to throw error if not authenticated:
import { requireAuth } from "@/lib/firebase/auth";
export async function POST(req: NextRequest) {
const user = await requireAuth(req); // Throws if not authenticated
// User is guaranteed to be authenticated here
return Response.json({ userId: user.uid });
}
API Routes
Default auth flow (password + Google)
- Client: Firebase Auth (
signInWithEmailAndPassword,createUserWithEmailAndPassword, orsignInWithPopup) - Client:
user.getIdToken()→POST /api/auth/loginwith{ idToken } - Server:
verifyIdToken→ mint Firebase session cookie (createSessionCookie) → set httpOnlysessioncookie - Server APIs:
requireAuthverifiessessioncookie first, then Bearer / legacyfirebase_token
FOOTGUN: Never POST { email, password } to the API. Never set session to a raw UID.
/api/auth/login
Exchange a Firebase ID token for an httpOnly session cookie. Also bootstraps users/{uid} on first login.
Request:
POST /api/auth/login
{
idToken: string; // from Firebase client SDK — NOT email/password
}
Response:
{
success: boolean;
data?: { uid: string; email: string | null; emailVerified: boolean };
error?: string;
}
Sets cookie: session=<Firebase session cookie> (opaque, 5-day maxAge). Middleware only checks cookie presence; requireAuth verifies the signature.
/api/auth/signup
Legacy Admin-SDK signup route still exists for advanced/server flows. The default SignupForm does not call it — it creates the user with the Firebase client SDK, then calls /api/auth/login with { idToken }.
/api/auth/logout
Sign out current user and clear session.
Request:
POST /api/auth/logout
/api/auth/reset
Request password reset email.
Request:
POST /api/auth/reset
{
email: string;
}
/api/auth/reset/verify
Verify password reset token and set new password.
Request:
POST /api/auth/reset/verify
{
token: string;
newPassword: string;
}
Protected Routes
Middleware Protection
Use Next.js middleware to protect routes automatically:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getCurrentUserServer } from "@/lib/firebase/auth";
export async function middleware(req: NextRequest) {
const user = await getCurrentUserServer(req);
if (!user && req.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/auth", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: "/dashboard/:path*",
};
See Protected Pages Tutorial for detailed examples.
Component-Level Protection
Protect individual components:
"use client";
import { useAuth } from "@/lib/firebase/client";
import { useRouter } from "next/navigation";
export default function ProtectedComponent() {
const { user, loading } = useAuth();
const router = useRouter();
useEffect(() => {
if (!loading && !user) {
router.push("/auth");
}
}, [user, loading, router]);
if (loading) return <div>Loading...</div>;
if (!user) return null;
return <div>Protected content</div>;
}
Security Features
- Secure token storage - Tokens stored in httpOnly cookies
- Server-side verification - All auth checks happen server-side
- Password hashing - Handled by Firebase automatically
- Rate limiting - Firebase provides built-in protection
- Email verification - Optional email verification flow
Best Practices
- Always verify server-side - Never trust client-side auth state
- Use middleware - Protect routes at the edge for better performance
- Handle errors gracefully - Show user-friendly error messages
- Session management - Use cookies for persistent sessions
- Password requirements - Enforce strong passwords client-side
Troubleshooting
"Invalid credentials" error
- Verify email/password are correct
- Check Firebase Authentication is enabled
- Ensure user exists in Firebase Console
Google OAuth not working
- Verify authorized domains in Firebase Console
- Check OAuth consent screen configuration
- Ensure redirect URLs are correct
Session not persisting
- Check cookie settings in auth configuration
- Verify domain matches production domain
- Check browser cookie settings
Learn More
- Firebase Setup - Configure Firebase
- Authentication Tutorial - Step-by-step guide
- Protected Pages Tutorial - Protect routes
- API Routes Tutorial - Create authenticated APIs