import ProfileForm from "./profile-form";
import classes from "./user-profile.module.css";
import { useSession, getSession } from "next-auth/react";
import { useEffect, useState } from "react";
function UserProfile() {
// Redirect away if NOT auth
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
getSession().then((session) => {
if (!session) {
window.location.href = "/auth";
} else {
setIsLoading(false);
}
});
}, []);
if (isLoading) {
return <p className={classes.profile}>Loading...</p>;
}
return (
<section className={classes.profile}>
<h1>Your User Profile</h1>
<ProfileForm />
</section>
);
}
export default UserProfile;
useSession
: 즉시 세션과 상태를 가져오고 세션데이터가 가져와지면 세션과 상태 전부 변경할 수 있다. 로그아웃해서 세션이 없다면 세션은 변경되지 않는다.getSession
: 새 요청을 보내서 최근 세션 데이터를 가져온다. 세션을 가져오는 동안 상태를 관리하며 적절하게 표현할 수 있다.export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res);
console.log("session=>", session);
if (!session) {
return {
redirect: {
destination: "/auth",
permanent: false, // 이 리디렉션이 영구적으로 적용되는 건지 임시로 리디렉션되는 건지 알려준다. => 이 리디렉션은 로그인 되지 않았을 때만 작용하는 임식 리디렉션임
},
};
}
return {
props: { session },
};
}
export default ProfilePage;
[next-auth][error][JWT_SESSION_ERROR] > https://next-auth.js.org/errors#jwt_session_error decryption operation failed {
message: 'decryption operation failed',
stack: 'JWEDecryptionFailed: decryption operation failed\n'
openssl rand -base64 32
NEXTAUTH_SECRET='openssl rand -base64 32 결과값'
NextAuth({
secret: process.env.NEXTAUTH_SECRET,
// ...
});
import UserProfile from "../components/profile/user-profile";
import { getServerSession } from "next-auth/next";
function ProfilePage() {
return <UserProfile />;
}
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res);
console.log("session=>", session);
if (!session) {
return {
redirect: {
destination: "/auth",
permanent: false,
},
};
}
if (session && session.user) {
session.user.name = session.user.name || null;
session.user.image = session.user.image || null;
}
return {
props: { session },
};
}
export default ProfilePage;
session.user.name
,session.user.image
가 undefined를 포함해 JSON으로 직렬화하는데 오류가 발생하게 된다. 따라서 이를 방지하기 위해getServerSideProps
내에서 두 값이 undefined이면 null로 변환하는 작업을 진행했다.
import { useState, useRef } from "react";
import { signIn } from "next-auth/react";
import { useRouter } from "next/router"; // useRouter를 이용하여 redirect
import classes from "./auth-form.module.css";
async function createUser(email, password) {
const response = await fetch("/api/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || "Somethine went wrong!");
}
return data;
}
function AuthForm() {
const [isLogin, setIsLogin] = useState(true);
const emailInputRef = useRef();
const passwordInputRef = useRef();
const router = useRouter(); // useRouter를 이용하여 redirect
function switchAuthModeHandler() {
setIsLogin((prevState) => !prevState);
}
async function submitHandler(event) {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
if (isLogin) {
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log(result);
// useRouter를 이용하여 redirect
if (!result.error) {
router.replace("/profile");
}
} else {
try {
const result = await createUser(enteredEmail, enteredPassword);
console.log(result);
} catch (err) {
console.log(err);
}
}
}
return {
/*...*/
};
}
export default AuthForm;
import AuthForm from "../components/auth/auth-form";
import { getSession } from "next-auth/react";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
function AuthPage() {
const [isLoading, setIsLoading] = useState(true);
const router = useRouter();
useEffect(() => {
getSession().then((session) => {
if (session) {
router.replace("/");
} else {
setIsLoading(false);
}
});
}, [router]);
if (isLoading) {
return <p>Loading...</p>;
}
return <AuthForm />;
}
export default AuthPage;
/profile에 접근할 때 getServerSideProps
를 이용해 세션을 확인하므로 인증된 사용자인지 아닌지를 판별할 수 있다. 따라서 /components/main-navigation.js에서 useSession
에서 세션을 확인할 필요가 없다.
/pages/_app.js에서 다음과 같이 작성한다.(이미 작성했지만 강의 순서 상 현재 작성)
🔗 참고
import Layout from "../components/layout/layout";
import "../styles/globals.css";
import { SessionProvider } from "next-auth/react";
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Layout>
<Component {...pageProps} />
</Layout>
</SessionProvider>
);
}
export default MyApp;
SessionProvider
에서 session 프로퍼티에 이미 가지고 있을 수 있는 세션을 전달한다.getStaticProps, getServerSideProps
가 결정하는 프로퍼티)도 가지고 온다.getServerSideProps
를 통한 props.session은 유효성 검사까지 프로퍼티이므로 pageProps에 속해질 것이다...! 대부분의 페이지에서는 session 프로퍼티가 없으므로 값이 정의되지 않을 테지만 /profile의 경우 getServerSideProps
에서 props.session으로 정의되었다.import { getServerSession } from "next-auth/next";
export default async function handler(req, res) {
if (req.method !== "PATCH") {
return;
}
// 인증된 사용자인지 확인
const session = await getServerSession(req);
if (!session) {
res.status(401).json({ message: "Not authenticated" });
return;
}
}
🔗 API Route에서 getServerSession
import { hashPassword, verifyPassword } from "@/lib/auth";
import { connectToDatabase } from "@/lib/db";
import { getServerSession } from "next-auth/next";
export default async function handler(req, res) {
if (req.method !== "PATCH") {
return;
}
// 인증된 사용자인지 확인
const session = await getServerSession(req, res);
if (!session) {
res.status(401).json({ message: "Not authenticated" });
return;
}
const oldPassword = req.body.oldPassword;
const newPassword = req.body.newPassword;
const userEmail = session.user.email;
const client = await connectToDatabase();
const usersCollection = client.db().collection("users");
const user = await usersCollection.findOne({ email: userEmail });
if (!user) {
res.status(404).json({ message: "사용자를 찾을 수 없습니다." });
client.close();
return;
}
const currentPassword = user.password;
const passwordAreEqual = await verifyPassword(oldPassword, currentPassword);
if (!passwordAreEqual) {
res.status(403).json({ message: "유효하지 않은 비밀번호입니다." });
client.close();
return;
}
const hashedPassword = await hashPassword(newPassword);
const result = await usersCollection.updateOne(
{ email: userEmail },
{ $set: { password: hashedPassword } }
);
// error handling
client.close();
res.status(200).json({ message: "성공적으로 비밀번호를 변경하였습니다." });
}
import { useRef } from "react";
import classes from "./profile-form.module.css";
function ProfileForm({ onChangePassword }) {
const newPasswordRef = useRef();
const oldPasswordRef = useRef();
async function submitChangePassword(event) {
event.preventDefault();
const enteredOldPassword = oldPasswordRef.current.value;
const enteredNewPassword = newPasswordRef.current.value;
// optional : validation..
onChangePassword({
oldPassword: enteredOldPassword,
newPassword: enteredNewPassword,
});
}
return (
<form className={classes.form} onSubmit={submitChangePassword}>
<div className={classes.control}>
<label htmlFor="new-password">New Password</label>
<input type="password" id="new-password" ref={newPasswordRef} />
</div>
<div className={classes.control}>
<label htmlFor="old-password">Old Password</label>
<input type="password" id="old-password" ref={oldPasswordRef} />
</div>
<div className={classes.action}>
<button>Change Password</button>
</div>
</form>
);
}
export default ProfileForm;
import ProfileForm from "./profile-form";
import classes from "./user-profile.module.css";
// import { useSession, getSession } from "next-auth/react";
// import { useEffect, useState } from "react";
function UserProfile() {
// Redirect away if NOT auth
// const [isLoading, setIsLoading] = useState(true);
// useEffect(() => {
// getSession().then((session) => {
// if (!session) {
// window.location.href = "/auth";
// } else {
// setIsLoading(false);
// }
// });
// }, []);
// if (isLoading) {
// return <p className={classes.profile}>Loading...</p>;
// }
async function changePasswordHandler(passwordData) {
const response = await fetch("/api/user/change-password", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(passwordData),
});
const data = await response.json();
console.log(data);
}
return (
<section className={classes.profile}>
<h1>Your User Profile</h1>
<ProfileForm onChangePassword={changePasswordHandler} />
</section>
);
}
export default UserProfile;
이전 비밀번호를 제대로 입력한 경우(성공 케이스)
바꾸기 이전
바꾸기 이후