Skip to content

Commit dfbbe17

Browse files
yellowjang장아영
andauthored
[#119] ✨ 로그인 미들웨어 생성 (토큰 재발급 및 권한 설정) (#124)
* [#119] ♻️ change refreshToken maxAge * [#119] ✨ create middleware * [#119] 🗑️ remove axios * [#119] 🗑️ remove auth api * [#119] ✨ create middleware for refreshToken * [#119] ♻️ change backend_api -> backend_base_url * [#119] 🚚 change middleware location * [#119] 🗑️ remove `auth.ts` file --------- Co-authored-by: 장아영 <[email protected]>
1 parent fa979cb commit dfbbe17

File tree

7 files changed

+105
-7
lines changed

7 files changed

+105
-7
lines changed

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"dependencies": {
33
"@tanstack/react-query": "^5.59.19",
4-
"axios": "^1.7.7",
54
"clsx": "^2.1.1",
65
"next": "^15.0.2",
76
"react": "^18.3.1",
@@ -82,7 +81,5 @@
8281
"overrides": {
8382
"@types/node": "22.9.0"
8483
}
85-
},
86-
"name": "",
87-
"version": ""
84+
}
8885
}

src/app/(pages)/protected/page.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function protectedPage() {
2+
return (
3+
<>
4+
<div>인증되어야만 접근 가능한 페이지</div>
5+
</>
6+
)
7+
}

src/app/api/auth/refresh/route.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { NextResponse } from 'next/server'
2+
3+
const BACKEND_BASE_URL = process.env.NEXT_PUBLIC_BACKEND_BASE_URL
4+
5+
export const POST = async (req: Request): Promise<NextResponse> => {
6+
const { refreshToken } = await req.json()
7+
8+
try {
9+
const response = await fetch(`${BACKEND_BASE_URL}/refresh`, {
10+
method: 'POST',
11+
headers: { 'Content-Type': 'application/json' },
12+
body: JSON.stringify({ refreshToken }),
13+
})
14+
15+
if (!response.ok) {
16+
const errorData = await response.json()
17+
return NextResponse.json(
18+
{
19+
success: false,
20+
message: errorData.message || 'Token refresh failed',
21+
},
22+
{ status: response.status }
23+
)
24+
}
25+
26+
const { accessToken } = await response.json()
27+
28+
return NextResponse.json({ success: true, accessToken })
29+
} catch (error: any) {
30+
console.error('Token refresh error:', error)
31+
return NextResponse.json(
32+
{ success: false, message: 'Internal server error' },
33+
{ status: 500 }
34+
)
35+
}
36+
}

src/app/api/auth/sign-in/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { NextResponse } from 'next/server'
22

3+
const BACKEND_BASE_URL = process.env.NEXT_PUBLIC_BACKEND_BASE_URL
4+
35
export const POST = async (req: Request): Promise<NextResponse> => {
46
const { email, password } = await req.json()
5-
const response = await fetch('http://43.202.50.174:8080/v1/auth/sign-in', {
7+
const response = await fetch(`${BACKEND_BASE_URL}/v1/auth/sign-in`, {
68
headers: { 'Content-Type': 'application/json' },
79
method: 'POST',
810
body: JSON.stringify({ email, password }),
@@ -33,7 +35,7 @@ export const POST = async (req: Request): Promise<NextResponse> => {
3335
secure: true,
3436
sameSite: 'strict',
3537
path: '/',
36-
maxAge: 86400,
38+
maxAge: 1209600,
3739
})
3840

3941
return res

src/middleware.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { type NextRequest, NextResponse } from 'next/server'
2+
3+
console.log('Middleware is running')
4+
export const config = {
5+
matcher: ['/protected/:path*'],
6+
// 인증이 필요한 사이트
7+
}
8+
9+
export async function middleware(req: NextRequest) {
10+
console.log('Middleware is running')
11+
console.log('Requested URL:', req.nextUrl.pathname)
12+
console.log('Headers:', req.headers)
13+
const cookies = req.headers.get('cookie')
14+
const accessToken = cookies?.match(/accessToken=([^;]*)/)?.[1]
15+
const refreshToken = cookies?.match(/refreshToken=([^;]*)/)?.[1]
16+
17+
if (!accessToken) {
18+
console.log('Access Token is missing')
19+
if (!refreshToken) {
20+
console.log('Refresh Token is also missing. Redirecting to /sign-in')
21+
return NextResponse.redirect(new URL('/sign-in', req.url))
22+
}
23+
24+
try {
25+
const refreshResponse = await fetch(`/api/auth/refresh`, {
26+
method: 'POST',
27+
headers: { 'Content-Type': 'application/json' },
28+
body: JSON.stringify({ refreshToken }),
29+
})
30+
31+
if (!refreshResponse.ok) {
32+
console.error('Failed to refresh Access Token. Redirecting to /sign-in')
33+
return NextResponse.redirect(new URL('/sign-in', req.url))
34+
}
35+
36+
const { accessToken: newAccessToken } = await refreshResponse.json()
37+
38+
const res = NextResponse.next()
39+
res.cookies.set('accessToken', newAccessToken, {
40+
httpOnly: true,
41+
secure: true,
42+
sameSite: 'strict',
43+
path: '/',
44+
maxAge: 3600,
45+
})
46+
console.log('Access Token refreshed successfully')
47+
return res
48+
} catch (error) {
49+
console.error('Middleware Error', error)
50+
return NextResponse.redirect(new URL('/sign-in', req.url))
51+
}
52+
}
53+
console.log('Access Token is valid. Keep going')
54+
return NextResponse.next()
55+
}

src/services/auth/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export const SignUp = async (data: SignUpRequest): Promise<Response> => {
1010
body: JSON.stringify(data),
1111
})
1212
}
13+

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@
2424
},
2525
"types": ["react", "jest", "node", "@storybook/react"]
2626
},
27-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/middleware.ts"],
2828
"exclude": ["node_modules"]
2929
}

0 commit comments

Comments
 (0)