Use of JWT in httpOnly cookie #4632

@Stephan_Du_Toit yeah, sure. When it comes to the implementation, then I have done everything as described in the tutorial. The only difference is that I went with cookieSetter and cookieGetter as part of Strapi’s middleware because I think it’s the most elegant solution.

The most crucial thing is to set origin URLs correctly. You have to declare the full path (e.g. “https://my-strapi.com” and “https://my-frontend.com”) to your backend and your frontend.

This is my middleware.js file:

module.exports = ({ env }) => ({
  load: {
    before: [
      "cookieGetter",
      "responseTime",
      "logger",
      "cors",
      "responses",
      "gzip",
    ],
    order: [
      "Define the middlewares' load order by putting their name in this array is the right order",
    ],
    after: ["parser", "router", "cookieSetter"],
  },
  settings: {
    cors: {
      origin: [
        env("CLIENT_URL", "http://localhost:8080"),
        env("API_URL", "http://localhost:1337"),
      ],
    },
    cookieGetter: {
      enabled: true,
    },
    cookieSetter: {
      enabled: true,
    },
  },
});

Implementation details of both cookie functions are below. IMPORTANT: inside cookie setter you need to declare a domain property (process.env.CLIENT_HOSTNAME), which value is the hostname of your frontend, e.g. “my-frontend.com”.

// cookieSetter
module.exports = (strapi) => {
  return {
    initialize() {
      strapi.app.use(async (ctx, next) => {
        await next();
        if (
          ctx.request.url.startsWith("/auth/") &&
          ctx.response.status === 200
        ) {
          const { jwt: jwtToken } = ctx.response.body;
          ctx.cookies.set("token", jwtToken, {
            httpOnly: true,
            secure: process.env.NODE_ENV === "production",
            maxAge: 1000 * 60 * 60 * 24 * 14, // 14 Day Age
            domain: process.env.CLIENT_HOSTNAME,
            sameSite: process.env.NODE_ENV === "development" ? true : "none",
            overwrite: true,
          });
        }
      });
    },
  };
};

// cookieGetter
module.exports = (strapi) => {
  return {
    initialize() {
      strapi.app.use(async (ctx, next) => {
        if (
          ctx.request &&
          ctx.request.header &&
          !ctx.request.header.authorization
        ) {
          const token = ctx.cookies.get("token");
          if (token) {
            ctx.request.header.authorization = `Bearer ${token}`;
          }
        }
        await next();
      });
    },
  };
};

With this code I have a fully functional authorization process, which works like a charm.

3 Likes