Implement secure JWT authentication with refresh tokens and best practices
# JWT Authentication Complete Guide for Google Antigravity
Implement secure JWT authentication in your Google Antigravity projects. This guide covers token generation, validation, refresh tokens, and security best practices.
## JWT Token Service
Create a secure token management service:
```typescript
// src/lib/auth/jwt.ts
import { SignJWT, jwtVerify, JWTPayload } from "jose";
import { cookies } from "next/headers";
interface TokenPayload extends JWTPayload {
userId: string;
email: string;
role: "user" | "admin";
sessionId: string;
}
interface TokenPair {
accessToken: string;
refreshToken: string;
}
const ACCESS_TOKEN_SECRET = new TextEncoder().encode(process.env.JWT_ACCESS_SECRET);
const REFRESH_TOKEN_SECRET = new TextEncoder().encode(process.env.JWT_REFRESH_SECRET);
const ACCESS_TOKEN_EXPIRY = "15m";
const REFRESH_TOKEN_EXPIRY = "7d";
export async function generateTokenPair(payload: Omit<TokenPayload, "iat" | "exp" | "jti">): Promise<TokenPair> {
const sessionId = crypto.randomUUID();
const tokenPayload = { ...payload, sessionId };
const accessToken = await new SignJWT(tokenPayload)
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuedAt()
.setExpirationTime(ACCESS_TOKEN_EXPIRY)
.setJti(crypto.randomUUID())
.sign(ACCESS_TOKEN_SECRET);
const refreshToken = await new SignJWT({ sessionId, userId: payload.userId })
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuedAt()
.setExpirationTime(REFRESH_TOKEN_EXPIRY)
.setJti(crypto.randomUUID())
.sign(REFRESH_TOKEN_SECRET);
// Store refresh token in database
await storeRefreshToken(sessionId, payload.userId, refreshToken);
return { accessToken, refreshToken };
}
export async function verifyAccessToken(token: string): Promise<TokenPayload | null> {
try {
const { payload } = await jwtVerify(token, ACCESS_TOKEN_SECRET);
return payload as TokenPayload;
} catch (error) {
if (error instanceof Error && error.name === "JWTExpired") {
console.log("Access token expired");
}
return null;
}
}
export async function verifyRefreshToken(token: string): Promise<{ sessionId: string; userId: string } | null> {
try {
const { payload } = await jwtVerify(token, REFRESH_TOKEN_SECRET);
// Check if refresh token is still valid in database
const isValid = await validateStoredRefreshToken(
payload.sessionId as string,
token
);
if (!isValid) {
return null;
}
return {
sessionId: payload.sessionId as string,
userId: payload.userId as string,
};
} catch {
return null;
}
}
export async function refreshAccessToken(refreshToken: string): Promise<TokenPair | null> {
const tokenData = await verifyRefreshToken(refreshToken);
if (!tokenData) {
return null;
}
// Get user data from database
const user = await getUserById(tokenData.userId);
if (!user) {
await revokeRefreshToken(tokenData.sessionId);
return null;
}
// Revoke old refresh token (rotation)
await revokeRefreshToken(tokenData.sessionId);
// Generate new token pair
return generateTokenPair({
userId: user.id,
email: user.email,
role: user.role,
sessionId: tokenData.sessionId,
});
}
```
## Authentication Middleware
Protect routes with JWT validation:
```typescript
// src/middleware.ts
import { NextRequest, NextResponse } from "next/server";
import { verifyAccessToken, refreshAccessToken } from "@/lib/auth/jwt";
const PUBLIC_PATHS = ["/", "/login", "/signup", "/api/auth/login", "/api/auth/signup"];
const AUTH_PATHS = ["/login", "/signup"];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Skip public paths
if (PUBLIC_PATHS.some((path) => pathname === path || pathname.startsWith("/api/public"))) {
return NextResponse.next();
}
const accessToken = request.cookies.get("access_token")?.value;
const refreshToken = request.cookies.get("refresh_token")?.value;
// No tokens - redirect to login
if (!accessToken && !refreshToken) {
if (pathname.startsWith("/api")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.redirect(new URL("/login", request.url));
}
// Try to verify access token
let user = accessToken ? await verifyAccessToken(accessToken) : null;
// If access token invalid, try refresh
if (!user && refreshToken) {
const newTokens = await refreshAccessToken(refreshToken);
if (newTokens) {
user = await verifyAccessToken(newTokens.accessToken);
if (user) {
const response = NextResponse.next();
// Set new tokens in cookies
response.cookies.set("access_token", newTokens.accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 15, // 15 minutes
});
response.cookies.set("refresh_token", newTokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7, // 7 days
});
return response;
}
}
}
// Still no valid user - clear cookies and redirect
if (!user) {
const response = pathname.startsWith("/api")
? NextResponse.json({ error: "Unauthorized" }, { status: 401 })
: NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("access_token");
response.cookies.delete("refresh_token");
return response;
}
// Redirect authenticated users away from auth pages
if (AUTH_PATHS.some((path) => pathname.startsWith(path))) {
return NextResponse.redirect(new URL("/dashboard", request.url));
}
// Add user info to headers for API routes
const response = NextResponse.next();
response.headers.set("x-user-id", user.userId);
response.headers.set("x-user-role", user.role);
return response;
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico|public).*)"],
};
```
## Login API Route
Implement secure login endpoint:
```typescript
// src/app/api/auth/login/route.ts
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import bcrypt from "bcryptjs";
import { generateTokenPair } from "@/lib/auth/jwt";
import { getUserByEmail } from "@/lib/db/users";
import { withErrorHandler } from "@/lib/api/errorHandler";
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
rememberMe: z.boolean().optional(),
});
export const POST = withErrorHandler(async (request: NextRequest) => {
const body = await request.json();
const { email, password, rememberMe } = loginSchema.parse(body);
// Get user from database
const user = await getUserByEmail(email);
if (!user) {
// Use same error for security (prevent email enumeration)
return NextResponse.json(
{ error: "Invalid credentials" },
{ status: 401 }
);
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.passwordHash);
if (!isValidPassword) {
// Log failed attempt for rate limiting
await logFailedLoginAttempt(email);
return NextResponse.json(
{ error: "Invalid credentials" },
{ status: 401 }
);
}
// Check if account is locked
if (user.lockedUntil && new Date(user.lockedUntil) > new Date()) {
return NextResponse.json(
{ error: "Account is temporarily locked. Please try again later." },
{ status: 423 }
);
}
// Generate tokens
const tokens = await generateTokenPair({
userId: user.id,
email: user.email,
role: user.role,
sessionId: crypto.randomUUID(),
});
// Clear failed attempts
await clearFailedLoginAttempts(email);
const response = NextResponse.json({
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
},
});
// Set cookies
const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax" as const,
};
response.cookies.set("access_token", tokens.accessToken, {
...cookieOptions,
maxAge: 60 * 15,
});
response.cookies.set("refresh_token", tokens.refreshToken, {
...cookieOptions,
maxAge: rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7,
});
return response;
});
```
## Client-Side Auth Hook
Create a React hook for authentication:
```typescript
// src/hooks/useAuth.ts
"use client";
import { useState, useEffect, useCallback } from "react";
import { useRouter } from "next/navigation";
interface User {
id: string;
email: string;
name: string;
role: "user" | "admin";
}
export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const router = useRouter();
useEffect(() => {
checkAuth();
}, []);
const checkAuth = async () => {
try {
const response = await fetch("/api/auth/me");
if (response.ok) {
const data = await response.json();
setUser(data.user);
}
} catch (error) {
console.error("Auth check failed:", error);
} finally {
setLoading(false);
}
};
const login = useCallback(async (email: string, password: string) => {
const response = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Login failed");
}
const data = await response.json();
setUser(data.user);
router.push("/dashboard");
}, [router]);
const logout = useCallback(async () => {
await fetch("/api/auth/logout", { method: "POST" });
setUser(null);
router.push("/login");
}, [router]);
return { user, loading, login, logout, checkAuth };
}
```
Google Antigravity generates secure JWT authentication implementations with proper token rotation, refresh token handling, and security best practices.This Auth prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their auth implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For Auth projects, consider mentioning your framework version, coding style, and any specific libraries you're using.