๐ฉ๐ปโ๐ป ๋ก์ง
๋ก๊ทธ์ธ -> ํ๋ก ํธ์๋ ์๋ฒ -> ๋ฐฑ์๋ ์๋ฒ ์์ฒญ -> ํ๋ก ํธ์๋ ์๋ฒ๋ก ์ฟ ํค๋ด์ 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์ด ๋ค ํฌํจ๋๋ ๊ฒ์ด๋ค.

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์ ๋ถ๋ฌ์ฌ ์ ์๋๋ก ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํ ์์ ์ด๋ค.
'Next.js (app router + 14v)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
- ๐ฉ๐ปโ๐ป ๋ก์ง
- 1. auth.js ์ค์น
- 2. auth.ts ํ์ผ์ app ํด๋ ์ต์๋จ์ ๋ง๋ ๋ค.
- 3. middleware๋ฅผ ์ ์ฉํ๋ค.
- 4. API ๋ผ์ฐํธ๋ฅผ ๋ง๋ค์ด์ค๋ค.
- 5. SessioProvider๋ก ๊ฐ์ธ์ useSession ์ฌ์ฉํ๊ธฐ
- 6. ๋ฐฑ์๋ ์๋ฒ์์ jwt ๋ฐ์์ค๊ธฐ
- 7. ๋ก๊ทธ์ธ ์ปดํฌ๋ํธ
- 8. ์ฟ ํค ์ฌ์ฉ