Skip to content

Commit c313ca6

Browse files
authored
Merge pull request #309 from manNomi/feat/login-redirect
feat : 소셜 로그인 또는 로그인 필요 페이지 로그인 시 리디렉션 구현
2 parents f3d3f02 + 49b055e commit c313ca6

File tree

5 files changed

+61
-91
lines changed

5 files changed

+61
-91
lines changed

src/api/auth/client/usePostAppleAuth.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useRouter } from "next/navigation";
1+
import { useRouter, useSearchParams } from "next/navigation";
22

33
import { AxiosResponse } from "axios";
44

55
import { publicAxiosInstance } from "@/utils/axiosInstance";
6+
import { validateSafeRedirect } from "@/utils/authUtils";
67

78
import useAuthStore from "@/lib/zustand/useAuthStore";
89
import { toast } from "@/lib/zustand/useToastStore";
@@ -34,6 +35,7 @@ const postAppleAuth = ({ code }: AppleAuthRequest): Promise<AxiosResponse<AppleA
3435

3536
const usePostAppleAuth = () => {
3637
const router = useRouter();
38+
const searchParams = useSearchParams();
3739

3840
return useMutation({
3941
mutationFn: postAppleAuth,
@@ -44,6 +46,13 @@ const usePostAppleAuth = () => {
4446
// 기존 회원일 시 - Zustand persist가 자동으로 localStorage에 저장
4547
// refreshToken은 서버에서 HTTP-only 쿠키로 자동 설정됨
4648
useAuthStore.getState().setAccessToken(data.accessToken);
49+
50+
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
51+
const redirectParam = searchParams.get("redirect");
52+
const safeRedirect = validateSafeRedirect(redirectParam);
53+
54+
toast.success("로그인에 성공했습니다.");
55+
router.replace(safeRedirect);
4756
} else {
4857
// 새로운 회원일 시 - 회원가입 페이지로 이동
4958
router.push(`/sign-up?token=${data.signUpToken}`);

src/api/auth/client/usePostEmailAuth.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRouter, useSearchParams } from "next/navigation";
33
import { AxiosResponse } from "axios";
44

55
import { publicAxiosInstance } from "@/utils/axiosInstance";
6+
import { validateSafeRedirect } from "@/utils/authUtils";
67

78
import useAuthStore from "@/lib/zustand/useAuthStore";
89
import { toast } from "@/lib/zustand/useToastStore";
@@ -37,14 +38,7 @@ const usePostEmailAuth = () => {
3738

3839
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
3940
const redirectParam = searchParams.get("redirect");
40-
let safeRedirect = "/"; // 기본값
41-
42-
if (redirectParam && typeof redirectParam === "string") {
43-
// 내부 경로인지 검증: 단일 "/"로 시작하고 "//"나 "://"를 포함하지 않아야 함
44-
if (redirectParam.startsWith("/") && !redirectParam.startsWith("//") && !redirectParam.includes("://")) {
45-
safeRedirect = redirectParam;
46-
}
47-
}
41+
const safeRedirect = validateSafeRedirect(redirectParam);
4842

4943
toast.success("로그인에 성공했습니다.");
5044
router.replace(safeRedirect);

src/api/auth/client/usePostKakaoAuth.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useRouter, useSearchParams } from "next/navigation";
33
import { AxiosResponse } from "axios";
44

55
import { publicAxiosInstance } from "@/utils/axiosInstance";
6+
import { validateSafeRedirect } from "@/utils/authUtils";
67

78
import useAuthStore from "@/lib/zustand/useAuthStore";
89
import { toast } from "@/lib/zustand/useToastStore";
@@ -49,15 +50,9 @@ const usePostKakaoAuth = () => {
4950

5051
// 안전한 리다이렉트 처리 - 오픈 리다이렉트 방지
5152
const redirectParam = searchParams.get("redirect");
52-
let safeRedirect = "/"; // 기본값
53-
54-
if (redirectParam && typeof redirectParam === "string") {
55-
// 내부 경로인지 검증: 단일 "/"로 시작하고 "//"나 "://"를 포함하지 않아야 함
56-
if (redirectParam.startsWith("/") && !redirectParam.startsWith("//") && !redirectParam.includes("://")) {
57-
safeRedirect = redirectParam;
58-
}
59-
}
53+
const safeRedirect = validateSafeRedirect(redirectParam);
6054

55+
toast.success("로그인에 성공했습니다.");
6156
router.replace(safeRedirect);
6257
} else {
6358
// 새로운 회원일 시 - 회원가입 페이지로 이동

src/app/admin/design-system/page.tsx

Lines changed: 0 additions & 71 deletions
This file was deleted.

src/utils/authUtils.ts

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@ import { appleOAuth2CodeResponse } from "@/types/auth";
22

33
import { toast } from "@/lib/zustand/useToastStore";
44

5+
// 오픈 리다이렉트 공격 방지를 위한 redirect 파라미터 검증
6+
// 단일 "/"로 시작하고 "//"나 "://"를 포함하지 않는 내부 경로만 허용
7+
export const validateSafeRedirect = (redirectParam: string | null): string => {
8+
if (!redirectParam || typeof redirectParam !== "string") {
9+
return "/";
10+
}
11+
12+
if (redirectParam.startsWith("/") && !redirectParam.startsWith("//") && !redirectParam.includes("://")) {
13+
return redirectParam;
14+
}
15+
16+
return "/";
17+
};
18+
519
export const authProviderName = (provider: "KAKAO" | "APPLE" | "EMAIL"): string => {
620
if (provider === "KAKAO") {
721
return "카카오";
@@ -16,8 +30,20 @@ export const authProviderName = (provider: "KAKAO" | "APPLE" | "EMAIL"): string
1630

1731
export const kakaoLogin = () => {
1832
if (window.Kakao && window.Kakao.Auth) {
33+
// 현재 URL에서 redirect 파라미터 추출 및 검증
34+
const urlParams = new URLSearchParams(window.location.search);
35+
const redirectParam = urlParams.get("redirect");
36+
const safeRedirect = validateSafeRedirect(redirectParam);
37+
38+
// 검증된 redirect 파라미터를 callback URL에 전달
39+
let redirectUri = `${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback`;
40+
// 기본값 "/"가 아닌 경우에만 redirect 파라미터 추가 (기본값이면 생략 가능)
41+
if (safeRedirect !== "/") {
42+
redirectUri += `?redirect=${encodeURIComponent(safeRedirect)}`;
43+
}
44+
1945
window.Kakao.Auth.authorize({
20-
redirectUri: `${process.env.NEXT_PUBLIC_WEB_URL}/login/kakao/callback`,
46+
redirectUri,
2147
});
2248
} else {
2349
toast.error("Kakao SDK를 불러오는 중입니다. 잠시 후 다시 시도해주세요.");
@@ -30,17 +56,34 @@ export const appleLogin = async () => {
3056
return;
3157
}
3258

59+
// 현재 URL에서 redirect 파라미터 추출 및 검증
60+
const urlParams = new URLSearchParams(window.location.search);
61+
const redirectParam = urlParams.get("redirect");
62+
const safeRedirect = validateSafeRedirect(redirectParam);
63+
64+
// 검증된 redirect 파라미터를 callback URL에 전달
65+
let redirectURI = `${process.env.NEXT_PUBLIC_WEB_URL}/login/apple/callback`;
66+
// 기본값 "/"가 아닌 경우에만 redirect 파라미터 추가 (기본값이면 생략 가능)
67+
if (safeRedirect !== "/") {
68+
redirectURI += `?redirect=${encodeURIComponent(safeRedirect)}`;
69+
}
70+
3371
window.AppleID.auth.init({
3472
clientId: process.env.NEXT_PUBLIC_APPLE_CLIENT_ID,
3573
scope: process.env.NEXT_PUBLIC_APPLE_SCOPE,
36-
redirectURI: `${process.env.NEXT_PUBLIC_WEB_URL}/login/apple/callback`,
74+
redirectURI,
3775
usePopup: true,
3876
});
3977

4078
try {
4179
const res: appleOAuth2CodeResponse = await window.AppleID.auth.signIn();
4280
if (res.authorization) {
43-
window.location.href = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
81+
// 검증된 redirect 파라미터를 callback URL에 전달
82+
let callbackUrl = `/login/apple/callback?code=${encodeURIComponent(res.authorization.code)}`;
83+
if (safeRedirect !== "/") {
84+
callbackUrl += `&redirect=${encodeURIComponent(safeRedirect)}`;
85+
}
86+
window.location.href = callbackUrl;
4487
}
4588
} catch (error) {
4689
// Log error for developers

0 commit comments

Comments
 (0)