最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

reactjs - How can I set a CSRF cookie as a server cookie in NextJS to send it to ExpressJS backend? - Stack Overflow

programmeradmin2浏览0评论

About my architecture: I have a backend with Express.js and a frontend with Next.js.

I have massive problems with my CSRF cookie. I create it in Express.js and send it to Next.js (Client). Here it is cached in a provider so that it is available for other components. However, although I set it, it is not passed as a cookie for requests.

My backend (Express.js) looks like this: server.ts

//...
import csrfProtection from "./middlewares/csrf";
//...
const corsOptions = {
    origin: [clientHost],
    credentials: true,
    allowedHeaders: ["Content-Type", "Authorization"],
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
}
app.use(cors(corsOptions));
app.use(csrfProtection);
app.use(helmet.contentSecurityPolicy({
    directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        imgSrc: ["'self'"],
        connectSrc: ["'self'"],
        fontSrc: ["'self'"],
        objectSrc: ["'none'"],
        mediaSrc: ["'self'"],
        frameSrc: ["'none'"]
    }
}));
app.use(helmet.crossOriginEmbedderPolicy({ policy: 'require-corp' }));
app.use(helmet.crossOriginOpenerPolicy({ policy: 'same-origin' }));
app.use(helmet.crossOriginResourcePolicy({ policy: 'same-origin' }));
app.use(helmet.referrerPolicy({ policy: 'no-referrer' }));
app.use(helmet.xssFilter());
app.use(helmet.hsts({ maxAge: 5184000, includeSubDomains: true, preload: true })); // HSTS
app.use(helmet.hidePoweredBy());
app.use(helmet.frameguard({ action: 'deny' }));
app.use(helmet.dnsPrefetchControl({ allow: false })); // deactivate DNS prefetching
app.use(helmet.noSniff());
app.use(helmet.originAgentCluster());

middlewares/csrf.ts

import csurf from "csurf";
import { Request, Response, NextFunction } from "express";

// initialize
const csrfProtection = csurf({
    cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax',
        maxAge: 3600 * 1000,
    },
    ignoreMethods: ["GET", "HEAD", "OPTIONS"]
});

export function getCsrfToken(req: Request, res: Response, next: NextFunction) {
    res.cookie("csrfToken", req.csrfToken(), {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'lax',
        maxAge: 3600000,
    })
    res.json({ csrfToken: req.csrfToken() });

}


export default csrfProtection; 

api/router.ts [THIS PART WORKS FINE - ONLY FOR COMPLETENESS]

import { getCsrfToken } from "../middlewares/csrf";
const router = express.Router();
router.get("/csrf-token", getCsrfToken);
export default router; 

api/auth/router.ts [THIS PART WORKS FINE - ONLY FOR COMPLETENESS]

import csrfProtection from "../../middlewares/csrf";
const router = express.Router();
router.post("/login",csrfProtection, login); 

My frontend (Next.js) looks like this: providers/csrfprovider.ts

"use client";

import { createContext, useContext, useState, useEffect } from "react";

export const CsrfContext = createContext<string | null>(null);

export const CsrfProvider = ({ children }: { children: React.ReactNode }) => {
    const [csrfToken, setCsrfToken] = useState<string | null>(null);

    useEffect(() => {
        const fetchCsrfToken = async () => {
            try {
                const response = await fetch(`/api/csrf-token`, {
                    credentials: "include", // Send cookies with the request
                });
                const data = await response.json();
                setCsrfToken(data.csrfToken);
            } catch (err) {
                console.error("not able to load CSRF-Token", err);
            }
        };
        fetchCsrfToken();
    }, []);

    return (
        <CsrfContext.Provider value={csrfToken}>{children}</CsrfContext.Provider>
    )
}

export const useCsrfToken = () => useContext(CsrfContext);

app/api/csrf-token/route.ts

import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
    try {
        const response = await fetch(`${process.env.SERVER_HOST}api/csrf-token`, {
            credentials: "include",
        });

        if (!response.ok) {
            throw new Error("Failed to fetch CSRF token");
        }

        const data = await response.json();
        const csrfToken = data.csrfToken;

        const cookieStore = cookies();
        (await cookieStore).set('csrfToken', csrfToken, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'lax',
            maxAge: 3600000,
            path: "/",
        });

        return NextResponse.json({ csrfToken: csrfToken });
    } catch (error) {
        console.error("Error fetching CSRF token:", error);
        return NextResponse.json({ error: "Failed to load CSRF token" }, { status: 500 });
    }
}

app/api/login/route.ts

import { NextResponse } from "next/server";

export async function POST(request: Request) {
    try {
        const { username, password, csrfToken } = await request.json();

        if (!username || !password || !csrfToken) {
            return NextResponse.json(
                { error: "Missing required fields" },
                { status: 400 }
            );
        }

        const loginPayload = {
            username,
            password,
        };

        const response = await fetch(`${process.env.SERVER_HOST}api/auth/login`, {
            method: "POST",
            headers: {
                "Content-Type": "application/json",
                "X-CSRF-Token": csrfToken,
            },
            credentials: "include",
            body: JSON.stringify(loginPayload),
        });
        // console.log(response)

        if (!response.ok) {
            throw new Error("Failed to login");
        }

        const data = await response.json();

        return NextResponse.json(data);

    } catch (error) {
        console.error("Error during login request:", error);
        return NextResponse.json(
            { error: "Failed to process login" },
            { status: 500 }
        );
    }
}

I log the request in the backend. A request always looks like this:

[...] 
Cookies: {}, 
Query Params: {}, 
Headers: {[...]"x-csrf-token":"Q6iGi2sq-kP1w3QDCo6jpw8DFkIV-OF-oDy4"}, 

Why is the sent cookie not returned? why is no cookie transferred even though it is set? I can see in the Development console that the cookie is there.

The CSRF protection logically rejects all requests because the cookie (which is not present) does not match the token in the header.

Can someone help me to better understand and solve the whole thing and the problem?

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论