I'll explain each point of the multi-app server-side token refresh solution in detail:
This creates a secure API endpoint in your authenticating app that other apps can call to refresh tokens.
Security Verification:
const authHeader = req.headers.authorization
if (authHeader !== `Bearer ${process.env.INTER_APP_SECRET}`) {
return res.status(401).json({ error: 'Unauthorized' })
}INTER_APP_SECRET) for inter-app authenticationOAuth2 Token Refresh:
const response = await fetch(`${process.env.OAUTH2_TOKEN_URL}`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.OAUTH2_CLIENT_ID,
client_secret: process.env.OAUTH2_CLIENT_SECRET,
}),
})JWT Generation:
const newJWT = await generateJWT({
access_token: refreshedTokens.access_token,
refresh_token: refreshedTokens.refresh_token || refresh_token,
expires_at: refreshedTokens.expires_at,
user: refreshedTokens.user
})This utility handles token validation and refresh logic within your main application's server-side code.
Token Expiry Check:
const now = Math.floor(Date.now() / 1000)
const isExpired = decoded.expires_at <= nowServer-to-Server Communication:
const response = await fetch(`${process.env.AUTH_APP_URL}/api/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.INTER_APP_SECRET}`,
},
body: JSON.stringify({
refresh_token: decodedToken.refresh_token,
access_token: decodedToken.access_token,
}),
})Cookie Management:
cookieStore.set('auth-token', newJWT, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/',
})Intercepts all requests to automatically refresh tokens before they reach your pages or API routes.
Proactive Refresh:
const willExpireSoon = decoded.expires_at <= (now + 300) // 5 minutes
if (isExpired || willExpireSoon) {
// Refresh token
}Request Interception:
export const config = {
matcher: [
'/((?!api/health|_next/static|_next/image|favicon.ico).*)',
],
}Transparent Updates:
const response = NextResponse.next()
response.cookies.set('auth-token', refreshedTokens.jwt, {
// cookie options
})
return responseShows how to use the refresh utility in Next.js App Router server components.
Server-Side Session Access:
const session = await getServerSession()Error Handling:
if (!session) {
redirect(`${process.env.AUTH_APP_URL}/login`)
}
if (session.error) {
redirect(`${process.env.AUTH_APP_URL}/login?error=token_refresh_failed`)
}API Calls with Fresh Tokens:
const response = await fetch(`${process.env.API_BASE_URL}/user`, {
headers: {
'Authorization': `Bearer ${session.access_token}`,
},
})Demonstrates how to handle authentication in your app's API routes.
Route Protection:
const session = await getServerSession()
if (!session || session.error) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}Downstream API Calls:
const response = await fetch(`${process.env.API_BASE_URL}/data`, {
headers: {
'Authorization': `Bearer ${session.access_token}`,
},
})Configuration that connects your apps and external services.
AUTH_APP_URL: URL of your global front door appINTER_APP_SECRET: Shared secret for secure inter-app communicationJWT_SECRET: Secret for JWT verificationAPI_BASE_URL: Your backend API endpointsOAUTH2_CLIENT_ID/SECRET: OAuth2 provider credentialsOAUTH2_TOKEN_URL: Provider's token refresh endpointINTER_APP_SECRET: Same shared secret as main appThis architecture ensures your server-side rendered pages and API routes always have valid tokens, while keeping your authentication logic centralized and secure.
The error you're encountering indicates that NextAuth is receiving a non-JSON response when it expects JSON. The base64 string "Rm9yYmlkZGVuCg==" decodes to "Forbidden", which suggests your authentication endpoint is returning a 403 Forbidden response.
In your multi-app architecture, this error typically occurs when:
Since you have a global front door handling authentication and routing to another app, you need to configure NextAuth properly:
If NextAuth continues to cause issues with your multi-app setup, consider a custom session provider:
The "Rm9yYmlkZGVuCg==" (Forbidden) error suggests your main app is being blocked when trying to access the authentication endpoints. Make sure your global front door is configured to accept requests from your main app's domain.
To configure the generation, complete these steps: