Marais.lee
Welcome to Marais's IT Home
Marais.lee
์ „์ฒด ๋ฐฉ๋ฌธ์ž
์˜ค๋Š˜
์–ด์ œ
  • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (87)
    • co-task ํ”„๋กœ์ ํŠธ (7)
    • Study (28)
      • ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ (5)
      • ๋ชจ๋˜ ๋ฆฌ์•กํŠธ Deep Dive (7)
      • ์šฉ์–ด (1)
      • ์ปดํ“จํ„ฐ๊ณผํ•™ (2)
      • ์ฝ”ํ…Œ (12)
      • ๋„คํŠธ์›Œํฌ (0)
    • ๊ฐœ๋ฐœ ํ™˜๊ฒฝ (3)
    • Next.js (pages router) (9)
    • Next.js (app router + 14v) (4)
    • TypeScript (11)
    • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (8)
    • ํ›„๊ธฐ ๋ฐ ๊ณ ๋ฏผ (10)
    • ๋งฅ๋ถ๊ด€๋ จ ์…‹ํŒ… ๋ฐ ์˜ค๋ฅ˜ (4)
    • Obsidian | ์˜ต์‹œ๋””์–ธ (1)
๋ฐ˜์‘ํ˜•

์ธ๊ธฐ ๊ธ€

์ตœ๊ทผ ๊ธ€

๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

  • ํ™ˆ
  • ํƒœ๊ทธ
  • ๋ฐฉ๋ช…๋ก
250x250
hELLO ยท Designed By ์ •์ƒ์šฐ.
Marais.lee

Welcome to Marais's IT Home

JWT ๋‹ค๋ฃจ๊ธฐ with Next.js app router + auth.js
Next.js (app router + 14v)

JWT ๋‹ค๋ฃจ๊ธฐ with Next.js app router + auth.js

2024. 6. 18. 16:23
728x90

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป ๋กœ์ง

๋กœ๊ทธ์ธ -> ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„ -> ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ์š”์ฒญ -> ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„๋กœ ์ฟ ํ‚ค๋‹ด์€ response ๋‚ ๋ ค์คŒ -> ๋ฌธ์ž์—ด๋กœ ๋œ ํ† ํฐ์„ ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์ฟ ํ‚ค ๊ตฝ๊ธฐ -> ๋ธŒ๋ผ์šฐ์ €์— ์ฟ ํ‚ค ์‹ฌ๊ธฐ

 

 

** ํ”„๋ก ํŠธ ์„œ๋ฒ„๋Š” ๊ณต์šฉ์„œ๋ฒ„์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฟ ํ‚ค๋ฅผ ์‹ฌ์œผ๋ฉด ์•ˆ๋œ๋‹ค. ๋ฌด์กฐ๊ฑด ๋ธŒ๋ผ์šฐ์ €์— ์‹ฌ์–ด์•ผํ•œ๋‹ค.
- next-auth๋Š” auth.js๋กœ ์ด๋ฆ„์ด ๋ฐ”๊ผˆ๋‹ค. v.4 ๋ฒ„์ „

- Auth.js๊ฐ€ ํ•ด์ฃผ๋Š” ์ผ

  • ์ฟ ํ‚ค ๋กœ๊ทธ์ธ์˜ ๊ฐ€์žฅ ํฐ ๋ณด์•ˆ ์œ„ํ˜‘์ธ CSRF๋ฅผ ์•Œ์•„์„œ ๋ง‰์•„์ค€๋‹ค.

1. auth.js ์„ค์น˜

yarn add next-auth@beta @auth-core

  • ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€๋ฅผ ๋“ค์–ด๊ฐ€๋ณด๋ฉด ์•„์ง์€ v.5์˜ beta๋ฒ„์ „์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๊ฒŒ Next.js์˜ app router์™€ ๋งž๋‹ค.

 

2. auth.ts ํŒŒ์ผ์„ app ํด๋” ์ตœ์ƒ๋‹จ์— ๋งŒ๋“ ๋‹ค.

  • ์ด๊ณณ์—์„œ providers๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ, ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๋กœ์ง์„ ์งœ๊ฑฐ๋‚˜ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
/src/auth.ts

import NextAuth from "next-auth"

// ์—ฌ๊ธฐ handlers๋ฅผ ๋‚˜์ค‘์— apiํด๋” ์•ˆ์—์„œ ์‚ฌ์šฉํ•  ์˜ˆ์ •
export const { handlers: { GET, POST }, signIn, signOut, auth } = NextAuth({ 
providers: \[\],  
})

 

3. middleware๋ฅผ ์ ์šฉํ•œ๋‹ค.

  • Next.js์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์œผ๋กœ ๋ฐ˜๋“œ์‹œ ํŒŒ์ผ์ด๋ฆ„์ด middleware.ts ์—ฌ์•ผํ•œ๋‹ค.
  • ๋กœ๊ทธ์ธ์„ ํ•ด์•ผ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ํŽ˜์ด์ง€ (ex) mypage)๋“ฑ์„ ์„ค์ •ํ•ด์„œ ๋ฏธ๋กœ๊ทธ์ธ์‹œ ํ•ด๋‹น ํŽ˜์ด์ง€๋กœ ์ ‘๊ทผํ•  ๊ฒฝ์šฐ redirectํ•œ ๊ฒฝ๋กœ๋กœ ๋ณด๋‚ด๋ฒ„๋ฆฐ๋‹ค. ์˜ˆ์ „์—” ํŽ˜์ด์ง€๋งˆ๋‹ค ์ผ์ผ์ด ์„ค์ •ํ•ด์ค˜์•ผํ–ˆ๋‹ค๋ฉด, ๊ทธ๋Ÿฐ ์ˆ˜๊ณ ๋ฅผ ๋œ์–ด์ค€๋‹ค. ํ”„๋ ˆ์ž„ ์›Œํฌ ์ตœ๊ณ ..
/src/middleware.ts  

import { NextResponse } from 'next/server';

import { auth } from './auth';

export async function middleware() {  
const session = await auth();  
if (!session) {  
return NextResponse.redirect('[http://localhost:3000/login'](http://localhost:3000/login'));  
}  
}

// ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•  ๋ผ์šฐํ„ฐ -> ๋กœ๊ทธ์ธ์„ ํ•ด์•ผ๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๊ณณ  
export const config = {  
matcher: \['/mypage'\],  
};

 

4. API ๋ผ์šฐํŠธ๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค.

  • ์ด๊ฒƒ๋„ ๊ฒฝ๋กœ๊ฐ€ ์ค‘์š”ํ•˜๋‹ค. Next.js๋Š” ํด๋”๋กœ ๋ผ์šฐํŒ…๋˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋”, ํŒŒ์ผ ์ด๋ฆ„์ด ์ค‘์š”ํ•˜๋‹ค.
  • ์—ฌ๊ธฐ์„œ [...nextauth]๋Š” Catch-all ๋ผ์šฐํŠธ์ธ๋ฐ ์•„๋ž˜์ฒ˜๋Ÿผ ์“ธ ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰ ํ•ด๋‹น ์œ„์น˜์˜ ๋ชจ๋“  url์ด ๋‹ค ํฌํ•จ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

Next.js ๊ณต์‹๋ฌธ์„œ

src/app/api/auth/[...nextauth]/route.ts

export { GET, POST } from '@/auth'; 
  • ์œ„ ๊ฒฝ๋กœ๋กœ api๋ฅผ ๋งŒ๋“ค๋ฉด ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„์—์„œ ๋งŒ๋“ค์–ด์ง„ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์†Œ "http://localhost:3000/api~"๊ฐ€ ์ˆจ๊ฒจ์ ธ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.
  • ํ•˜์ง€๋งŒ auth.js๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฑฐ๊ธฐ์„œ ์ œ๊ณตํ•˜๋Š” handler๋ฅผ ์“ฐ๋ฉด ๋œ๋‹ค.

** Next.js๋„ ์„œ๋ฒ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ๋“ฑ์€ ์ถฉ๋ถ„ํžˆ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ๋„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทœ๋ชจ๊ฐ€ ์ปค์งˆ์ˆ˜๋ก ํ”„๋ก ํŠธ, ๋ฐฑ ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์œ ๋ฆฌํ•˜๋‹ค.

Why? ๋งŒ์ผ ํ”„๋ก ํŠธ์— ์š”์ฒญ์ด ๋งŽ์ด์˜ค๊ณ  ๋ฐฑ์—๋Š” ์š”์ฒญ์ด ์ ๊ฒŒ ์˜ฌ๊ฒฝ์šฐ ํ”„๋ก ํŠธ๋งŒ ์„œ๋ฒ„๋ฅผ ๋Š˜๋ ค์ฃผ๋ฉด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ํ•œ ์„œ๋ฒ„๋ฅผ ๊ฐ™์ด ์ด์šฉํ•  ๊ฒฝ์šฐ์—๋Š” ๊ทธ ํ•˜๋‚˜๋งŒ ๋Š˜๋ฆด ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋น„ํšจ์œจ์ ์ด๋‹ค.

 

5. SessioProvider๋กœ ๊ฐ์‹ธ์„œ useSession ์‚ฌ์šฉํ•˜๊ธฐ

[์ฐธ๊ณ  ]https://authjs.dev/getting-started/session-management/get-session

 

Auth.js | Get Session

Authentication for the Web

authjs.dev

 

/src/app/AuthSession.tsx

'use client';

import { SessionProvider } from 'next-auth/react';

type Props = {
  children: React.ReactNode;
};

export default function AuthSession({ children }: Props) {
  return <SessionProvider>{children}</SessionProvider>;
}
/app/layout.tsx

<AuthSession>
    <main className="h-screen w-full">{children}</main>
</AuthSession>

session ์ •๋ณด ๊ฐ€์ ธ์˜ฌ ๋•Œ,

a. ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์šฉ

'use client'  

import { useSession } from 'next-auth/react';  
import { useRouter } from 'next/navigation';

(...)  
const router = useRouter()  
const {data: session} = useSession();  
if (session?.user) {  
Router.replace('/mypage');  
return null;  
}  
(...)

b. ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์šฉ

import {auth} from "@/auth";

export default async function Home() {
    const session = await auth();
    if(session?.user) {
        redirect('/mypage')
        return null;
    }
    return(
        <Main/>
    )
}

 

6. ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์—์„œ jwt ๋ฐ›์•„์˜ค๊ธฐ

2๋ฒˆ์—์„œ ๋งŒ๋“ค์–ด๋†“์€ auth.ts์—์„œ ๋กœ๊ทธ์ธ ๋กœ์ง์„ ์ง ๋‹ค. ๋‚ด ๊ฒฝ์šฐ์—๋Š” ๋กœ๊ทธ์ธ ์‹œ ๋ฐฑ์—”๋“œ์—์„œ response๋กœ jwt๋ฅผ ๋‚ ๋ ค์ฃผ๊ณ  ๊ทธ๊ฑธ๊ฐ€์ง€๊ณ  ๋‹ค์‹œ ์œ ์ €์ •๋ณด ํ™•์ธ API์— ์š”์ฒญ์„ ๋‚ ๋ ค์„œ ๊ทธ ์œ ์ €์˜ ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๋Š” ํ˜•์‹์ด๋‹ค.

 

์ฆ‰, ๋ฐฑ์—”๋“œ api๋กœ ๋กœ๊ทธ์ธ ์š”์ฒญ -> ์‘๋‹ต๊ฐ’์œผ๋กœ ๋ฐ›๋Š” jwt๋ฅผ cookies์— ์ €์žฅ -> jwt์˜ access_token์„ ๊ฐ€์ง€๊ณ  ํšŒ์›์ •๋ณด ํ˜ธ์ถœ -> zustand์— ํšŒ์›์ •๋ณด ์ €์žฅ -> ์ €์žฅํ•œ cookies๋ฅผ ์ ‘๊ทผ ๊ถŒํ•œ์ด ํ•„์š”ํ•œ API์— ํ—ค๋”๋กœ ๋„ฃ์–ด์„œ ์‚ฌ์šฉ

 

์—ฌ๊ธฐ์„œ ์ฃผ์˜ํ•  ์ ์€ return ๊ฐ’์— ๋„ฃ๋Š” ํ‚ค ๊ฐ’์—๋Š” Auth.js์—์„œ ์ง€์ •ํ•œ email, name, image ๋งŒ ๋“ค์–ด๊ฐ„๋‹ค. 

/src/auth.ts

import { fetcher } from '@/lib/fetcher';
import { cookies } from 'next/headers';
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';

import { SignInResponse, UserResponse } from './services/auth/type';

export const {
  handlers: { GET, POST },
  auth,
  signIn,
} = NextAuth({
  pages: {
      // Auth.js์—์„œ ์ง€์ •ํ•ด ๋†“์€ ๊ฒฝ๋กœ๋ฅผ ๋‚ด๊ฐ€ ๋งŒ๋“ค์–ด ๋†“์€ endpoint๋กœ ๋Œ€์ฒด
    signIn: '/login',
    newUser: '/register',
  },
  callbacks: {
      // ๋กœ๊ทธ์ธ์‹œ ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ๋ถ€๋ถ„ (ํ„ฐ๋ฏธ๋„์—์„œ ํ™•์ธ ๊ฐ€๋Šฅ)
    jwt({ token }) {
      console.log('auth.ts jwt', token);
      return token;
    },
    session({ session, newSession, user }) {
      console.log('auth.ts session', session, newSession, user);
      return session;
    },
  },
  providers: [
    CredentialsProvider({
      async authorize(credentials) {
        // ๋กœ๊ทธ์ธ ํ˜ธ์ถœ
        const authResponse = await fetcher.post<SignInResponse>('/api/v1/auth/login', {
          // next-auth์—์„œ ๊ณ ์ •๋œ ์ด๋ฆ„ credentials.~
          email: credentials.username,
          password: credentials.password,
        });

        const setCookie = authResponse['access_token'];
        if (setCookie) {
          // ๋ธŒ๋ผ์šฐ์ €์— ์ฟ ํ‚ค๋ฅผ ์‹ฌ์–ด์ฃผ๊ธฐ
          cookies().set('access_token', setCookie, {
            httpOnly: true,
          });
        }
        if (!authResponse) {
          return null;
        }

        const myInfo = await fetcher.get<UserResponse>('/api/v1/user/me', {
          headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${setCookie}`,
          },
        });
        const { user } = myInfo;

        return {
          // next-auth์—์„œ๋Š” ์•„๋ž˜์˜ ์„ธ๊ฐœ ์ด๋ฆ„๋งŒ ์ง€์›
          email: user.email,
          name: user.nickname,
          image: user.profile_image,
          ...user,
        };
      },
    }),
  ],
});

 

7. ๋กœ๊ทธ์ธ ์ปดํฌ๋„ŒํŠธ

credentials์— username, password๋งŒ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์žˆ์–ด์„œ ๊ทธ ์ด๋ฆ„์— ๋งž์ถฐ์„œ Submit์„ ํ•ด์ค˜์•ผํ•œ๋‹ค. 

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ผ ๊ฒฝ์šฐ 'next-auth/react'์—์„œ ๋ถˆ๋Ÿฌ์™€์•ผํ•˜๊ณ  ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์ผ ๊ฒฝ์šฐ ์œ„์— ๋งŒ๋“ค์–ด๋†“์€ 'auth.ts'์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋ฉด๋œ๋‹ค. 

 // login/page.tsx
 (...)
 import { signIn } from 'next-auth/react'; 
 
 const onSubmit = async (data: z.infer<typeof LoginSchema>) => {
    try {
      const response = await signIn('credentials', {
        username: data.email,
        password: data.password,
      });
      if (!response.ok) {
        console.log('๋กœ๊ทธ์ธ ์‹คํŒจ');
      } else {
        console.log('๋กœ๊ทธ์ธ ์„ฑ๊ณต');
      }
      router.replace('/');
    } catch (err) {
      throw new Error(`${err}`);
    }
  };
  
  (...)

 

๋กœ๊ทธ์ธ ํ›„ ์ฐํžˆ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์„œ๋ฒ„ ํ„ฐ๋ฏธ๋„

 



8. ์ฟ ํ‚ค ์‚ฌ์šฉ

/** ์ ‘๊ทผ ๊ถŒํ•œ ํ•„์š” api ์˜ˆ์‹œ */
import { fetcher } from '@/lib/fetcher';
import { cookies } from 'next/headers';

export const Test = async () => {
  const cookieStore = cookies();
  const cookie = cookieStore.get('access_token');
  const data = await fetcher.get('/api/v1/user/me', {
    headers: {
      Authorization: `Bearer ${cookie}`,
    },
  });
  return data;
};

 

 

ํ˜„์žฌ๋Š” access_token์„ ๊ฐ€์ง€๊ณ  ์ ‘๊ทผ ์ œํ•œ์ด ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋„๋กœ๋งŒ ํ•ด๋†“์•˜๋‹ค. ์ถ”ํ›„ refresh_token์„ ๊ฐ€์ง€๊ณ  access_token์ด ๋งŒ๋ฃŒ๋˜์—ˆ์„ ๋•Œ ์ž๋™์œผ๋กœ ์ƒˆ๋กœ์šด access_token์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ด๋‹ค.

 

728x90
๋ฐ˜์‘ํ˜•
์ €์ž‘์žํ‘œ์‹œ ๋น„์˜๋ฆฌ ๋ณ€๊ฒฝ๊ธˆ์ง€

'Next.js (app router + 14v)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Next.js useSelectedLayoutSegment | ํ˜„์žฌ ์žˆ๋Š” ํŽ˜์ด์ง€์˜ ์ฃผ์†Œ๋ฅผ ์•Œ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ  (0) 2024.02.01
useSearchParams | Next.js 13 app router (๊ฐ„๋‹จํ•จ)  (1) 2024.01.28
Next 14 | ์ฃผ์†Œ์ฐฝ์— ๋œจ์ง€ ์•Š๋Š” ํด๋”๊ตฌ์กฐ 3๊ฐ€์ง€  (0) 2023.12.30
    'Next.js (app router + 14v)' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
    • Next.js useSelectedLayoutSegment | ํ˜„์žฌ ์žˆ๋Š” ํŽ˜์ด์ง€์˜ ์ฃผ์†Œ๋ฅผ ์•Œ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ
    • useSearchParams | Next.js 13 app router (๊ฐ„๋‹จํ•จ)
    • Next 14 | ์ฃผ์†Œ์ฐฝ์— ๋œจ์ง€ ์•Š๋Š” ํด๋”๊ตฌ์กฐ 3๊ฐ€์ง€
    Marais.lee
    Marais.lee
    ๊ตฌ๊ธ€๋ง์œผ๋กœ ํ•œ๊ตญ์–ด๋กœ ๋œ ๊ธ€์„ ์ฐพ์ง€ ๋ชปํ–ˆ๊ฑฐ๋‚˜ ์ดํ•ดํ•˜๋Š”๋ฐ ์–ด๋ ค์›€์ด ์žˆ์—ˆ๋˜ ์ด์Šˆ๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

    ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”