TIL

2024.04.15 TIL #Next.js #middleware #multiple_middleware #chain #๋ผ์šฐํŠธ๋ณดํ˜ธ #์ธ๊ฐ€

inz1234 2024. 5. 7. 22:57

๐Ÿ“Œ Task TODOLIST

- [x] middleware ๋ฅผ ํ†ตํ•œ ๋ผ์šฐํŠธ ๋ณดํ˜ธ

- [x] ์—ฌ๋Ÿฌ ๋ผ์šฐํŠธ ๋ณดํ˜ธ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— chain ๋งŒ๋“ค๊ธฐ


โœจ ๊ฐœ๋ฐœ ๋‚ด์šฉ

Why(์ด์œ ) ?

์ปดํฌ๋„ŒํŠธ ๋‹จ์—์„œ ๋ผ์šฐํŠธ ๋ณดํ˜ธ๋ฅผ ํ•  ์ง€(ex. (auth) / (nonAuth) ๋กœ ํด๋”๋ฅผ ๋‚˜๋ˆ„์–ด์„œ),

middleware์—์„œ ๋ผ์šฐํŠธ ๋ณดํ˜ธ๋ฅผ ํ•  ์ง€ ํŒ€์›๋“ค์ด๋ž‘ ์ƒ์˜๋ฅผ ํ–ˆ๋Š”๋ฐ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๋‹น์ฒจ๋๋‹ค.

์ด์œ ๋Š”

1. ์ผ๋‹จ ๊ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ์จ์•ผ ํ•  ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ๊ฐ€ ์ค„์–ด๋“ ๋‹ค!

๊ถŒํ•œ์ด ์—†์œผ๋ฉด router.push๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค๋“ ์ง€, 

alert("๋กœ๊ทธ์ธ ํ›„ ์ด์šฉ๊ฐ€๋Šฅ ํ•ฉ๋‹ˆ๋‹ค") ๋“ฑ์˜ alert๋ฅผ ๋งค ์ปดํฌ๋„ŒํŠธ๋“ค์—์„œ ๋งˆ๋‹ค ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•˜๋Š” ๊ฒƒ์„

๋ฏธ๋“ค์›จ์–ด์—์„œ ์•„์˜ˆ ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

2. ์ด๋ฒˆ ๊ธฐํšŒ๋กœ middleware๋ฅผ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.

 

How(๊ณผ์ •)  && what(๊ฒฐ๊ณผ) 

  (1) ์–ด๋–ค ์กฐ๊ฑด์œผ๋กœ ๊ฐ ๋ผ์šฐํŠธ๋ฅผ ๋ณดํ˜ธํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค.

  ์šฐ๋ฆฌ ํŒ€์€ ์ด 5๊ฐœ์˜ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

  <์„ธ์…˜ ์—…๋ฐ์ดํŠธ / ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ / ๋‚ด๊ฐ€ ์ฐธ์—ฌ์ค‘์ธ ๋ฏธํŒ…๋ฐฉ๋งŒ / ๋‚ด๊ฐ€ ์ฐธ์—ฌ์ค‘์ธ ์ฑ„ํŒ…๋ฐฉ๋งŒ / ํ•™๊ต์ธ์ฆ ์—ฌ๋ถ€>

  ์— ๋”ฐ๋ผ์„œ ๋ผ์šฐํŠธ ๋ณดํ˜ธ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

// ์„ธ์…˜ ์—…๋ฐ์ดํŠธ
export const updateSessionMiddleware = (middleware: CustomMiddleware) => {
  return async (request: NextRequest, event: NextFetchEvent) => {
    await updateSession(request);
    return middleware(request, event, NextResponse.next());
  };
};

// ๋กœ๊ทธ์ธ ์—ฌ๋ถ€
export const routeHandlerMiddleware = (middleware: CustomMiddleware) => {
  return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
    const supabase = serverSupabase();
    const { data } = await supabase.auth.getUser();

    if (data.user) {
      // ๋กœ๊ทธ์ธ ํ•œ ์œ ์ €
      if (request.nextUrl.pathname.startsWith('/login')) {
        return NextResponse.redirect(new URL('/', request.nextUrl.origin));
      } else if (request.nextUrl.pathname.startsWith('/join')) {
        return NextResponse.redirect(new URL('/', request.nextUrl.origin));
      }
    } else {
      // ๋กœ๊ทธ์ธ ์•ˆํ•œ ์œ ์ €
      if (request.nextUrl.pathname.startsWith('/mypage')) {
        // ๋งˆ์ดํŽ˜์ด์ง€ ์ ‘๊ทผ ๋ฐฉ์ง€ + ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ redirect
        return NextResponse.redirect(new URL('/login', request.url));
      } else if (request.nextUrl.pathname.startsWith('/meetingRoom') || request.nextUrl.pathname.startsWith('/chat/')) {
        // ๋กœ๋น„๋กœ ์ ‘๊ทผ ๋ถˆ๊ฐ€
        return NextResponse.redirect(new URL('/login', request.url));
      }
    }
    return middleware(request, event, response);
  };
};

export const chatRoomHandler = (middleware: CustomMiddleware) => {
  return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
    // ์ฑ„ํŒ…์ฐฝ
    if (request.nextUrl.pathname.startsWith('/chat/')) {
      const supabase = serverSupabase();
      const {
        data: { user }
      } = await supabase.auth.getUser();
      // ๋‚ด๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋Š” ๋ฐฉ๋“ค
      const myChatRooms = [];
      const { data: myRooms } = await supabase
        .from('participants')
        .select('room_id')
        .eq('user_id', String(user?.id))
        .eq('isDeleted', false);

      if (myRooms) {
        for (let room of myRooms) {
          const { data: myChatRoomId } = await supabase
            .from('chatting_room')
            .select('chatting_room_id')
            .eq('room_id', room.room_id)
            .eq('isActive', true);
          if (myChatRoomId && myChatRoomId.length) {
            myChatRooms.push(myChatRoomId[0].chatting_room_id);
          }
        }
        if (myChatRooms.includes(request.nextUrl.pathname.replace('/chat/', ''))) {
          // ๋‚ด๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋Š” ๋ฐฉ์ด๋ฉด OK
          return NextResponse.next();
        } else {
          // ๋‚ด๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋Š” ๋ฐฉ์ด ์•„๋‹ˆ๋ฉด ๋กœ๋น„๋กœ
          return NextResponse.redirect(new URL('/meetingRoom', request.nextUrl.origin));
        }
      }
    }

    return middleware(request, event, NextResponse.next());
  };
};

export const meetingRoomHandler = (middleware: CustomMiddleware) => {
  return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
    // ์ˆ˜๋ฝ์ฐฝ
    if (request.nextUrl.pathname.startsWith('/meetingRoom/')) {
      const supabase = serverSupabase();
      const {
        data: { user }
      } = await supabase.auth.getUser();
      // ๋‚ด๊ฐ€ ๋“ค์–ด๊ฐ€์žˆ๋Š” ๋ฐฉ๋“ค
      const { data: myRooms } = await supabase
        .from('participants')
        .select('room_id')
        .eq('user_id', String(user?.id))
        .eq('isDeleted', false);
      if (
        myRooms &&
        myRooms.map((room) => room.room_id).includes(request.nextUrl.pathname.replace('/meetingRoom/', ''))
      ) {
        return NextResponse.next();
      } else {
        return NextResponse.redirect(new URL('/', request.nextUrl.origin));
      }
    }

    return middleware(request, event, NextResponse.next());
  };
};

// ํ•™๊ต์ธ์ฆ์—ฌ๋ถ€
export const schoolValidateMiddleware = (middleware: CustomMiddleware) => {
  return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
    if (request.nextUrl.pathname.startsWith('/meetingRoom')) {
      const supabase = serverSupabase();
      const { data } = await supabase.auth.getUser();
      const { data: isValidate } = await supabase
        .from('users')
        .select('isValidate')
        .eq('user_id', data.user?.id as string);

      if (isValidate && !isValidate[0].isValidate) {
        return NextResponse.redirect(new URL('/mypage', request.url));
      }
    }
    return middleware(request, event, NextResponse.next());
  };
};

 

(2) ๋ฏธ๋“ค์›จ์–ด ์ข…๋ฅ˜๊ฐ€ 5๊ฐœ ์ด๋‹ˆ ํ•˜๋‚˜์˜ middleware.ts ํŒŒ์ผ์— ์šฐ๊ฒจ๋„ฃ๊ธฐ ๋ณด๋‹ค๋Š” multiple middleware๋ฅผ chain์„ ํ†ตํ•ด ์žฌ๊ท€ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด๋ณธ๋‹ค.

// chain.ts
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';
import { CustomMiddleware } from './middlewareType';

type MiddlewareFactory = (middleware: CustomMiddleware) => CustomMiddleware;

export const chain = (functions: MiddlewareFactory[], index = 0): CustomMiddleware => {
  const current = functions[index];

  if (current) {
    const next = chain(functions, index + 1);
    return current(next);
    // current(chain(functions, 1)) => current(current(chain(functions,2))) => current(current(chain(functions,3)))
    // ์ด๋Ÿฐ ์‹์œผ๋กœ chain์ด ํ˜•์„ฑ๋˜๊ณ , ์žฌ๊ท€ํ•จ์ˆ˜ ์‹คํ–‰
  }

  // ๋งˆ์ง€๋ง‰ ์ˆœ์„œ์ผ ๋•Œ
  return async (request: NextRequest, event: NextFetchEvent, response: NextResponse) => {
    return response;
  };
};

 

(3) middleware.ts์—์„œ ํ•˜๋‚˜์˜ chain์œผ๋กœ ์—ฎ์–ด์ค€๋‹ค.

// src > middleware.ts
const middlewareList = [
  updateSessionMiddleware,
  routeHandlerMiddleware,
  schoolValidateMiddleware,
  meetingRoomHandler,
  chatRoomHandler
];

export default chain(middlewareList);

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)']
};

 

๐Ÿšจ  TroubleShotting  && ๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท

์›๋ž˜ ํ•™๊ต์ธ์ฆ์—ฌ๋ถ€ middleware๋Š” ํ•™๊ต์ธ์ฆ์—ฌ๋ถ€๋ฅผ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด์— header์— boolean ๊ฐ’์„ ์‹ฌ๊ณ , ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด์—์„œ ์‹ฌ์–ด์ง„ ํ—ค๋”์—์„œ get ํ•ด์„œ ๋ผ์šฐํŠธ ๋ณดํ˜ธ๋ฅผ ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

 

๊ทผ๋ฐ ๋ฐ›๋Š” ๋ฏธ๋“ค์›จ์–ด์—์„œ get์ด ์•ˆ ๋˜๋Š” ๊ฒƒ์ด๋‹ค..!!

๊ทผ๋ฐ ๋ฒ„๊ทธ์ธ ๊ฑฐ ๊ฐ™๋‹ค..

์™œ ๋ฒ„๊ทธ๋ผ ํ•˜๋ƒ๋ฉด, chain์œผ๋กœ ์—ฎ์ง€ ์•Š๊ณ  ์ผ๋ฐ˜ middleware๋กœ ํ•˜๋ฉด header๊ฐ€ ์ž˜ get ๋œ๋‹ค.
ํ•˜์ง€๋งŒ chain๋งŒ ์—ฎ์œผ๋ฉด header๊ฐ€ ์—†์–ด์ง€๋Š”...Bug....

์ฐพ์•„๋ณด๋‹ˆ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๋„ middleware ์ฒด์ธ์—์„œ header์— ์‹ฌ์€ ๊ฒƒ์„ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด์—์„œ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ธ€๋“ค์ด ๋งŽ์•˜๋‹ค.

https://github.com/traefik/traefik/issues/5890

 

v2: chain middleware removes headers added by headers middleware · Issue #5890 · traefik/traefik

Do you want to request a feature or report a bug? Bug What did you do? Expect to see custom headers when the headers middleware is used. What did you expect to see? custom headers What did you see ...

github.com

=> ํ•˜์—ฌ header์— ์‹ฌ๋Š” ๋ฐฉ๋ฒ•์€ pass!

๋ฐ”๋กœ ๋ฆฌ๋””๋ ‰์…˜์„ ์‹œ์ผœ์ค˜๋ฒ„๋ ธ๋‹ค.

๐Ÿ“š ๋ ˆํผ๋Ÿฐ์Šค

https://www.youtube.com/watch?v=fmFYH_Xu3d0&list=PL1lHTvxfWQGoJ6IornrxP0rwUV8m_iluA&index=2

https://medium.com/@aididalam/approach-to-multiple-middleware-and-auth-guard-in-next-js-app-routing-bbb641401477

 

Approach to Multiple Middleware and Auth Guard in Next.js App Routing

Hey there! Ready to level up your Next.js website? ๐Ÿš€ Let’s explore two powerful tools: Middleware and Authentication Guards.

medium.com

https://github.com/Team-MeetGo/MeetGO/pull/86

 

ํ™ฉ์ธ์ • - [feat]: ๋ฏธ๋“ค์›จ์–ด(์ฑ„ํŒ…์ฐฝ ์ ‘๊ทผ๊ถŒํ•œ ์„ค์ •) by 2njeong · Pull Request #86 · Team-MeetGo/MeetGO

๐Ÿ“Œ ๊ด€๋ จ ์ด์Šˆ #69 Task TODOLIST ์ฑ„ํŒ…์ฐฝ์—์„œ ๋’ค๋กœ๊ฐ€๊ธฐ๋กœ ์ˆ˜๋ฝ์ฐฝ์œผ๋กœ ๋ชป ๋„˜์–ด๊ฐ€๊ฒŒ ํ•˜๊ธฐ ์ง์ ‘ ํƒ€์ดํ•‘ ํ•ด์„œ ์ฑ„ํŒ…์ฐฝ์œผ๋กœ ๋ชป๋“ค์–ด๊ฐ€๊ฒŒ ํ•˜๊ธฐ ์ˆ˜๋ฝ์ฐฝ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—์„œ ๋’ค๋กœ๊ฐ€๊ธฐ๋กœ ์ฑ„ํŒ…์ฐฝ์œผ๋กœ ๋ชป ๋“ค

github.com

https://github.com/Team-MeetGo/MeetGO/pull/127

 

ํ™ฉ์ธ์ • - [fix]: ๋ฏธ๋“ค์›จ์–ด(์ฑ„ํŒ…์ฐฝ) by 2njeong · Pull Request #127 · Team-MeetGo/MeetGO

๐Ÿ“Œ ๊ด€๋ จ ์ด์Šˆ #69 Task TODOLIST ์ฃผ์†Œ์ฐฝ์— ์ง์ ‘ ์ณ์„œ ์ฑ„ํŒ…์ฐฝ์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์„ ๋ง‰์œผ๋ ค ์ฝ”๋“œ๋ฅผ ์งฐ์—ˆ๋Š”๋ฐ, ๊ทธ ์ฝ”๋“œ ๋•Œ๋ฌธ์— ์ƒˆ๋กœ๊ณ ์นจ์„ ํ–ˆ์„ ๋•Œ๋„ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ ์‹œํ‚ค๋Š” ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒ

github.com