Skip to content

Commit 16adc18

Browse files
committed
feat: token refresh
1 parent ea8ad01 commit 16adc18

File tree

4 files changed

+91
-144
lines changed

4 files changed

+91
-144
lines changed

app/(auth)/refresh/actions.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use server'
2+
3+
import { cookies } from 'next/headers'
4+
5+
export async function refreshToken(from: string = '/') {
6+
const refreshTokenName = process.env.REFRESH_TOKEN_COOKIE_NAME as string
7+
const sessionName = process.env.SESSION_COOKIE_NAME as string
8+
9+
const cookieStore = await cookies()
10+
const refreshToken = cookieStore.get(refreshTokenName)?.value
11+
12+
if (!refreshToken) {
13+
return { success: false, error: 'No refresh token found' }
14+
}
15+
16+
try {
17+
const refreshResponse = await fetch(
18+
`https://securetoken.googleapis.com/v1/token?key=${process.env.FIREBASE_API_KEY}`,
19+
{
20+
method: 'POST',
21+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
22+
body: new URLSearchParams({
23+
grant_type: 'refresh_token',
24+
refresh_token: refreshToken,
25+
}),
26+
}
27+
)
28+
29+
if (!refreshResponse.ok) {
30+
console.log('Refresh response not ok:', await refreshResponse.text())
31+
return { success: false, error: 'Token refresh failed' }
32+
}
33+
34+
const data = await refreshResponse.json()
35+
const newIdToken = data.id_token
36+
const newRefreshToken = data.refresh_token
37+
38+
cookieStore.set(sessionName, newIdToken, {
39+
maxAge: 30 * 24 * 60 * 60, // 30 days
40+
httpOnly: true,
41+
secure: true,
42+
sameSite: 'strict',
43+
})
44+
45+
cookieStore.set(refreshTokenName, newRefreshToken, {
46+
maxAge: 30 * 24 * 60 * 60, // 30 days
47+
httpOnly: true,
48+
secure: true,
49+
sameSite: 'strict',
50+
})
51+
52+
console.log('Token refresh successful')
53+
return { success: true, redirectTo: from }
54+
} catch (refreshError) {
55+
console.error('Token refresh failed:', refreshError)
56+
return { success: false, error: 'Token refresh failed' }
57+
}
58+
}

app/(auth)/refresh/page.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client'
2+
3+
import { use, useEffect, useRef } from 'react'
4+
import { useRouter } from 'next/navigation'
5+
import { refreshToken } from './actions'
6+
import type { Route } from 'next'
7+
8+
export default function RefreshPage({
9+
searchParams,
10+
}: {
11+
searchParams: Promise<{ from?: string }>
12+
}) {
13+
const router = useRouter()
14+
const { from = '/' } = use(searchParams)
15+
const hasRefreshed = useRef(false)
16+
17+
useEffect(() => {
18+
if (hasRefreshed.current) return
19+
hasRefreshed.current = true
20+
21+
refreshToken(from).then((result) => {
22+
if (result.success && result.redirectTo) {
23+
router.replace(result.redirectTo as Route)
24+
} else {
25+
router.replace(`/login?from=${from}` as Route)
26+
}
27+
})
28+
}, [from, router])
29+
30+
return null
31+
}

lib/firebase/firebase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ export async function Verify({
4747
}
4848

4949
if (forceRedirect && (!headers.has('X-Uid') || !headers.has('X-Client-Id'))) {
50-
redirect(`/login?from=${encodeURIComponent(from)}`, RedirectType.replace)
50+
// redirect(`/login?from=${encodeURIComponent(from)}`, RedirectType.replace)
51+
redirect(`/refresh?from=${encodeURIComponent(from)}`, RedirectType.replace)
5152
}
5253

5354
return headers

proxy.ts

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

0 commit comments

Comments
 (0)