Loading src/app/api/logout/route.ts +2 −1 Original line number Diff line number Diff line import { buildExternalUrl } from '@/libs/url' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export async function POST(req: NextRequest) { const redirectTo = new URL('/login', req.url) const redirectTo = buildExternalUrl(req, '/login') const isProduction = process.env.NODE_ENV === 'production' const res = NextResponse.redirect(redirectTo) Loading src/app/api/refresh/route.ts +8 −6 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { cookies } from 'next/headers'; import { jwtVerify } from 'jose' import { buildExternalUrl } from '@/libs/url'; export async function POST(req: NextRequest) { Loading @@ -19,7 +20,8 @@ export async function GET(req: NextRequest) { const isProduction = process.env.NODE_ENV === 'production' if (!refreshToken) { return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } else { let payload; Loading @@ -31,7 +33,7 @@ export async function GET(req: NextRequest) { } catch (e) { // if not verified (invalid or expired), redirect to login console.log('Refresh token verification failed, redirecting to login.'); return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } console.log('Refresh token found and valid'); Loading Loading @@ -60,7 +62,7 @@ export async function GET(req: NextRequest) { const data = await res.json() if (!res.ok) { const response = NextResponse.redirect(new URL('/api/logout', req.url)) const response = NextResponse.redirect(buildExternalUrl(req, '/api/logout')) response.cookies.set('access_token', '', { path: '/', httpOnly: true, Loading @@ -81,10 +83,10 @@ export async function GET(req: NextRequest) { const access_token = data?.access_token const refresh_token = data?.refresh_token if (!access_token) return NextResponse.redirect(new URL('/api/logout', req.url)) if (!access_token) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) const response = NextResponse.redirect(new URL(nextPath, req.url)) const response = NextResponse.redirect(buildExternalUrl(req, nextPath)) response.cookies.set('access_token', access_token, { path: '/', Loading @@ -106,6 +108,6 @@ export async function GET(req: NextRequest) { return response } catch (err) { console.error('Refresh endpoint error:', err) return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } } src/libs/url.ts 0 → 100644 +17 −0 Original line number Diff line number Diff line import { NextRequest } from "next/server"; function getForwardedValue(value: string | null): string | null { return value?.split(',')[0]?.trim() || null; } export function buildExternalUrl(req: NextRequest, path: string): URL { const forwardedProto = getForwardedValue(req.headers.get('x-forwarded-proto')); const forwardedHost = getForwardedValue(req.headers.get('x-forwarded-host')); const host = forwardedHost ?? getForwardedValue(req.headers.get('host')); if (forwardedProto && host) { return new URL(path, `${forwardedProto}://${host}`); } return new URL(path, req.url); } No newline at end of file src/proxy.ts +6 −5 Original line number Diff line number Diff line import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { jwtVerify } from 'jose' import { buildExternalUrl } from './libs/url' const PUBLIC_PATHS = ['/login', '/api', '/_next/', '/favicon.ico', '/public/', '/_next/static'] Loading @@ -22,7 +23,7 @@ export async function proxy(req: NextRequest) { if (!refreshToken) { console.log('No refresh token found — redirecting to login') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } else { console.log('Refresh token found') Loading @@ -38,7 +39,7 @@ export async function proxy(req: NextRequest) { if (err instanceof Error && (err as any).code === 'ERR_JWT_EXPIRED') { console.log('Refresh Token expired — refresh not possible redirecting to login'); } return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } } Loading @@ -51,7 +52,7 @@ export async function proxy(req: NextRequest) { if (!payload.sub) { console.error('JWT missing sub claim') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } console.log('JWT verified successfully'); Loading @@ -62,7 +63,7 @@ export async function proxy(req: NextRequest) { console.error('JWT verification failed') if (err instanceof Error && (err as any).code === 'ERR_JWT_EXPIRED') { console.log('Access Token expired — redirecting to server refresh endpoint'); const refreshUrl = new URL(`/api/refresh?onlyAccessToken=true`, req.url) const refreshUrl = buildExternalUrl(req, `/api/refresh?onlyAccessToken=true`) refreshUrl.searchParams.set('next', req.nextUrl.pathname) return NextResponse.redirect(refreshUrl) Loading @@ -71,7 +72,7 @@ export async function proxy(req: NextRequest) { } console.error('No token found in cookies') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] } Loading
src/app/api/logout/route.ts +2 −1 Original line number Diff line number Diff line import { buildExternalUrl } from '@/libs/url' import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export async function POST(req: NextRequest) { const redirectTo = new URL('/login', req.url) const redirectTo = buildExternalUrl(req, '/login') const isProduction = process.env.NODE_ENV === 'production' const res = NextResponse.redirect(redirectTo) Loading
src/app/api/refresh/route.ts +8 −6 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { cookies } from 'next/headers'; import { jwtVerify } from 'jose' import { buildExternalUrl } from '@/libs/url'; export async function POST(req: NextRequest) { Loading @@ -19,7 +20,8 @@ export async function GET(req: NextRequest) { const isProduction = process.env.NODE_ENV === 'production' if (!refreshToken) { return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } else { let payload; Loading @@ -31,7 +33,7 @@ export async function GET(req: NextRequest) { } catch (e) { // if not verified (invalid or expired), redirect to login console.log('Refresh token verification failed, redirecting to login.'); return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } console.log('Refresh token found and valid'); Loading Loading @@ -60,7 +62,7 @@ export async function GET(req: NextRequest) { const data = await res.json() if (!res.ok) { const response = NextResponse.redirect(new URL('/api/logout', req.url)) const response = NextResponse.redirect(buildExternalUrl(req, '/api/logout')) response.cookies.set('access_token', '', { path: '/', httpOnly: true, Loading @@ -81,10 +83,10 @@ export async function GET(req: NextRequest) { const access_token = data?.access_token const refresh_token = data?.refresh_token if (!access_token) return NextResponse.redirect(new URL('/api/logout', req.url)) if (!access_token) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) const response = NextResponse.redirect(new URL(nextPath, req.url)) const response = NextResponse.redirect(buildExternalUrl(req, nextPath)) response.cookies.set('access_token', access_token, { path: '/', Loading @@ -106,6 +108,6 @@ export async function GET(req: NextRequest) { return response } catch (err) { console.error('Refresh endpoint error:', err) return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } }
src/libs/url.ts 0 → 100644 +17 −0 Original line number Diff line number Diff line import { NextRequest } from "next/server"; function getForwardedValue(value: string | null): string | null { return value?.split(',')[0]?.trim() || null; } export function buildExternalUrl(req: NextRequest, path: string): URL { const forwardedProto = getForwardedValue(req.headers.get('x-forwarded-proto')); const forwardedHost = getForwardedValue(req.headers.get('x-forwarded-host')); const host = forwardedHost ?? getForwardedValue(req.headers.get('host')); if (forwardedProto && host) { return new URL(path, `${forwardedProto}://${host}`); } return new URL(path, req.url); } No newline at end of file
src/proxy.ts +6 −5 Original line number Diff line number Diff line import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { jwtVerify } from 'jose' import { buildExternalUrl } from './libs/url' const PUBLIC_PATHS = ['/login', '/api', '/_next/', '/favicon.ico', '/public/', '/_next/static'] Loading @@ -22,7 +23,7 @@ export async function proxy(req: NextRequest) { if (!refreshToken) { console.log('No refresh token found — redirecting to login') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } else { console.log('Refresh token found') Loading @@ -38,7 +39,7 @@ export async function proxy(req: NextRequest) { if (err instanceof Error && (err as any).code === 'ERR_JWT_EXPIRED') { console.log('Refresh Token expired — refresh not possible redirecting to login'); } return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } } Loading @@ -51,7 +52,7 @@ export async function proxy(req: NextRequest) { if (!payload.sub) { console.error('JWT missing sub claim') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } console.log('JWT verified successfully'); Loading @@ -62,7 +63,7 @@ export async function proxy(req: NextRequest) { console.error('JWT verification failed') if (err instanceof Error && (err as any).code === 'ERR_JWT_EXPIRED') { console.log('Access Token expired — redirecting to server refresh endpoint'); const refreshUrl = new URL(`/api/refresh?onlyAccessToken=true`, req.url) const refreshUrl = buildExternalUrl(req, `/api/refresh?onlyAccessToken=true`) refreshUrl.searchParams.set('next', req.nextUrl.pathname) return NextResponse.redirect(refreshUrl) Loading @@ -71,7 +72,7 @@ export async function proxy(req: NextRequest) { } console.error('No token found in cookies') return NextResponse.redirect(new URL('/api/logout', req.url)) return NextResponse.redirect(buildExternalUrl(req, '/api/logout')) } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }