"next": "^14.0.3", //13 사용하다 14로 업데이트됨. next-auth와 무관
"next-auth": "^4.23.1",
"jose": "^4.15.4",
"typescript": "5.1.6",
...
{
"compilerOptions": {
"target": "es2017",
...
}
import { auth } from 'lib/auth'; // 단순히 파일 분리
const handler = auth;
export { handler as GET, handler as POST };
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;
}
}
}
//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=
: 아래처럼 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 }
);
}
}