본문 바로가기

클라이언트/Next.js
[Next.js] Next 14에서 NextAuth 사용하여 소셜 로그인 구현하기 (구글 로그인)

// NextAuth

NextAuth는 Next 앱의 오픈소스 인증 솔루션이다.

OAuth 인증, 비밀번호 기반 인증, 세션 관리 등이 필요한 상황에서 주로 사용한다.

간편한 설정으로 인증을 관리할 수 있다는 장점이 있으며, 필요에 따라 커스터마이징도 가능하다.

* 공식 사이트: https://next-auth.js.org/

 

NextAuth.js

Authentication for Next.js

next-auth.js.org


NextAuth는 App Routes 와 Route Handlers 에서 사용법이 다르기 때문에 14 버전은 Route Handlers 가이드를 보아야 한다.


▪ NextAuth 설치

npm install next-auth

▪ route.ts 파일 생성

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

 

import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

const handler = NextAuth();

export { handler as GET, handler as POST };

const authOptions: any ={
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_OAUTH_ID || '',
      clientSecret: process.env.GOOGLE_OAUTH_SECRET || '',
    }),
  ],  
}
const handler = NextAuth(authOptions);

export { authOptions, handler as GET, handler as POST };

Google, KaKao 등 다양한 Provider를 추가할 수 있다.

이때 clientId와 clientSecret은 외부에 공개되면 안 되기 때문에 환경변수로 사용한다.


▪ clientId, clientSecret 생성

https://console.cloud.google.com/

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com


Google 클라우드 플랫폼에서 clientId와 clientSecret을 발급 받아야 한다.

GoogleCloud 로고 옆 셀렉트 박스를 누르면 새 프로젝트를 생성할 수 있는 모달이 뜬다.

나는 이미 생성한 프로젝트가 있는 상태라서 기존 게 뜬다.

프로젝트 이름을 자유롭게 설정하고 만들어준다.

프로젝트를 생성하면 생성한 프로젝트에서 작업할 수 있다.

API 및 서비스를 눌러준다.

API의 OAuth 동의 화면을 클릭하여 consent screen을 설정해주어야 한다.

외부에서 구글 로그인을 사용할 것이기 떄문에 External을 체크한 후 만들어 준다.

 

앱 이름, 사용자 지원 이메일, 개발자 이메일만 필수로 입력한다.

앱 도메인이나 승인된 도메인은 배포 후 해당 도메인을 나중에 추가해줘도 된다.

그 다음은 그냥 쭉쭉 넘겨준다.

이번에는 사용자 인증 정보를 만들어야 한다.

OAuth 클라이언트 ID를 클릭한다.

승인된 JavaScript 원본은 우선 localhost로 등록해놓고 배포 후 수정해주야 한다.

그러면 클라이언트ID, 클라이언트 보안 비밀번호를 얻을 수 있다.

저 두 가지를 .env 파일에 입력해주면 된다.


▪ SessionProvider 컴포넌트 생성

app/context/AuthContext.tsx
'use client';

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

interface AuthProps {
  children: React.ReactNode;
}
export default function AuthContext({ children }: AuthProps) {
  return <SessionProvider>{children}</SessionProvider>;
}

▪ app/layout.ts 파일 설정

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} mx-auto max-w-screen-md text-lg`}>
        <AuthContext>
          <Header />
          <main>{children}</main>
        </AuthContext>
      </body>
    </html>
  );
}

Next.js 는 기본으로 서버 컴포넌트인데 next-auth에서 제공해주는 훅을 사용하려면 클라이언트 컴포넌트여야 한다.

그래서 SessionProvider를 클라이언트 컴포넌트로 분리하고, 이 컴포넌트를 layout에서 사용하는 방식으로 구현한다.


▪ 로그인 / 로그아웃

next-auth에서는 useSession 이라는 훅을 제공해주기 때문에 로그인 여부를 쉽게 알 수 있다.

또한 signIn, signOut 이라는 함수를 제공하여 소셜 로그인을 손쉽게 구현할 수 있게 해준다.

export default function Header() {
  const { data: session } = useSession();

  return (
    <header className="sticky top-0 z-10 mb-5 flex items-center justify-between border-b border-solid border-gray-300 bg-white text-3xl">          
      {session ? (
        <ColorButton text="Sign out" onClick={() => signOut()} />
      ) : (
        <ColorButton text="Sign in" onClick={() => signIn()} />
      )}
    </header>
  );
}

 

Sign in 버튼을 누르면

구글로 로그인할 수 있는 버튼이 뜨고, 그 버튼을 누르면

로그인할 수 있다!

로그인하고 나면 Sign out 버튼으로 바뀐다.

그리고 Sign out을 누르면 다시 Sign in으로 변하는 걸 확인할 수 있다


▪ 로그인 UI 커스텀

지금은 기본으로 제공하는 UI를 사용하는데, 구글 로그인만 사용할 게 아니라 다른 로그인 방법도 함께 사용해야 하는 경우가 대부분일 것이다.

그러기 위해서는 로그인 UI를 커스텀해줄 수 있다.

import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

const authOptions: NextAuthOptions = {
  pages: {
    signIn: 'login',
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_OAUTH_ID || '',
      clientSecret: process.env.GOOGLE_OAUTH_SECRET || '',
    }),
  ],
};
const handler = NextAuth(authOptions);

export { authOptions, handler as GET, handler as POST };

providers를 추가해준 handler에 로그인 페이지를 정의해준다.

이름은 꼭 login이 아니라 다른 것으로 설정해도 된다.

그러고나서는 src/app/api/auth/login/page.tsx 로 페이지를 생성해 준다.

만약 login이 아닌 다른 걸로 signIn에 정의했다면  src/app/api/auth /원하는파일명/page.tsx 로 페이지를 생성해주면 된다.

src/app/api/auth /원하는파일명/page.tsx

* 복사용! * 

 

테스트용으로 임시 로그인 페이지를 구현해보았다.

 

이번엔 Sign in 버튼을 누르면 내가 커스텀한 UI가 표시된다!


import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { redirect } from 'next/navigation';
import { getProviders } from 'next-auth/react';
import Login from '@/components/Login';

interface LoginParams {
  searchParams: {
    callbackUrl: string;
  };
}

export default async function LoginPage({
  searchParams: { callbackUrl },
}: LoginParams) {
  const session = await getServerSession(authOptions);

  if (session) {
    redirect('/');
  }

  const providers = (await getProviders()) ?? {};

  return <Login providers={providers} callbackUrl={callbackUrl} />;
}
'use client';

import ColorButton from '@/components/ColorButton';
import { ClientSafeProvider, signIn } from 'next-auth/react';

export interface LoginProps {
  providers: Record<string, ClientSafeProvider> | {};
  callbackUrl: string;
}

export default function Login({ providers, callbackUrl }: LoginProps) {
  return (
    <div className="mt-[30%] flex items-center justify-center">
      {providers &&
        Object.values(providers).map(({ name, id }) => (
          <ColorButton
            key={id}
            text={`Sign in with ${name}`}
            onClick={() => signIn(id, { callbackUrl })}
            size="medium"
          />
        ))}
    </div>
  );
}

▪ 로그인 callback

import NextAuth, { NextAuthOptions } from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

const authOptions: NextAuthOptions = {
  pages: {
    signIn: 'login',
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_OAUTH_ID || '',
      clientSecret: process.env.GOOGLE_OAUTH_SECRET || '',
    }),
  ],
  callbacks: {
    async signIn({ account, user, profile }) {
      //처리
      return true;
    },
  },
};
const handler = NextAuth(authOptions);

export { authOptions, handler as GET, handler as POST };

callbacks의 signIn은 로그인을 성공했을 경우 받는 함수이다.

 로그인 정보를 받아오고 받아온 정보를 서버에 넘기는 등 로그인 후 작업을 처리한다.