Next.js Discord

Discord Forum

Auth System with Seperate Express.js Server

Unanswered
Large oak-apple gall posted this in #help-forum
Open in Discord
Large oak-apple gallOP
For the project I wrote with Nextjs, I will also make a mobile application in the future. So, I needed to separate the backend of my project so that I could access it from the mobile application as well. While trying to do this, I got stuck in the auth system. There must be an auth system that works both in my nextjs project and in my mobile application. So I thought a jwt based auth system with jose library would be good.

I need to find out whether the user is allowed to access the panel every time an API is requested by checking the status column on the user database. If status is 0, the user should be forcibly logged out. It's not that hard to do this on the client-side. It's pretty simple to delete the cookie and redirect to the login page. But I couldn't do it on the server side. I've been trying for a few days but couldn't find a solution. Here is my code and the error message I got;

I use MariaDB for database. I provide database connection with Knex module.

Error Message:

Error: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options
src\lib\backend\useAuth.js (12:21) @ $$ACTION_0

  10 |         return result;
  11 |     } catch (error) {
> 12 |         cookieList.delete("token");
     |                           ^
  13 |         return error;
  14 |     }
  15 | };

4 Replies

Large oak-apple gallOP
Nextjs Structure
├── app
|  ├── (protected)
|  |  └── dashboard
|  |     ├── (type101)
|  |     |  └── reservations
|  |     |     └── page.jsx
|  |     ├── layout.jsx
|  |     ├── page.jsx
|  |     ├── profile
|  |     |  └── page.jsx
|  |     └── users
|  |        └── page.jsx
|  ├── (public)
|  |  └── login
|  |     └── page.jsx
|  ├── favicon.ico
|  ├── layout.js
|  └── page.js
├── jsconfig.json
├── middleware.js
├── next.config.mjs
├── package.json
├── public
├── README.md
├── src
|  ├── components
|  |  ├── Dashboard.jsx
|  |  ├── Header.jsx
|  |  └── Login.jsx
|  ├── lib
|  |  ├── backend
|  |  |  ├── useAuth.js
|  |  |  └── verifyJwt.js
|  |  ├── frontend
|  |  |  └── useSession.js
|  ├── _helpers
|  |  └── auth-repo.js
|  └── _routes
|     ├── auth.router.js
|     └── login.router.jsx
└── yarn.lock


useAuth.js
"use server";
import { cookies } from "next/headers";
import { authAPI } from "src/_routes/auth.router";

export const useAuth = async () => {
    const cookieList = cookies();
    const { value: token } = cookieList.get("token") ?? { value: null };
    try {
        const result = await authAPI.getSession(token);
        return result;
    } catch (error) {
        cookieList.delete("token");
        return error;
    }
};


Dashboard.jsx
import React from "react";
import { useAuth } from "src/lib/backend/useAuth";

const Dashboard = async () => {
    const auth = await useAuth();
    console.log(auth);
    return (
        <div>
            <button>Logout</button>
        </div>
    );
};

export default Dashboard;


app/dashboard/page.jsx
import React from "react";
import Dashboard from "src/components/Dashboard";

export const metadata = {
    title: "Dashboard",
    description: "Generated by create next app",
    robots: {
        index: false,
        follow: false,
    },
};

function Home() {
    return (
        <div>
            <Dashboard />
        </div>
    );
}

export default Home;
Node.js Structure

├── helper
|  ├── auth-repo.js
|  └── db.js
├── index.js
├── lib
|  ├── basicFunctions.js
|  ├── generateInt.js
|  ├── globalVariables.js
|  ├── mail-templates
|  |  ├── contactBodyMail.js
|  |  └── customerContactBodyMail.js
|  └── verifyJwt.js
├── middleware
|  └── auth.js
├── package.json
├── permission-taslak.json
├── routes
|  ├── auth.router.js
|  └── login.router.js
└── yarn.lock


index.js
import express from "express";
import "dotenv/config";
import { requireAuth } from "./middleware/auth.js";
import { authRouter } from "./routes/auth.router.js";
import cors from "cors";
import http from "http";
import { loginRouter } from "./routes/login.router.js";
const app = express();

const server = http.createServer(app);
const origin = process.env.NODE_ENV === "development" ? "http://192.168.1.13:3001" : "https://panel.agencive.com";
const PORT = process.env.PORT || 3004;

app.use(cors({ origin }));
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

app.use("/v1/login", loginRouter);
app.use("/v1/auth", requireAuth, authRouter);

server.listen(PORT, () => {
    console.log(`Server running on ${PORT}`);
});


auth.router.js
import express from "express";
import { authRepo } from "../helper/auth-repo.js";

const authRouter = express.Router();

authRouter.post("/session", authRepo.userSession);

export { authRouter };
middleware/auth.js
import { pool } from "../helper/db.js";
import { verifyJwtToken } from "../lib/verifyJwt.js";

export const requireAuth = async (req, res, next) => {
    const db = await pool.transaction();
    const authHeader = req.headers.authorization;
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
        throw JSON.stringify({ from: "node-middleware", message: "Yetkisiz Giriş: Kayıp ya da geçersiz token.", errorType: "unauthorized" });
    }
    try {
        const token = authHeader.split(" ")[1];
        const secret = new TextEncoder().encode(process.env.JWT_SECRET);
        const payload = await verifyJwtToken(token, secret);
        const tableName = `${payload.company_code}_users`;
        const [[checkUser]] = await db.raw("SELECT is_login, status FROM ?? WHERE userid = ?", [tableName, payload.sub]);
        if (checkUser.is_login !== 1 || checkUser.status !== 1) {
            throw JSON.stringify({
                from: "node-middleware",
                message: "Çıkış yapmaya zorlandınız. Lütfen tekrar giriş yapın.",
                errorType: "forcedlogout",
            });
        }
        await db.commit();
        next();
    } catch (error) {
        const parsedError = JSON.parse(error);
        await db.rollback();
        return res.status(401).json(parsedError);
    }
};
userSession Function
async function userSession(req, res) {
    const db = await pool.transaction();
    const { body } = req;
    try {
        const verifiedToken = await verifyJwtToken(body.token);
        const tableName = `${verifiedToken.company_code}_users`;
        const innerJoin = `${verifiedToken.company_code}_user_preferences`;
        const [[resUsers]] = await db.raw(
            "SELECT u.userid, u.username, u.first_name, u.last_name, u.rank, u.permissions, u.point, u.is_login, u.status, pref.time_zone FROM ?? AS u INNER JOIN ?? AS pref ON u.userid = pref.userid WHERE u.userid = ?",
            [tableName, innerJoin, verifiedToken.sub]
        );
        await db.commit();
        return res.status(200).json(resUsers);
    } catch (error) {
        const errorMsg = errorDefiner(error);
        await db.rollback();
        return res.status(error.status || 500).json({ message: errorMsg });
    }
}