0

I followed this tutorial to use HTTP-Only cookies in my project: https://maxschmitt.me/posts/next-js-http-only-cookie-auth-tokens

The backend is working fine, I can see the cookie in Postman. But for reference, I'll post the backend code: (note: secure: true is hashed)

router.post('/login', upload.none(), async (req, res, next) => {

    const { username, password } = req.body;

    if (password != "12345678") {
        return res.status(401).send({
            message: `You're not authorized!`,
            success: false
        })
    }
    const admin_id = "1";

    const payload = { id: admin_id, role: "admin" };
    const jwt = JWT.sign(payload, secret, { algorithm: 'HS256', expiresIn: "7d" });

    res.cookie( "token", jwt, {
        httpOnly: true,
        // secure: true // only works on https
    });

    return res.status(200).send({
        message: 'Logged in!',
        success: true
    });
});

The frontend:

The admin login service:

const adminLogin = async (url: string, data: any) => {

    const { arg: { username, password } } = data;

    let formData = new FormData();
    formData.append("username", username);
    formData.append("password", password);

    try {
        const result = await axios.post(url, formData, { headers: { "Content-Type": "multipart/form-data" } });
        console.log(result);
        return result.data;
    } catch (error) {
        const err = error as AxiosError;
        return err.response!.data;
    }
};

The login page: (I'm using SWR package, the onLogin is inside LoginPage component)

const { trigger, isMutating } = useSWRMutation("http://localhost:8000/api/v1/admins/login", adminLogin);

const onLogin = async (data: any) => {
        const response = await trigger(data);

        if (response.success === true) {
            console.log(response);
        } else {
            setErrorMsg(response.message);
            setOpenAlertDialog(true);
        }
    };

next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: true
};

module.exports = {
    async rewrites() {
        return [{
            source: "/api/v1/:path*",
            destination: "http://localhost:8000/api/v1/:path*"
        }];
    }
};

module.exports = nextConfig;

api/[...path].ts:

import httpProxy from "http-proxy";
import Cookies from "cookies";
import url from "url";


const proxy = httpProxy.createProxyServer();

export const config = {
    api: {
        bodyParser: false
    }
};

const main = (req: any, res: any) => {
    return new Promise<void>((resolve, reject) => {
        const pathname = url.parse(req.url).pathname;
        const isLogin = pathname === "/api/login" || pathname === "/api/v1/admins/login";

        const cookies = new Cookies(req, res);
        const authToken = cookies.get("token");

        req.url = req.url.replace(/^\/api/, "");

        req.headers.cookie = "";

        if (authToken) {
            req.headers["token"] = authToken;
        }

        if (isLogin) {
            proxy.once("proxyRes", interceptLoginResponse);
        }

        proxy.once("error", reject);
        proxy.web(req, res, {
            target: "http://localhost:8000/api/v1",
            autoRewrite: false,
            selfHandleResponse: isLogin
        });

        function interceptLoginResponse(proxyRes: any, req: any, res: any) {
            let apiResponseBody = "";
            proxyRes.on("data", (chunk: any) => {
                apiResponseBody += chunk;
            });

            proxyRes.on("end", () => {
                try {
                    const { authToken } = JSON.parse(apiResponseBody);

                    const cookies = new Cookies(req, res);
                    cookies.set("token", authToken, {
                        httpOnly: true,
                        sameSite: "lax",
                        path: "/"
                    });

                    res.status(200).json({ loggedIn: true });
                    resolve();
                } catch (err) {
                    reject(err);
                }
            });
        }
    });
};

export default main;

What am I missing, and how can I cookies from the backend in the login function inside my next.js on my local machine? And what changes will I need to make in my production set up? (I'll be using NginX on my server).

Thanks in advance...

6
  • 1
    I'm not sure if it will help, but this are the cookie options I use: github.com/Eloi-Perez/v42-bears-team-27/blob/dev/packages/… Commented Mar 31, 2023 at 15:33
  • 1
    thanks for your comment.. so everything else is fine in my code, and I only need to change it to the one you provided? @Eloi Commented Mar 31, 2023 at 15:44
  • I just read all your code, sorry, that won't work for you. I'm using secure cookies so the client can't read the cookies. If I remember correctly you need to assign HttpOnly to false if you want to read the cookie with JS on the client, but you may need to change other configurations. Commented Mar 31, 2023 at 21:53
  • I want to have a solution for HttpOnly cookie that can't be read by the client. I thought my solution will do it. The tutorial indicated it will. But if you have another solution for HttpOnly cookies, please share it with me.. thanks in advance.. @Eloi Commented Mar 31, 2023 at 22:37
  • I can't read now all the article, but it doesn't look like you are following the diagram. It sends a JSON from the API to nextjs and then nextjs sets the cookie on the client. And is nextjs which validates the cookie, to later send a JSON to the express API. Commented Mar 31, 2023 at 23:08

2 Answers 2

0

You are not telling axios to set the cookies, so it just ignores any cookie it gets

try {
        const result = await axios.post(url, formData, { headers: { "Content-Type": "multipart/form-data" },withCredentials: true });
        console.log(result);
        return result.data;
    } catch (error) {
        const err = error as AxiosError;
        return err.response!.data;
    }
Sign up to request clarification or add additional context in comments.

3 Comments

Thank You!! It worked.. but now I have an error in the console that says: Access to XMLHttpRequest at 'localhost:8000/api/v1/admins/login' from origin 'localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Basically localhost:8000 is complaining because you are making a request to it from a different site. Localhost:3000 is a different domain than localhost:8000. You can either laxen the cors requirements, but that requires you to set an ssl certificate. Or you can put both ports behind a reverse proxy (I use nginx). That way both site live in the same domain
Thanks again for taking the time to reply... I thought the proxy server that I used will solve that problem, at least that what's the author said in the article..
0

Set 'use client' in the login service file from the frontend.

'use client';

const adminLogin = async (url: string, data: any) => {}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.