diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index f10c0ba4..fef32096 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -1,174 +1,37 @@ 'use client'; -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import toast from 'react-hot-toast'; -import axiosInstance from '@/lib/axios'; -import { - LoginWrapper, - ButtonSignupWrapper, - LogoContainer, -} from '@/app/(auth)/login/Login.styles'; -import Logo from '@/styles/Icon/Logo.svg'; -// import MainLayout from '@/styles/Icon/mainLayout.svg'; // 용량이 너무 크다. . . -import Image from 'next/image'; -import Input from '@common/Input/Input'; -import AutoLoginCheckbox from '@layout/Login/AutoLogin'; -import Button from '@common/Button/Button'; -import SignupTabs from '@layout/Login/SignUpTabs'; -import useUserStore from '@/stores/useUserStore'; -import ValidationMessage from '@/components/Common/ValidationMessage/ValidationMessage'; - -interface LoginError { - email: string; - password: string; -} - -const Login = () => { - const router = useRouter(); - const { setNickname, setEmail } = useUserStore(); - - const [isHiddenPassword, setIsHiddenPassword] = useState(true); - const [emailInput, setEmailInput] = useState(''); - const [password, setPassword] = useState(''); - const [errors, setErrors] = useState({ email: '', password: '' }); - const [isLoading, setIsLoading] = useState(false); - const [isFormValid, setIsFormValid] = useState(false); - - const handlePasswordVisibility = () => { - setIsHiddenPassword((prev) => !prev); - }; - - const validateEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - }; - - const validateForm = (): boolean => { - let isValid = true; - const newErrors = { email: '', password: '' }; - - if (emailInput) { - if (!validateEmail(emailInput)) { - newErrors.email = '이메일 형식이 올바르지 않습니다 (예: xxx@xxx.com)'; - isValid = false; - } - } - - if (!emailInput || !password) { - isValid = false; - } - - setErrors(newErrors); - setIsFormValid(isValid); - return isValid; - }; - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!validateForm()) { - return; - } - - setIsLoading(true); - try { - const response = await axiosInstance.post('/api/login', { - email: emailInput, - password, - }); - - const nickname = response.data; - - setEmail(emailInput); - setNickname(nickname); - - localStorage.setItem('userId', emailInput); - toast.success(`안녕하세요 ${nickname}님 환영합니다!`); - router.push('/home'); - } catch (error) { - console.error('Login error:', error); - setErrors({ - email: '', - password: '이메일 또는 비밀번호가 올바르지 않습니다.', - }); - setIsFormValid(false); - } finally { - setIsLoading(false); - } - }; - - const handleEmailChange = (value: string) => { - setEmailInput(value); - if (errors.email) { - setErrors({ ...errors, email: '' }); - } - - if (value && !validateEmail(value)) { - setErrors((prev) => ({ - ...prev, - email: '이메일 형식이 올바르지 않습니다 (예: xxx@xxx.com)', - })); - setIsFormValid(false); - } else { - setIsFormValid(!!value && !!password); - } - }; - - const handlePasswordChange = (value: string) => { - setPassword(value); - setErrors({ ...errors, password: '' }); - setIsFormValid(!!value && !!emailInput && validateEmail(emailInput)); +import { signIn, signOut, useSession } from 'next-auth/react'; +import { LoginWrapper } from './Login.styles'; + +const LoginPage = () => { + const { data: session } = useSession(); + console.log(session?.accessToken); + + const handleToken = async () => { + const response = await fetch('/api/auth/token', { + method: 'POST', + headers: { + Authorization: `Bearer ${session?.accessToken}`, // 카카오 토큰 + }, + }); + const { token } = await response.json(); // 백엔드 JWT 받기 + return token; }; return ( - - logo - {/* mainLayout */} - -
- - ) : null - } - /> - - ) : null - } - /> - - - + + ) : ( + + )}
); }; -export default Login; +export default LoginPage; diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..a5aa4be9 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,36 @@ +import NextAuth, { NextAuthOptions } from 'next-auth'; +import KakaoProvider from 'next-auth/providers/kakao'; + +const authOptions: NextAuthOptions = { + providers: [ + KakaoProvider({ + clientId: process.env.KAKAO_CLIENT_ID as string, + clientSecret: process.env.KAKAO_CLIENT_SECRET as string, + }), + ], + session: { + strategy: 'jwt', //JWT 기반 인증 + maxAge: 24 * 60 * 60, // 24시간 + }, + secret: process.env.NEXTAUTH_SECRET, //JWT 암호화 키 설정 + callbacks: { + async signIn({ user }) { + return !!user.email; + }, + async jwt({ token, account }) { + if (account) { + token.accessToken = account.access_token; + token.refreshToken = account.refresh_token; + } + return token; + }, + async session({ session, token }) { + session.accessToken = token.accessToken as string; + return session; + }, + }, +}; + +const handler = NextAuth(authOptions); + +export { authOptions, handler as GET, handler as POST }; diff --git a/app/layout.tsx b/app/layout.tsx index 4391ce63..bc6d607d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -5,6 +5,7 @@ import Container from '@/styles/Container'; import GlobalStyle from '@/styles/GlobalStyle'; import { Toaster } from 'react-hot-toast'; import colors from '@styles/color/palette'; +import SessionWrapper from '@/components/Layout/Login/SessionWrapper'; export { metadata }; export { viewport }; @@ -15,29 +16,31 @@ export default function RootLayout({ children: ReactNode; }>) { return ( - - - - - {children} - + + + + + {children} + - - - + }} + /> + + + + ); } diff --git a/components/Layout/Login/SessionWrapper.tsx b/components/Layout/Login/SessionWrapper.tsx new file mode 100644 index 00000000..99127624 --- /dev/null +++ b/components/Layout/Login/SessionWrapper.tsx @@ -0,0 +1,8 @@ +'use client'; +import { SessionProvider } from 'next-auth/react'; + +const SessionWrapper = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; + +export default SessionWrapper; diff --git a/lib/types/next-auth.d.ts b/lib/types/next-auth.d.ts new file mode 100644 index 00000000..d9c55794 --- /dev/null +++ b/lib/types/next-auth.d.ts @@ -0,0 +1,12 @@ +import 'next-auth'; + +declare module 'next-auth' { + interface Session { + accessToken?: string; + } + + interface JWT { + accessToken?: string; + refreshToken?: string; + } +} diff --git a/package-lock.json b/package-lock.json index 6a726fc6..5123bc92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "exifr": "^7.1.3", "framer-motion": "^11.13.1", "next": "14.2.18", + "next-auth": "^4.24.11", "react": "^18", "react-dom": "^18", "react-hot-toast": "^2.4.1", @@ -1802,7 +1803,6 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "dev": true, "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -3132,6 +3132,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -9904,6 +9913,15 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -10509,6 +10527,56 @@ } } }, + "node_modules/next-auth": { + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", + "license": "ISC", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-auth/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -10648,6 +10716,12 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==", + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10658,6 +10732,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -10791,6 +10874,15 @@ "dev": true, "license": "ISC" }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "license": "MIT", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10827,6 +10919,39 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/openid-client": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", + "license": "MIT", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -11372,6 +11497,34 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/preact": { + "version": "10.25.4", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.4.tgz", + "integrity": "sha512-jLdZDb+Q+odkHJ+MpW/9U5cODzqnB+fy2EiHSZES7ldV5LK7yjlVzTp7R8Xy6W6y75kfK8iWYtFVH7lvjwrCMA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz", + "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==", + "license": "MIT", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11895,7 +12048,6 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true, "license": "MIT" }, "node_modules/regenerator-transform": { diff --git a/package.json b/package.json index fbdbfceb..4744c567 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "exifr": "^7.1.3", "framer-motion": "^11.13.1", "next": "14.2.18", + "next-auth": "^4.24.11", "react": "^18", "react-dom": "^18", "react-hot-toast": "^2.4.1",