Next.js middleware와 getServerSideProps

세션방식의 로그인 인증, middleware와 getServerSideProps를 사용해 구현한 기록

2023-02-22

인증방식에는 대표적으로 두가지가 있다.

제일 많이 사용하는 세션 방식과 토큰 방식


세션방식

세션 방식은 HTTP요청이 노출되어도 세션 자체에는 의미있는 값이 없기 때문에 보안상 유리할 수 있다.

각 사용자마다 고유한 id를 갖고 있기 때문에 매번 회원정보를 확인할 필요가 없다.

서버에 따로 세션 저장소를 두기 때문에 사용자가 많아지면 서버에 부담이 간다.

각 브라우저들은 보안상의 이슈로 서버와 프론트간의 도메인이 같아야 쿠키가 전달된다.


우선 localhost와 서버상의 도메인이 달라 proxy 설정을 해줬다.

// pages/api/[...path].ts
import type { NextApiRequest, NextApiResponse } from "next";
import httpProxyMiddleware from "next-http-proxy-middleware";

export const config = {
  api: {
    externalResolver: true
  },
}

export default (req: NextApiRequest, res: NextApiResponse) => {
    httpProxyMiddleware(req, res, {
        target: process.env.NEXT_PUBLIC_API_URL,
        changeOrigin: true,
        pathRewrite: [
          {
            patternStr: "^/api/",
            replaceStr: "/",
          },
        ],
        cookieDomainRewrite: {
          "*": "localhost",
        },
      })
    };
/** @type {import('next').NextConfig} */
const nextConfig = {
  swcMinify: true,
  reactStrictMode: true,
  compiler: {
    styledComponents: true,
    removeConsole: process.env.NODE_ENV === "production",
  },
  async rewrites() {
    return process.env.NODE_ENV === "production"
      ? [
          {
            source: "/api/:path*",
            destination: `${process.env.NEXT_PUBLIC_API_URL}/:path*`,
          },
        ]
      : [];
  },
{...}
};

위 처럼 설정해 주니 서버에서 보낸 cookie가 localhost 브라우저에 잘 들어왔다.

여기서 고민했던 부분이 csr환경이 아닌 next의 ssr환경인 프론트측 서버단에서는 유저인증을 어떻게 확인할 것인가 였다.

우선 로그인 상태와 비로그인 상태에서 접근불가능한 페이지 접근시 리다이렉트를 시키는 방법으로 진행했다.

Nextjs middleware는 root폴더에 middleware.ts 파일을 만들어 주면 페이지 라우팅 되기 전에 middleware가 자동으로 실행된다.

Nextjs에서 지원하는 middleware는 들어오는 요청에 따라서 req, res, header를 다시 작성하거나 리디렉션 등을 수정하거나 재작성할 수 있다.

import type { NextRequest } from "next/server";
import { fetchAuth, inValidateAuth, validateAuth } from "@/lib/validateUser";

const validatedUrl = [
  "/users/sign-in",
  "/users/sign-up/normal",
  "/users/sign-up/social",
  "/users/sign-up/vendor",
  "/users/password/reset",
];

const inValidateUrl = ["/users/my-page"];

export async function middleware(req: NextRequest) {
  const url = req.nextUrl;

  if (validatedUrl.includes(url.pathname)) {
    return await validateAuth(req);
  }

  if (inValidateUrl.includes(url.pathname)) {
    return await inValidateAuth(req);
  }

  return await fetchAuth(req);
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico|assets).*)"],
};
import { NextRequest, NextResponse } from "next/server";

export const validateUser = (req: NextRequest) => {
  return fetch("url", {
    method: "get",
    mode: "cors",
    credentials: "same-origin",
    headers: {
      "Content-Type": "application/json",
      cookie: `${req.headers.get("cookie")}`,
    },
  });
};

{...}

export const fetchAuth = async (req: NextRequest) => {
    try {
        const res = NextResponse.next();

        const response = await validateUser(req);
        const data = await response.json();

        if (response.ok) {
            res.cookies.set("user", JSON.stringify(data));
            return res;
        }

        if (!response.ok) {
            res.cookies.delete("user");
            res.cookies.delete("user.sid");
            return res;
        }
    } catch (e) {
        throw new Error("Validate Error");
    }
};

처음에 이런식으로 미들웨어를 구현했다. matcher로 페이지단만 확인했고, 페이지 라우팅시 유저 인증에 성공하면 cookie를 통해 유저정보와 세션을 전달했다.

또 인증성공시 접근 x, 비인증시 접근 x 한 url을 확인해 리다이렉트 시키도록 했다.

처음 해봤던 작업이었고, 제대로 구현한지 확신이 들지 않아 코드리뷰를 요청했다.

middleware를 사용하지 않고 hocs를 사용해 auth를 호출하고, axios config에 headers를 전달하는 방법으로 알려주셨다.

import React from "react";
import { GetServerSideProps, GetServerSidePropsContext } from "next";
import { getAuth } from "@/models/auth";
import merge from "lodash/merge";

const validatedUrl = [
  "/users/sign-in",
  "/users/sign-up/normal",
  "/users/sign-up/social",
  "/users/sign-up/vendor",
  "/users/password/reset",
];

const inValidateUrl = [
  "/users/my-page",
  "/users/my-page/profile",
  "/users/my-page/review",
];

export default function withAuth(getServerSideProps?: GetServerSideProps) {
  return async function (context: GetServerSidePropsContext) {
    const url = context.req.url as string;
    let user = null;
    try {
      const res = await getAuth({ headers: context.req.headers });
      user = res.data;
    } catch (e) {}
    if (inValidateUrl.includes(url) && !user) {
      return {
        redirect: {
          permanent: false,
          destination: "/users/sign-in",
        },
      };
    }
    {...}
    const ret = await getServerSideProps?.(context);
    return merge(ret, { props: { user } });
  };
}

middleware는 nextjs에서 서버도 같이 구성해 사용하면 좋을거라고 조언을 주셨다.

// page/*
{...}

export const getServerSideProps = withAuth();

{...}