-refreshtoken용 테이블을 user 테이블과 합침

-db에서 refreshtoken_hash 를 확인하는걸로 변경
This commit is contained in:
pd0a6847 2025-06-26 16:42:04 +09:00
parent 63e3831951
commit 7fc3434567
6 changed files with 73 additions and 53 deletions

View File

@ -2,6 +2,8 @@ import type { Handle } from '@sveltejs/kit';
import * as auth from '$lib/server/auth';
import { db } from '$lib/server/db';
import * as table from '$lib/server/db/schema';
import {compare, hash} from "bcryptjs";
import {eq} from "drizzle-orm";
const handleAuth: Handle = async ({ event, resolve }) => {
// 1. 먼저 액세스 토큰 확인
@ -18,45 +20,61 @@ const handleAuth: Handle = async ({ event, resolve }) => {
}
// 2. 액세스 토큰이 없거나 유효하지 않을 때 리프레시 토큰 확인
const refreshToken = event.cookies.get(auth.refreshCookieName);
if (!refreshToken) {
const refreshTokenCookie = event.cookies.get(auth.refreshCookieName);
if (!refreshTokenCookie) {
// 두 토큰 모두 없거나 유효하지 않으면 인증되지 않은 사용자로 처리
event.locals.user = null;
return resolve(event);
}
// 리프레시 토큰 검증
const { userId, isValid } = auth.validateRefreshToken(refreshToken);
const { userId, isValid } = auth.validateRefreshToken(refreshTokenCookie);
if (!isValid || !userId) {
// 리프레시 토큰이 유효하지 않으면 모든 쿠키 삭제
auth.deleteAuthCookies(event);
event.locals.user = null;
return resolve(event);
}
// 리프레시 토큰이 유효하면 사용자 DB의 리프레시토큰해시와 비교
const results = await db
.select({refreshTokenHash: table.user.refreshTokenHash})
.from(table.user)
.where(eq(table.user.id, userId));
// 유효한 리프레시 토큰이 있으면 사용자 정보 조회
const user = await db.query.refreshTokens.findFirst({
where: (refreshTokens, { eq }) => eq(refreshTokens.id, userId)
});
const refreshToken_data = results.at(0);
if (!user) {
if (!refreshToken_data) {
auth.deleteAuthCookies(event);
event.locals.user = null;
return resolve(event);
}
console.log("refreshToken_cookie: ",refreshTokenCookie);
console.log("refreshToken_hash: ",refreshToken_data.refreshTokenHash);
// 새 액세스 토큰 발급
const newAccessToken = auth.generateAccessToken(user.id);
auth.setAccessTokenCookie(event, newAccessToken);
const validRefreshToken = await compare(refreshTokenCookie, refreshToken_data.refreshTokenHash);
console.log("validRefreshToken: ",validRefreshToken);
if(!validRefreshToken){
auth.deleteAuthCookies(event);
event.locals.user = null;
return resolve(event);
// 사용자 정보 설정
event.locals.user = {
id: user.id,
};
} else {
// 새 액세스 토큰 발급
const newAccessToken = auth.generateAccessToken(userId);
const newRefreshToken = auth.generateRefreshToken(userId);
auth.setAccessTokenCookie(event, newAccessToken);
auth.setRefreshTokenCookie(event, newRefreshToken);
const refreshTokenHash = await hash(refreshTokenCookie, 10) ;
await db.update(table.user).set({ refreshTokenHash: refreshTokenHash }).where(eq(table.user.id,userId));
// 사용자 정보 설정
event.locals.user = {
id: userId,
};
return resolve(event);
}
return resolve(event);
};
export const handle: Handle = handleAuth;

View File

@ -74,4 +74,8 @@ export function setRefreshTokenCookie(event: RequestEvent, token: string) {
export function deleteAuthCookies(event: RequestEvent) {
event.cookies.delete(jwtCookieName, { path: '/' });
event.cookies.delete(refreshCookieName, { path: '/' });
}
export function deleteJwtCookie(event: RequestEvent) {
event.cookies.delete(jwtCookieName, { path: '/' });
}

View File

@ -3,15 +3,9 @@ import { pgTable, serial, integer, text, timestamp } from 'drizzle-orm/pg-core';
export const user = pgTable('user', {
id: text('id').primaryKey(),
email: text('email').notNull().unique(),
passwordHash: text('password_hash').notNull()
passwordHash: text('password_hash').notNull(),
refreshTokenHash: text('refresh_token_hash').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
});
export const refreshTokens = pgTable('refresh_tokens', {
id: text('id').primaryKey(),
userId: text('user_id').notNull(),
expiresAt: timestamp('expires_at').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull()
});
export type RefreshTokens = typeof refreshTokens.$inferSelect;
export type User = typeof user.$inferSelect;

View File

@ -1,23 +1,17 @@
// D:/gitea/Jwt/src/routes/demo/lucia/+page.server.ts
import * as auth from '$lib/server/auth';
import { redirect } from '@sveltejs/kit'; // `fail`은 사용되지 않으므로 제거합니다.
import { redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types';
import {db} from "$lib/server/db";
import * as table from "$lib/server/db/schema";
import {eq} from "drizzle-orm";
// `load` 함수는 event 객체에서 locals와 cookies를 직접 받아옵니다.
export const load: PageServerLoad = async ({ locals, cookies }) => {
// 1. `locals`에서 직접 사용자 정보를 확인합니다. 이것이 표준 방식입니다.
if (!locals.user) {
// 2. 사용자가 없으면 여기서 바로 리다이렉트합니다.
throw redirect(302, '/demo/lucia/login');
}
// 이 코드는 사용자가 로그인한 경우에만 실행됩니다.
// 이전에 정의한 쿠키 이름으로 수정했습니다.
const accessToken = cookies.get(auth.jwtCookieName);
const refreshToken = cookies.get(auth.refreshCookieName);
// 3. `locals`의 사용자와 쿠키에서 읽은 토큰을 반환합니다.
return {
user: locals.user,
accessToken: accessToken ?? 'N/A', // 토큰이 없을 경우를 대비해 기본값 설정
@ -27,17 +21,13 @@ export const load: PageServerLoad = async ({ locals, cookies }) => {
export const actions: Actions = {
logout: async (event) => {
// 더 안전한 로그아웃을 위해 DB의 리프레시 토큰을 무효화합니다.
const refreshTokenFromCookie = event.cookies.get(auth.refreshCookieName);
// if (refreshTokenFromCookie) {
// // auth.ts에 구현된 invalidateRefreshToken 함수를 호출합니다.
// await auth.invalidateRefreshToken(refreshTokenFromCookie);
// }
// 그 다음 쿠키를 삭제합니다.
if (event.locals.user) {
await db.update(table.user).set({refreshTokenHash: ''}).where(eq(table.user.id, event.locals.user.id));
}
auth.deleteAuthCookies(event);
// `actions`에서는 throw redirect(...)를 사용하는 것이 표준입니다.
throw redirect(302, '/demo/lucia/login');
},
removeJwtCookie: async (event) => {
auth.deleteJwtCookie(event);
}
};

View File

@ -13,3 +13,7 @@
<form method='post' action='?/logout' use:enhance>
<button>Sign out</button>
</form>
<form method='post' action='?/removeJwtCookie' use:enhance>
<button>Remove Jwt Cookie</button>
</form>

View File

@ -45,15 +45,25 @@ export const actions: Actions = {
if (!validPassword) {
return fail(400, { message: 'Incorrect username or password' });
}
const saltRounds = 10;
// 두 토큰 모두 생성
const accessToken = auth.generateAccessToken(existingUser.id);
const refreshToken = auth.generateRefreshToken(existingUser.id);
const refreshTokenHash = await hash(refreshToken, saltRounds) ;
// 두 쿠키 모두 설정
auth.setAccessTokenCookie(event, accessToken);
auth.setRefreshTokenCookie(event, refreshToken);
try {
await db.update(table.user).set({ refreshTokenHash: refreshTokenHash }).where(eq(table.user.id, existingUser.id));
// 두 토큰 모두 생성
// 두 쿠키 모두 설정
auth.setAccessTokenCookie(event, accessToken);
auth.setRefreshTokenCookie(event, refreshToken);
} catch {
return fail(500, { message: 'An error has occurred' });
}
return redirect(302, '/demo/lucia');
},
register: async (event) => {
@ -62,6 +72,7 @@ export const actions: Actions = {
const password = formData.get('password');
const userId = generateUserId();
const refreshId = generateUserId();
// 타입 체크 및 검증
if (!email || typeof email !== 'string') {
return fail(400, { message: '이메일이 필요합니다' });
@ -76,13 +87,12 @@ export const actions: Actions = {
const passwordHash = await hash(password, saltRounds);
try {
await db.insert(table.user).values({ id: userId, email, passwordHash });
// 두 토큰 모두 생성
const accessToken = auth.generateAccessToken(userId);
const refreshToken = auth.generateRefreshToken(userId);
const refreshTokenHash = await hash(auth.generateRefreshToken(userId), saltRounds) ;
await db.insert(table.user).values({ id: userId, email, passwordHash, refreshTokenHash: refreshTokenHash });
// 두 쿠키 모두 설정
auth.setAccessTokenCookie(event, accessToken);
auth.setRefreshTokenCookie(event, refreshToken);