# NextAuth Apple, Google provider 사용

홍인열·2023년 11월 27일
0
post-custom-banner

NextAuth Apple, Google provider 사용

*Nextjs App router 사용

"next": "^14.0.3", //13 사용하다 14로 업데이트됨. next-auth와 무관
"next-auth": "^4.23.1",
"jose": "^4.15.4",
"typescript": "5.1.6",
...

tsconfig.json

{
  "compilerOptions": {
    "target": "es2017",
...
}

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

import { auth } from 'lib/auth'; // 단순히 파일 분리

const handler = auth;

export { handler as GET, handler as POST };

lib/auth.ts

import Apple from 'next-auth/providers/apple';
import Google from 'next-auth/providers/google';

import REDIS_CUSTOM from '@/util/redis';
import { createPrivateKey, randomUUID } from 'crypto';
import { AuthOptions } from 'next-auth';
import { SignJWT } from 'jose';
import AxiosServer from '@/axios/axiosServer';
import { flightSlackMessage } from '@/util/flightSlackMessage';
import NextAuth from 'next-auth/next';

const getAppleToken = async () => {
	//토큰 생성시간
  const issuedAt = new Date().getTime() / 1000;

	//expireTime이 만료되면 해당 apple login이 되지 않는다.
	//토큰생성은 배포시점에 next sever가 실행되는 처음 한번만 생성된다.
	//6개월, 이를 초과할 경우도 login이 되지않는다.(원인은 애플정책일것으로 추측)
  const expireTime = issuedAt + 60 * 60 * 24 * 30 * 6 ;

	

  const appleToken = await new SignJWT({})
    .setAudience('https://appleid.apple.com')
    .setIssuer(process.env.NEXT_PUBLIC_APPLE_TEAM_ID)
    .setIssuedAt(issuedAt)
    .setExpirationTime(expireTime)
    .setSubject(process.env.NEXT_PUBLIC_APPLE_CLIENT_ID)
    .setProtectedHeader({
      alg: 'ES256',
      kid: process.env.NEXT_PUBLIC_APPLE_KEY_ID
    })
    .sign(createPrivateKey(key));

	// Token 생성시 슬렉 메시지를 날리도록 구성.(필요시)
  // await flightSlackMessage(
  //   `[-----APPLE_LOGIN_TOKEN_GEN-----] expireTIme: ${expireTime}`
  // );

  return appleToken;
};

export const authOption: AuthOptions = {

  // 애플 로그인 시 cookies option을 적용해주어야함.
  cookies: {
    pkceCodeVerifier: {
      name: 'next-auth.pkce.code_verifier',
      options: {
        httpOnly: true,
        sameSite: 'none',
        path: '/',
        secure: true
      }
    }
  },

  providers: [
    Apple({
      clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID,
      clientSecret: await getAppleToken(), //최상위 await es2017 이상에서 사용가능.
      idToken: true,
      profile(profile, tokens) {
        return {
          id: profile.sub,
          email: profile.email,
          provider: 'APPLE' //필요시
        };
      }
    }),

    Google({
      clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
      clientSecret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET,
      idToken: true,
      profile(profile, tokens) {
        return {
          id: profile.sub,
          email: profile.email,
          provider: 'GOOGLE' //필요시
        };
      }
    })
  ],

  callbacks: {
    async jwt({ token, user }: any) {
		//user : provier return value
      return token;
    },

    async session({ session, token }: any) {
		//token : jwt return value
      return session;
    }
  },

	//필요시 설정
  //error 미설정시, nextAuth login 중 error 발생시 nextAuth 기본 제공 페이지로 이동
  pages: {
    signIn: '/',
    error: '/close'
  },

  secret: process.env.NEXTAUTH_SECRET
};

export const auth = async (req: any, res: any) => {
	// authOption을 따로 만들지 않으면 getAppleToken 생성시 es2017 불필요. (async/await 사용)
	// 굳이 authOption을 객체로 따로 둔 이유는 아래에 설명
  return await NextAuth(req, res, authOption);
};

declare module 'next-auth/jwt' {
  interface JWT {
    /** The user's role. */
    userRole?: 'admin';
  }
}

declare module 'next-auth' {
  interface Session {
    user: {
      id: string;
      email: string | null;
      name: string | null;
      isSession: boolean;
      useSession: boolean;
      memberId: string;
      sid: string | null;
      image: string | null;
      error: string | null;
    };
  }
}

declare global {
  namespace NodeJS {
    export interface ProcessEnv {
      NEXT_PUBLIC_GOOGLE_CLIENT_ID: string;
      NEXT_PUBLIC_GOOGLE_CLIENT_SECRET: string;

      //example
      NEXTAUTH_SECRET: string;
      AUTH_APPLE_ID: string;
      AUTH_APPLE_SECRET: string;
      AUTH_GOOGLE_ID: string;
      AUTH_GOOGLE_SECRET: string;
    }
  }
}

env

//apple은 아래 정보를 이용해 token을 생성해줘야함
//애플 개발자 계정
//https://developer.apple.com/account
//식별자 (Certificates, Identifiers & Profiles)

// app 추가 처음 확인 가능
NEXT_PUBLIC_APPLE_PRIVATE_KEY= 

// apple 개발자 계정 로그인시 오른쪽 상단 내이름 밑에서 확인.
NEXT_PUBLIC_APPLE_TEAM_ID= 

// app 추가후
// indentifier / Service IDs / IDENDTIFIER

NEXT_PUBLIC_APPLE_CLIENT_ID= 
// app 추가
// kesy 메뉴에서 확인
NEXT_PUBLIC_APPLE_KEY_ID= 

// google console에서 발급받은것 바로 사용
// https://console.cloud.google.com/
// API 및 서비스 / 사용자 인증정보 / OAuth 2.0 클라이언트 ID
NEXT_PUBLIC_GOOGLE_CLIENT_ID= 
NEXT_PUBLIC_GOOGLE_CLIENT_SECRET= 

authOption을 따로 생성한 이유

: 아래처럼 next server side에서 사용하기위해서

//예

import { authOption } from 'lib/auth';
import { getServerSession } from 'next-auth';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  const session = (await getServerSession(authOption)) //<- 여기 때문에

  
  } catch (error: any) {
    return NextResponse.json(
      {
        ...
      },
      { status: error.response.data.status }
    );
  }
}
profile
함께 일하고싶은 개발자
post-custom-banner

0개의 댓글