최근들어 NextAuth 의 사용성이 높아지고 Oauth 인증 관련 활용할 수 있는 기능들이 많아 학습해 보고자 한다.
학습한 내용을 바탕으로 ID & PW 로그인 , 카카오 로그인, 네이버 로그인 을 적용할 예정이며
그 과정을 기록해 두려고 한다. 누군가에게 조금이나마 도움이 됐으면 좋겠다. 😄
next.js 로 개발된 페이지에서 로그인을 쉽게 구현할 수 있도록 여러 기능을 제공하는 라이브러리 이다.
특히, Oauth Provider 를 제공해 주어 Oauth 인증 방식의 로그인 서비스를 쉽게 구현 할 수 있어 핵심 로직에 집중할 수 있다.
로그인에 있어 JWT Access Token 을 안전하게 보관하는 것이 굉장히 중요하다.
일반적으로 localstorage 와 Cookie에 저장하게 되는데 각각의 장단점이 존재한다.
로컬스토리지에 저장하면 매우 간편하지만 XSS 공격에 취약하다.
로컬 스토리지는 자바스크립트로 접근이 가능하므로 사용자의 브라우저가 악의적인 코드에 노출되게 된다면 해커에게 토큰이 탈취 당할 위험이 있다. 😷
👿 XSS(Cross Site Scripting attack) 👿
해커가 사용자의 브라우저 환경에서 악의적인 자바스크립트 코드를 실행하는 공격이다.
먼저, 쿠키에 http-only 와 Secure 설정을 하면 자바스크립트로 쿠키에 접근할 수 없기 때문에 XXS 공격으로부터 안전하다.
하지만, 4KB 용량 제한이 있기 때문에 큰 토큰의 경우 저장이 불가능하고 CSRF 공격에 취약할 수 있다.
그러나 next-auth는 쿠키에 토큰을 저장할 때 기본적으로 Same-site 속성을 Lax로 저장한다. 이 속성을 사용하면 브라우저가 cross-site에 HTTP 요청을 보낼 때 쿠키에 해당정보를 담지 않겠다는 뜻이므로 토큰이 유출되지 않는다. 🧚🏻
😈 CSRF 공격 😈
CSRF 공격은 사용자의 의도와 무관하게 특정 HTTP 요청을 하게 만드는 공격 방법이다.
토큰은 HTTP 요청마다RequestHeader에 담겨 보내지기 때문에 갈취당할 위험이 있다.
만약 서버에서 쿠키에 담긴 토큰 정보를 이용해 사용자 권한을 검증한다면 문제가 발생한다.
http-only 설정을 하게되면 자바스크립트로 쿠키에 접근하지 못해 XXS 로부터 안전할 수 있으나 사용자도 접근이 불가능하다.
next-auth 에서는 jwt, session 콜백 함수 를 이용해 토큰 정보 일부를 노출시킬 수 있고, useSession 과 같은 훅을 통해 이를 참조할 수 있다.
API 라우트는 서버사이드에서 동작하는 코드로, NextAuth가 Next.js의 동적 API 라우트를 이용하기 때문에 설정해 주어야 한다.
반드시 /app/api/auth/[...nextauth] 경로에 route.ts 파일을 만들어 주어야 한다.
// /app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
const handler = NextAuth({
providers: [ ... ],
callbacks:{...},
pages:{...}
})
export { handler as GET, handler as POST }
NextAuth는 Naver, Kakao, ID & PW 등 로그인 방식에 따라 provider 를 제공한다.
개발자들은 이를 활용해 로그인 기능을 쉽게 구현할 수 있어 핵심 로직에 집중할 수 있다.
// /app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import NaverProvider from "next-auth/providers/naver"
const handler = NextAuth({
providers: [
// 네이버 Provider
NaverProvider({
clientId: process.env.NAVER_CLIENT_ID,
clientSecret: process.env.NAVER_SECRET,
}),
],
callbacks:{...},
pages:{...}
})
export { handler as GET, handler as POST }
// /app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import KakaoProvider from "next-auth/providers/kakao"
const handler = NextAuth({
providers: [
// 카카오 Provider
KakaoProvider({
clientId: process.env.KAKAO_CLIENT_ID,
clientSecret: process.env.KAKAO_SECRET,
})
],
callbacks:{...},
pages:{...}
})
export { handler as GET, handler as POST }
각 Provider의 ClientId 와 ClientSecret 은 개발자 센터에 앱을 등록하고
할당받은 ID와 Secret을 넣으면 된다.
카카오의 경우 RestAPI Key를 넣고
로그인 > 보안 탭에서 Client Secret를 생성해 넣어주면 된다.
🚨 주의!!
ClientId와ClientSecret을 환경변수로 관리할 때NEXT_PUBLIC를 prefix로 붙이면 외부에 노출되기 때문에 붙이면 안된다.

로그인을 하기 위해 개발자 센터에서 Redirect URL을 등록해야 한다.
개발 및 배포 환경에 따라 도메인 주소 뒤에 api/auth/callback/(naver OR kakao) 를 붙여주면 된다.
http://localhost:3000/api/auth/callback/(naver OR kakao) http://www.stg-project.com/api/auth/callback/(naver OR kakao)http://www.project.com/api/auth/callback/(naver OR kakao)SNS 로그인을 위한 세팅은 이게 전부이다.
💡 next-auth 가 지원하는 소셜 로그인
네이버,카카오,애플,구글,페이스북,인스타그램,트위터,깃랩,줌,틱톡,링크드인,디스코드,코인베이스등등
Credential Provider 는 개발자가 직접, 로그인에 필요한 정보들(credentials)을 구성하여 로그인을 할 수 있도록 만들어 주는 provider 이다.
// 로그인 화면에서 로그인 버튼
const onSubmit:FormEventHandler<HTMLFormElement> = async (e) => {
...
try{
const response = await signIn("credentials", {
username: id,
password,
redirect: false,
})
...
}catch (e) {
...
}
};
// /api/auth/[...nextauth]/route.ts
import CredentialsProvider from "next-auth/providers/credentials"
...
providers: [
CredentialsProvider({
name: 'Credentials'
},
async authorize(credentials, req) {
const res = await fetch(`${process.env.NEXTAUTH_URL}/api/signin`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: credentials?.username,
password: credentials?.password,
}),
})
const user = await res.json()
console.log('$$$user: ', user)
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null then an error will be displayed advising the user to check their details.
return null
// You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
}
},
})
],
callbacks:{
async jwt({ token, user }) {
// 리턴되는 값들은 token에 저장된다.
return { ...token, ...user};
},
async session({ session, token }) {
session.user = token as any;
return session;
},
},
...
위의 코드는 next-auth 로그인 구현 실습에서 사용하는 로그인 기능이다.
authroize()는 credentials 값을 통해 사용자의 유효성을 체크해 로그인을 제어할 수 있다.
인증에 성공하면 User 객체를 반환하고 그렇지 않으면 null or false 값을 반환한다.
jwt
jwt 콜백에서의 token은 Next.js 의 JWT 토큰이며 user 는 authroize()의 반환 값이다.
return 값은 token에 저장되므로 jwt() 에서는 token에 사용자 정보를 추가해주는 역할을 한다.
session
session 콜백에서는 토큰 정보를 Session 에 넣어주는 역할을 한다.
사용자 로그인
로그인하기 버튼을 클릭하면 ID & PW 값을 인자로 넣어 next-auth의 signIn 함수를 호출한다.
인증확인
CredentialsProvider 에서 authorize() 가 credentials를 인자로 받아 백엔드와 통신한다.
인증에 성공할 경우 authorize()는 사용자 정보를 을 반환하고 callbakcs가 실행된다.
JWT 생성
authorize() 의 반환값들이 jwt() 콜백에 전달된다.
이때, 반환된 토큰이 jwt 콜백 내에서 token 객체에 전달 되고 이를 session 콜백에 전달한다.
세션 생성
session 콜백은 이 토큰을 세션 객체에 할당하고 클라이언트에게 반환한다.
클라이언트에서 사용
클라이언트에서는 useSession 훅을 통해 세션에 접근할 수 있다.
즉, authorize() 에서 발급된 토큰은 jwt 콜백을 통해 Next.js 서버의 JWT 토큰에 저장되고, 이 정보는 다시 session 콜백을 통해 세션 객체로 변환되어 클라이언트에 전달된다.
💡 User 객체 타입
// type.d.ts export interface DefaultUser { id: string; name?: string | null; email?: string | null; image?: string | null; } export interface User extends DefaultUser { }
id속성은 필수값으로 넣어야 하며 속성을 추가할 수 있다.
💡 next-auth 에서 제공하는 로그인 폼
next-auth 는 기본적인 login 페이지를 지원해준다. (
localhost:3000/api/auth/signin)
보통은 커스텀 로그인 페이지를 따로 만들어 사용해label과placeholder속성은 필요로하지 않는다.
소셜 로그인에서 사용되는 Client ID 와 Secret 값 외에 두가지가 더 있다.
NEXTAUTH_SECRET 은 토큰정보를 암호화 & 복호화 하기 위해 필요하며 문자열을 입력해주면 되고 ( ex. secretstring )
NEXTAUTH_URL은 보안성의 이유로 로그인 기능이 포함된 도메인 주소를 작성해주면 된다. (ex. http://localhost:3000 )
참고
NextAuth 공식문서
환경변수 설정
NextAuth callbacks
@dosomething
@zooyaho
@cloud_oort