Loading...
Loading...
Authentication and security patterns for EFT-Tracker using NextAuth. Covers password reset, session management, CSRF protection, and security reviews. Activates when user mentions: auth, authentication, password, NextAuth, session, security, login, logout, CSRF, rate limit, token, JWT.
npx skill4agent add tuckerandrew21/eft-tracker security-auth// Secure session settings
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: true, // HTTPS only in production
},
},
},
};// Server-side protection
import { getServerSession } from "next-auth";
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... protected logic
}import bcrypt from "bcrypt";
const SALT_ROUNDS = 12; // Minimum 10, 12 recommended
// Hash password
const hashedPassword = await bcrypt.hash(plainPassword, SALT_ROUNDS);
// Verify password
const isValid = await bcrypt.compare(plainPassword, hashedPassword);crypto.randomBytes(32)dangerouslySetInnerHTMLnpm auditimport { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json({ error: "Too many requests" }, { status: 429 });
}
// ... handle request
}| Endpoint | Limit | Window |
|---|---|---|
| Login | 5 attempts | 15 min |
| Password reset | 3 requests | 1 hour |
| API general | 100 requests | 1 min |
| Registration | 3 accounts | 1 hour |
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
});
const resetSchema = z.object({
email: z.string().email(),
});
const newPasswordSchema = z.object({
token: z.string().min(32),
password: z
.string()
.min(8)
.max(128)
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[0-9]/, "Must contain number"),
});// next.config.ts
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
];// BAD - Hardcoded secret
const API_KEY = "sk-1234567890";
// BAD - SQL injection risk
const query = `SELECT * FROM users WHERE id = ${userId}`;
// BAD - XSS risk
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
// BAD - Timing attack vulnerability
if (token === storedToken) {
/* ... */
}// GOOD - Environment variable
const API_KEY = process.env.API_KEY;
// GOOD - Parameterized (Prisma)
const user = await prisma.user.findUnique({ where: { id: userId } });
// GOOD - Escaped output
return <div>{userInput}</div>;
// GOOD - Constant-time comparison
import { timingSafeEqual } from "crypto";
if (timingSafeEqual(Buffer.from(token), Buffer.from(storedToken))) {
/* ... */
}NEXTAUTH_SECRETNEXTAUTH_URLRESEND_API_KEY.env