이번 시간에는 로그인 후 지속적으로 로그인을 유지할 수 있도록
프론트와 백에 코드를 추가하여 마무리할 예정입니다.
로그인을 유지할 수 있는 라우트는 user로 정해서 진행했습니다.
const express = require('express');
const router = express.Router();
const { User } = require('../models');
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
// 로그인 사용자 정보 가져오기 (계속 로그인 상태를 만들기 위한)
// GET /user
// isLoggedIn 발견되면 인증 체크 후 다음 미들웨어를 실행시킨다.
router.get('/', isLoggedIn, async (req, res, next) => {
try {
// 로그인 인증이 되었다면, req.user에서 유저 정보 확인 가능
if (req.user) {
// 로그인 인증된 유저의 id 가져오기
const user = await User.findOne({
where: { id: req.user.id }
});
// 그 유저의 정보 중 비밀번호를 제외한 정보 가져오기
const fullUserWithoutPassword = await User.findOne({
where: { id: user.id },
attributes: {
exclude: ['password'], // exclude: 제외한 나머지 정보 가져오기
},
});
res.status(200).json(fullUserWithoutPassword);
} else {
res.status(200).json(null);
}
} catch(error) {
console.error(error);
next(error);
}
});
isLoggedIn / isNotLoggedIn 뭐지?
로그인을 했는지 체크해주는 미들웨어다.
그 안에는 아래처럼 작성하였다.
// 인증이 되어있는가? true
exports.isLoggedIn = (req, res, next) => {
if (req.isAuthenticated()) {
next();
} else {
res.status(401).send('로그인이 필요합니다.');
}
};
// 인증이 되지 않았는가? false
exports.isNotLoggedIn = (req, res, next) => {
if (!req.isAuthenticated()) {
next();
} else {
res.status(401).send('로그아웃 후 접근이 가능합니다.');
}
};
여기서 isAuthenticated()
메소드는 서버에 요청을 보낸 사용자가 인증 유무를 boolean
값으로 알려준다.
로그인과 회원가입은 코드도 중요하지만, 어떻게 미들웨어가 흘러가는지도 파악해야한다.
import { combineReducers } from 'redux';
import { HYDRATE } from 'next-redux-wrapper';
import userSlice from './user';
const rootReducer = (state: any, action: any) => {
switch (action.type) {
case HYDRATE:
console.log('HYDRATE', action);
return action.payload;
default: {
const combineReducer = combineReducers({
user: userSlice.reducer,
});
return combineReducer(state, action);
}
}
};
// 기존 아래 코드를 제거하고, 위 코드를 넣는다.
// const rootReducer = combineReducers({
// user: userSlice.reducer,
// });
export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;
여기서는 HYDRATE
의 액션을 추가하기 위해 위 처럼 switch문을 사용했다.
서버에서 생성된 상태를 클라이언트로 전달해주는 역할을 한다.
콘솔로 찍어보면, 현재 상태에 대한 내용이 찍혀 나온다.
import React from 'react';
import { useSelector } from 'react-redux';
import axios from 'axios';
import { Container, ProfileWrapper } from '../styles/style';
import LoginForm from '../component/loginForm';
import { RootState } from '../slices';
import { loadUser } from 'actions/user';
import wrapper from 'store/configureStore';
import Link from 'next/Link';
const Home = () => {
const isLoggedIn = useSelector((state: RootState) => state.user.isLoggedIn);
const { email, nickname } = useSelector(
(state: RootState) => state.user.user
);
return (
<Container>
{isLoggedIn ? (
<ProfileWrapper>
<div>
<h1>
<strong>{nickname}님</strong> 환영합니다.
</h1>
<p>{email}</p>
</div>
<Link href="/profile">마이페이지</Link>
</ProfileWrapper>
) : (
<LoginForm />
)}
</Container>
);
};
// SSR은 프론트서버에서 진행되기 때문에 브라우저에서는 개입할 수 없다.
// SSR은 프론트서버에서 백엔드서버로 데이터를 요청하고, 받은 후 브라우저로 데이터와 렌더링을 한번에 보낸다.
// 서버에서 진행이 되기 떄문에 쿠키를 넣어서 직접 보내줘야한다.
export const getServerSideProps = wrapper.getServerSideProps(async context => {
// back 서버로 쿠키 전달
// 로그인을 하게되면 context에서 req를 사용 가능
// 프론트서버에서 쿠키가 공유되는걸 방지하기 위해 if문으로 조건 작업 진행
console.log('context', context);
const cookie = context.req ? context.req.headers.cookie : '';
axios.defaults.headers.Cookie = '';
if (context.req && cookie) {
axios.defaults.headers.Cookie = cookie;
}
await context.store.dispatch(loadUser());
});
export default Home;
여기서 context
는 무엇일까? 콘솔로 찍어보면 많은 내용이 찍혀나오지만,
결과로는 개발자 도구에서 확인할 수 있는 headers
에 대한 내용이 있다.
또한 getServerSideProps
는 맨 하단에 있는 export default
위에 위치하고 있어야한다.
기존에는 dispatch(loadUser())
는 컴포넌트 내부에서 진행을 했었지만,
SSR을 하기 위해서는 컴포넌트 내부가 아닌 getServerSideProps
에서 호출을 해야한다.
SSR은 브라우저 개입을 못하기 때문에 먼저 실행하여 유저가 로그인을 했는지 파악한 뒤
인증서를 쿠키에 담아지게 되기 때문에 로그인을 한 상태를 새로고침, 페이지 이동을 해도 유지할 수 있다.
이렇게 React
, Next.js
, Node.js
, MySQL
을 사용하여 로그인 및 회원가입을
구현해보았다. 양심상 타입스크립크는 아닌 것 같아서 안했다고 말을 하자...
다음 시간에는 로컬 회원가입이 아닌 SNS로 회원가입에 대해서 정리를 하도록 하겠다.
잘못된 설명이 있다면 피드백 부탁드릴게요 😔