NextAuth 란?

김재한·2024년 2월 16일
4
post-thumbnail

서론

최근들어 NextAuth 의 사용성이 높아지고 Oauth 인증 관련 활용할 수 있는 기능들이 많아 학습해 보고자 한다.

학습한 내용을 바탕으로 ID & PW 로그인 , 카카오 로그인, 네이버 로그인 을 적용할 예정이며

그 과정을 기록해 두려고 한다. 누군가에게 조금이나마 도움이 됐으면 좋겠다. 😄

NextAuth 란? 😮

next.js 로 개발된 페이지에서 로그인을 쉽게 구현할 수 있도록 여러 기능을 제공하는 라이브러리 이다.
특히, Oauth Provider 를 제공해 주어 Oauth 인증 방식의 로그인 서비스를 쉽게 구현 할 수 있어 핵심 로직에 집중할 수 있다.

토큰 저장 방법

로그인에 있어 JWT Access Token 을 안전하게 보관하는 것이 굉장히 중요하다.

일반적으로 localstorageCookie에 저장하게 되는데 각각의 장단점이 존재한다.

☝🏻 localstorage

로컬스토리지에 저장하면 매우 간편하지만 XSS 공격에 취약하다.

로컬 스토리지는 자바스크립트로 접근이 가능하므로 사용자의 브라우저가 악의적인 코드에 노출되게 된다면 해커에게 토큰이 탈취 당할 위험이 있다. 😷

👿 XSS(Cross Site Scripting attack) 👿

해커가 사용자의 브라우저 환경에서 악의적인 자바스크립트 코드를 실행하는 공격이다.

먼저, 쿠키에 http-onlySecure 설정을 하면 자바스크립트로 쿠키에 접근할 수 없기 때문에 XXS 공격으로부터 안전하다.

하지만, 4KB 용량 제한이 있기 때문에 큰 토큰의 경우 저장이 불가능하고 CSRF 공격에 취약할 수 있다.

그러나 next-auth는 쿠키에 토큰을 저장할 때 기본적으로 Same-site 속성을 Lax로 저장한다. 이 속성을 사용하면 브라우저가 cross-site에 HTTP 요청을 보낼 때 쿠키에 해당정보를 담지 않겠다는 뜻이므로 토큰이 유출되지 않는다. 🧚🏻

😈 CSRF 공격 😈

CSRF 공격은 사용자의 의도와 무관하게 특정 HTTP 요청을 하게 만드는 공격 방법이다.
토큰은 HTTP 요청마다 RequestHeader 에 담겨 보내지기 때문에 갈취당할 위험이 있다.
만약 서버에서 쿠키에 담긴 토큰 정보를 이용해 사용자 권한을 검증한다면 문제가 발생한다.

💡 그렇다면 어떻게 클라이언트에서 쿠키에 저장된 토큰에 접근할까?

http-only 설정을 하게되면 자바스크립트로 쿠키에 접근하지 못해 XXS 로부터 안전할 수 있으나 사용자도 접근이 불가능하다.

next-auth 에서는 jwt, session 콜백 함수 를 이용해 토큰 정보 일부를 노출시킬 수 있고, useSession 과 같은 훅을 통해 이를 참조할 수 있다.

NextAuth 사용법 📝

API 라우트 구성하기

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 }

Oauth Provider 구성하기

NextAuth는 Naver, Kakao, ID & PW 등 로그인 방식에 따라 provider 를 제공한다.

개발자들은 이를 활용해 로그인 기능을 쉽게 구현할 수 있어 핵심 로직에 집중할 수 있다.

SNS(Naver & Kakao) Provider

☝🏻 Naver

// /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 }

✌🏻 Kakao


// /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의 ClientIdClientSecret 은 개발자 센터에 앱을 등록하고
할당받은 ID와 Secret을 넣으면 된다.

카카오의 경우 RestAPI Key를 넣고
로그인 > 보안 탭에서 Client Secret를 생성해 넣어주면 된다.

🚨 주의!!

ClientIdClientSecret을 환경변수로 관리할 때 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

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 값을 반환한다.

Callbacks

  • jwt
    jwt 콜백에서의 tokenNext.js 의 JWT 토큰이며 userauthroize()의 반환 값이다.

    return 값은 token에 저장되므로 jwt() 에서는 token에 사용자 정보를 추가해주는 역할을 한다.

  • session
    session 콜백에서는 토큰 정보를 Session 에 넣어주는 역할을 한다.

실행 순서 ✏️

  1. 사용자 로그인
    로그인하기 버튼을 클릭하면 ID & PW 값을 인자로 넣어 next-auth의 signIn 함수를 호출한다.

  2. 인증확인
    CredentialsProvider 에서 authorize()credentials를 인자로 받아 백엔드와 통신한다.

    인증에 성공할 경우 authorize()사용자 정보를 을 반환하고 callbakcs가 실행된다.

  3. JWT 생성
    authorize() 의 반환값들이 jwt() 콜백에 전달된다.

    이때, 반환된 토큰이 jwt 콜백 내에서 token 객체에 전달 되고 이를 session 콜백에 전달한다.

  4. 세션 생성
    session 콜백은 이 토큰을 세션 객체에 할당하고 클라이언트에게 반환한다.

  5. 클라이언트에서 사용
    클라이언트에서는 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)
보통은 커스텀 로그인 페이지를 따로 만들어 사용해 labelplaceholder 속성은 필요로하지 않는다.

환경변수 설정하기

소셜 로그인에서 사용되는 Client IDSecret 값 외에 두가지가 더 있다.

NEXTAUTH_SECRET 은 토큰정보를 암호화 & 복호화 하기 위해 필요하며 문자열을 입력해주면 되고 ( ex. secretstring )

NEXTAUTH_URL은 보안성의 이유로 로그인 기능이 포함된 도메인 주소를 작성해주면 된다. (ex. http://localhost:3000 )

로그인 구현(1) - 프로젝트 생성 및 Prisma 설정 ➡️

참고
NextAuth 공식문서
환경변수 설정
NextAuth callbacks
@dosomething
@zooyaho
@cloud_oort

0개의 댓글