Nextjs 13버전으로 api를 이용해서
로그인/로그아웃 로직을 구현하면서 middleware도 같이 사용하게 되었다.
구현하면서 테이블 설계한 내용들과 배운 내용들을 기록해놓고자 한다.
두가지 조건을 기준으로 user 테이블 스키마를 정의하면서
2개의 테이블로 분리해서 데이터를 저장하기로 했다.
users
passwords
유저의 데이터는 비밀번호데이터 보다 비교적 자주 확인해야 하는 데이터이다.
Auth처리와 보안처리는 디비에서 부터 나누는것이 낫다 는 피드백을 받고 테이블을 분리하였다.
관련 게시물
- https://www.quora.com/Should-we-keep-the-user-name-and-password-in-the-same-table-where-the-other-personal-information-is
- https://www.danilothedev.com/blog/keeping-user-password-in-a-separate-sql-table
# gpt
일반적으로 특히 보안상의 이유로 사용자 이름과 비밀번호를 다른 개인 정보와 별도의 테이블에 보관하는 것이 좋습니다. 이는 보안 위반이 발생하는 경우 공격자가 사용자 이름과 비밀번호는 물론 기타 민감한 정보에 액세스하지 못하도록 하는 데 도움이 됩니다. 또한 보안을 더욱 강화하기 위해 비밀번호를 일반 텍스트가 아닌 해시 형식으로 저장하는 것이 모범 사례입니다.
필수사항은 아닌거 같으나 실수로 노출될 가능성을 최대한 줄이기 위해 보안상의 이슈로 테이블을 분리하는 경우인듯 하다.
로그인 성공
- accessToken, refreshToken cookie 세팅
- accessToken 만료 3시간
미들웨어 (로그인 관련페이지)
- accessToken 없고 refreshToken 있을 때
- refresh 토큰 해독해서 user정보 확인
- user 정보 없으면 -> 로그인페이지
- uesr 정보 있으면 -> accessToken 새로 발급
- 토큰이 아예 존재하지 않는경우 접속하지 못하는페이지에 접속하는 경우에는 리다이렉트
1차 로직
초기에는 헤더에 velog와 같이
header
에 로그인 여부에 따라 분기처리를 해주려고logged-in
값을 쿠키에 넣어 사용하려고 하였다.cookie 세팅해준 뒤 성공 후처리 로직으로
router.refresh
추가해서 사용했다.
rotuer refresh
새로고침된 라우트의 데이터를 서버에서 다시 가져오는것
쿠키값을 사용하고 있으니 logged-in 데이터를 다시 불러와서 헤더를 업데이트 했다.
하지만 디자인이 변경되면서 헤더에 값을 유지할 필요가 없어서 logged-in
값은 제거 !
미들웨어는 API 요청 이전에 우선으로 동작하여, 로직이 처리되기 이전에
요청과 응답을 수정해서 사용할 수 있다.
주로 인증처리에 사용되고 유저가 로그인여부에 따라 접근할 수 있는 페이지 분기처리할 때 사용한다!
(물론 필요에 의해 다른것들도 핸들링 할 수 있다)
미들웨어의 실행 순서
1. headers from next.config.js 2. redirects from next.config.js 3. Middleware (rewrites, redirects, etc.) 4. beforeFiles (rewrites) from next.config.js 5. Filesystem routes (public/, _next/static/, pages/, app/, etc.) 6. afterFiles (rewrites) from next.config.js 7. Dynamic Routes (/blog/[slug]) 8. fallback (rewrites) from next.config.js
파일 마지막에 아래와 같이 matcher
부분을 커스텀하여,
어느 라우터를 대상으로 동작할지 정해주면 된다.
export const config = {
matcher: ['/my/:path*', '/login', '/signup'],
};
// 잘못 된 경로로 접근했을 때
return NextResponse.redirect(new URL(`/login`, req.url));
// 응답에 cookie setting
response.cookies.set(access_cookieOptions);
• 리다이렉트는 사용자가 접근하려는 페이지를 다른 페이지로 리다이렉트하는 것이다.
• 리라이트는 사용자가 접근하려는 페이지의 경로를 유지한 채 다른 페이지로 리다이렉트하는 것이다.
• 쿠키를 설정할 때는 response 객체의 set_cookie 메서드를 사용하여 쿠키를 설정할 수 있다.
이를 이용하여 유저의 토큰을 확인해 원하는대로 로직을 작성해주면 된다!
useQuery
! 새로 알게된 사실
이전 버전에는 string 값으로 queryKey 정의가 가능했지만,
쿼리키를 이제 무조건 배열로 받는 사실도 새로 알게 됐다.
토큰 해독을 jwtwebtoken 라이브러리를 사용중이였는데, 미들웨어를 적용시키면서 아래 에러와 마주했다.
미들웨어 터미널 에러
JWT verification error: The edge runtime does not support Node.js 'crypto' module.
Learn More: https://nextjs.org/docs/messages/node-module-in-edge-runtime
미들웨어 또는 Edge API 경로 의 코드는 Node.js 런타임의 기능을 사용하고 있으나, Edge 런타임은 Node.js API 및 전역 항목을 지원하지 않습니다.
라고 공식문서에서 알려주고 있고,
해결방법으로는 폴리필을 사용하거나 web crypto API로 바꾸는 방법이 있다고 안내가 되어있다.
그 외 방법으로는 jose
라이브러리로 대체하여 암호화하는 방법이 있는데
vercel의 예제코드에서는 jose를 적용해놓고 있어 똑같이 사용하였다.
예제코드 : https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md
https://github.com/vercel/examples/blob/main/edge-middleware/jwt-authentication/lib/auth.ts
import { ACCESS_TOKEN_SECRET, REFRESH_TOKEN_SECRET } from '@/constants';
import { SignJWT, jwtVerify } from 'jose';
export const signJWT = async (payload: object) => {
const iat = Math.floor(Date.now() / 1000);
const accessExp = iat + 60 * 60 * 3;
const refreshExp = iat + 60 * 60 * 24 * 14;
const accessToken = await new SignJWT({ ...payload })
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
.setExpirationTime(accessExp)
.setIssuedAt(iat)
.setNotBefore(iat)
.sign(new TextEncoder().encode(ACCESS_TOKEN_SECRET));
const refreshToken = await new SignJWT({ ...payload })
.setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
.setExpirationTime(refreshExp)
.setIssuedAt(iat)
.setNotBefore(iat)
.sign(new TextEncoder().encode(REFRESH_TOKEN_SECRET));
return { accessToken, refreshToken };
};
빌드에러로 발생해서 공식문서에서 찾아보니까 ,
여러가지의 에러 상황을 전달해주는데 vercel에서 발생한 에러코드와 비교해 보았을 때
내 에러는 사용할 수 없는 props 데이터를 쓰고 있는것
이였다.
프롭 값의 가용성 확인: 프롭(props) 값이 예상대로 존재하는 것을 가정하는 코드를 확인하세요. 프롭 값이 없을 수도 있는 경우에 대비하여 코드를 수정하거나 기본 값을 설정하세요. 이렇게 하면 "undefined"가 아닌 "null" 값을 사용하여 직렬화할 수 있습니다.
실제로 에러가 발생하는 코드는 fetch type error 였고,
api 도메인이 수기로 작성되어있는 부분을 수정해서 에러 해결완료 !
회원가입/로그인/로그아웃 기능을 구현하면서 오랜만에 토큰관련된 로직을 다시 되짚어보면서,
토큰으로 유저인지 확인하는부분을 다시 짚어볼 수 있었다..
금방할 줄 알았는데 여러 상황들에서 분기처리도 추가해줬어야해서 생각보다 오래걸렸지만
오랜만에 기본기 짚어가면서 공부한거같아서 좋았다 !
누군가에게 이 글이 도움이 되길 바라며 .. 😌