Skip to content

Commit 459d3d3

Browse files
authored
Merge pull request #80 from team-ppointer/feat/service/kakao-login-#78
[Feat/service/#78] 카카오로그인
2 parents a07377c + e55dcb5 commit 459d3d3

File tree

21 files changed

+3000
-1203
lines changed

21 files changed

+3000
-1203
lines changed

apps/service/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
"build": "next build",
88
"start": "next start",
99
"lint": "next lint",
10-
"openapi": "pnpm dlx openapi-typescript https://dev.math-pointer.com/v3/api-docs --output ./src/types/api/schema.d.ts && prettier --write ./src/types/api/schema.d.ts"
10+
"openapi": "pnpm dlx openapi-typescript https://api.math-pointer.com/v3/api-docs --output ./src/types/api/schema.d.ts && prettier --write ./src/types/api/schema.d.ts"
1111
},
1212
"dependencies": {
1313
"@next/third-parties": "^15.2.4",
1414
"@repo/pointer-design-system": "workspace:*",
1515
"@tanstack/react-query": "^5.66.0",
1616
"@tanstack/react-query-devtools": "^5.66.0",
1717
"dayjs": "^1.11.13",
18+
"clsx": "^2.1.1",
1819
"next": "15.1.4",
1920
"openapi-fetch": "^0.13.4",
2021
"openapi-react-query": "^0.3.0",

apps/service/src/apis/authMiddleware.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
11
'use client';
22
import { Middleware } from 'openapi-fetch';
33

4-
import { getAccessToken, setAccessToken } from '@utils';
4+
import { getAccessToken, setAccessToken, setName, setRefreshToken } from '@utils';
5+
import { postRefreshToken } from '@/apis/controller/auth';
56

6-
const UNPROTECTED_ROUTES = ['/api/v1/auth/admin/login', '/api/v1/auth/oauth/social-login'];
7+
const UNPROTECTED_ROUTES = ['/api/student/auth/social/login', '/api/common/auth/refresh'];
78

89
const reissueToken = async () => {
9-
try {
10-
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/v1/auth/reissue`, {
11-
method: 'GET',
12-
credentials: 'include',
13-
});
10+
let accessToken = getAccessToken();
1411

15-
if (!response.ok) throw new Error('Token reissue failed');
16-
17-
const data = await response.json();
18-
const accessToken = data.data.accessToken;
19-
setAccessToken(accessToken);
12+
if (accessToken) {
2013
return accessToken;
21-
} catch (error) {
22-
console.error('Reissue failed:', error);
14+
}
15+
16+
const result = await postRefreshToken();
17+
18+
if (!result.isSuccess || !result.data) {
19+
console.error('액세스토큰 갱신 실패:', result.error);
2320
localStorage.removeItem('accessToken');
21+
localStorage.removeItem('refreshToken');
2422
window.location.href = '/login';
2523
return null;
2624
}
25+
26+
if (result.data?.token.accessToken) {
27+
setAccessToken(result.data.token.accessToken);
28+
accessToken = result.data.token.accessToken;
29+
}
30+
if (result.data?.token.refreshToken) {
31+
setRefreshToken(result.data.token.refreshToken);
32+
}
33+
if (result.data?.name) {
34+
setName(result.data.name);
35+
}
36+
return accessToken;
2737
};
2838

2939
const authMiddleware: Middleware = {
@@ -32,18 +42,12 @@ const authMiddleware: Middleware = {
3242
return undefined;
3343
}
3444

35-
let accessToken = getAccessToken();
45+
const accessToken = await reissueToken();
3646

37-
if (!accessToken) {
38-
accessToken = await reissueToken();
39-
40-
if (!accessToken) {
41-
console.error('Access token reissue failed. Logging out...');
42-
return request;
43-
}
47+
if (accessToken) {
48+
request.headers.set('Authorization', `Bearer ${accessToken}`);
4449
}
4550

46-
request.headers.set('Authorization', `Bearer ${accessToken}`);
4751
return request;
4852
},
4953

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import postLogin from './postLogin';
21
import postKakaoLogin from './postKakaoLogin';
2+
import postUserInfo from './postUserInfo';
3+
import postRefreshToken from './postRefreshToken';
34

4-
export { postLogin, postKakaoLogin };
5+
export { postKakaoLogin, postUserInfo, postRefreshToken };
Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,24 @@
11
'use client';
22

3-
import { setAccessToken, setName } from '@utils';
43
import { client } from '@/apis/client';
54

6-
const postKakaoAccessToken = async (code: string) => {
7-
const response = await fetch(`https://kauth.kakao.com/oauth/token`, {
8-
method: 'POST',
9-
headers: {
10-
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
11-
},
12-
body: `grant_type=authorization_code&client_id=${process.env.NEXT_PUBLIC_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&code=${code}`,
13-
});
14-
const jsonData = await response.json();
15-
return jsonData.access_token;
16-
};
17-
18-
const postKakaoLogin = async (code: string) => {
19-
const accessToken = await postKakaoAccessToken(code);
20-
const response = await client.POST('/api/v1/auth/oauth/social-login', {
21-
params: {
22-
header: {
23-
social_access_token: accessToken,
24-
},
25-
query: {
26-
provider: 'KAKAO',
27-
},
5+
const postKakaoLogin = async () => {
6+
const response = await client.POST('/api/student/auth/login/social', {
7+
body: {
8+
provider: 'KAKAO',
9+
redirectUri: process.env.NEXT_PUBLIC_REDIRECT_URI ?? '',
2810
},
2911
});
3012

3113
try {
32-
if (
33-
response &&
34-
response.data &&
35-
response.data.data &&
36-
response.data.data.name &&
37-
response.data.data.accessToken
38-
) {
39-
const { accessToken, name } = response.data.data;
40-
setAccessToken(accessToken);
41-
setName(name);
42-
43-
window.location.href = '/';
14+
if (response && response.data) {
15+
return { isSuccess: true, loginUrl: response.data.loginUrl };
4416
} else {
45-
console.error('accessToken을 찾을 수 없습니다:', response);
17+
return { isSuccess: false, error: '데이터를 찾을 수 없습니다.' };
4618
}
4719
} catch (error) {
48-
console.error('소셜 로그인 요청 오류:', error);
20+
return { isSuccess: false, error: error };
4921
}
50-
51-
return response;
5222
};
5323

5424
export default postKakaoLogin;

apps/service/src/apis/controller/auth/postLogin.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
'use client';
2+
3+
const postRefreshToken = async () => {
4+
try {
5+
const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/common/auth/refresh`, {
6+
method: 'GET',
7+
});
8+
if (!res.ok) throw new Error('Refresh failed');
9+
10+
const data = await res.json();
11+
return { isSuccess: true, data };
12+
} catch (e) {
13+
return { isSuccess: false, error: e };
14+
}
15+
};
16+
17+
export default postRefreshToken;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { client } from '@apis';
2+
3+
const postUserInfo = async (name: string, grade: number) => {
4+
try {
5+
const response = await client.POST('/api/student/auth/register/social', {
6+
body: {
7+
name: name,
8+
grade: grade,
9+
},
10+
});
11+
return { isSuccess: true, data: response.data };
12+
} catch (error) {
13+
return { isSuccess: false, error: error };
14+
}
15+
};
16+
17+
export default postUserInfo;

apps/service/src/app/api/auth/callback/kakao/page.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
'use client';
22

3-
import { useSearchParams } from 'next/navigation';
3+
import { useRouter, useSearchParams } from 'next/navigation';
44
import { useEffect } from 'react';
55

6-
import { postKakaoLogin } from '@apis';
6+
import { setAccessToken, setRefreshToken } from '@utils';
77

88
const Page = () => {
99
const searchParams = useSearchParams();
10-
const code = searchParams.get('code');
10+
const router = useRouter();
11+
const { success, isFirstLogin, accessToken, refreshToken } = Object.fromEntries(
12+
searchParams.entries()
13+
);
1114

1215
useEffect(() => {
13-
if (code) {
14-
postKakaoLogin(code);
16+
if (!success || !accessToken) {
17+
router.replace('/login');
18+
return;
1519
}
16-
}, [code]);
20+
21+
setAccessToken(accessToken);
22+
setRefreshToken(refreshToken);
23+
24+
if (isFirstLogin) {
25+
router.replace('/onboarding');
26+
} else {
27+
router.replace('/');
28+
}
29+
}, [searchParams]);
1730

1831
return <></>;
1932
};

apps/service/src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export default function RootLayout({
5050
modal: React.ReactNode;
5151
}>) {
5252
return (
53-
<html lang='ko'>
53+
<html lang='ko' suppressHydrationWarning>
5454
<head>
5555
<link rel='preconnect' href='https://www.google-analytics.com' crossOrigin='anonymous' />
5656
<link rel='preconnect' href='https://prod.math-pointer.com' crossOrigin='anonymous' />

apps/service/src/app/login/page.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ import { useEffect } from 'react';
33
import { useRouter } from 'next/navigation';
44

55
import { getAccessToken, trackEvent } from '@utils';
6+
import { postKakaoLogin } from '@apis';
67
import { LogoLogin } from '@/assets/svg/logo';
78
import { KakaoButton } from '@/components/login';
89

910
const Page = () => {
1011
const router = useRouter();
11-
const kakaoLoginUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${
12-
process.env.NEXT_PUBLIC_REST_API_KEY
13-
}&redirect_uri=${process.env.NEXT_PUBLIC_REDIRECT_URI}&response_type=code`;
1412

15-
const handleLoginClick = () => {
13+
const handleLoginClick = async () => {
1614
trackEvent('kakao_login_click');
17-
window.location.replace(kakaoLoginUrl);
15+
const result = await postKakaoLogin();
16+
if (result.isSuccess && result.loginUrl) {
17+
router.push(result.loginUrl);
18+
} else {
19+
console.error('로그인 URL을 가져오는 데 실패했습니다.');
20+
}
1821
};
1922

2023
useEffect(() => {

0 commit comments

Comments
 (0)