Using Client-Side APIs
The starter example uses the inMemoryPersistence (opens in a new tab) strategy to rely solely on server-side tokens, thus avoiding any consistency issues on client-side.
This approach is recommended, but causes a few issues user may run into:
- Stale tokens: In long-running client sessions, server-side tokens may no longer be valid, requiring user to refresh the page in order to get access to valid token. This may happen when user re-opens tab after 1 hour.
- Unauthenticated Firebase Client SDK environment:
inMemoryPersistence
will causecurrentUser
to benull
, when trying to access it using client-side APIs (opens in a new tab), in most cases. This prevents us from using Firebase client-side SDKs.
next-firebase-auth-edge
provides a number of features that solves aforementioned issues, as listed below:
Enable Refresh Token API endpoint in Auth Middleware
In long-running client-side sessions (e.g., if a user reopens a tab after 1 hour), the server-side token may be expired. This can cause access issues when using the token to validate external API calls or when using the customToken
together with the signInWithCustomToken
Firebase SDK function.
To fix this, authMiddleware
can expose a special endpoint to refresh client-side tokens, if current server-side token has expired.
To enable the endpoint, define refreshTokenPath
middleware option:
export async function middleware(request: NextRequest) {
return authMiddleware(request, {
loginPath: "/api/login",
logoutPath: "/api/logout",
refreshTokenPath: "/api/refresh-token"
// other options...
});
}
export const config = {
// Make sure to include the path in `matcher`
matcher: ["/api/login", "/api/logout", "/api/refresh-token", "/", "/((?!_next|favicon.ico|api|.*\\.).*)"],
};
Calling /api/refresh-token
does two things:
- It checks if the current token is expired. If it is, it regenerates the token and updates the cookies by returning
Set-Cookie
header with fresh token. - It resolves with JSON containing valid
idToken
andcustomToken
getValidIdToken
getValidIdToken
works together with refresh token endpoint to provide latest, valid id token. It can be useful if you use token
to authorize external API calls
It requires serverIdToken
, which is the token
returned by getTokens function inside server components
The function is designed to be fast and safe to use when called multiple times. Thus, the /api/refresh-token
endpoint will only be called if the token has expired.
Example usage:
import {getValidIdToken} from 'next-firebase-auth-edge/lib/next/client';
export async function fetchSomethingFromExternalApi(serverIdToken: string) {
const idToken = await getValidIdToken({
serverIdToken,
refreshTokenUrl: '/api/refresh-token'
});
return fetch("https://some-external-api.com/api/example", {
method: "GET",
headers: {
Authorization: `Bearer ${idToken}`,
},
})
}
getValidCustomToken
getValidCustomToken
works together with refresh token endpoint to provide latest, valid custom token. It can be useful if you use customToken
together with Firebase's signInWithCustomToken (opens in a new tab) method
It requires serverCustomToken
, which is the customToken
returned by getTokens function inside server components
The function is designed to be fast and safe to use when called multiple times. Thus, the /api/refresh-token
endpoint will only be called if the token has expired.
Example usage:
export async function signInWithServerCustomToken(
serverCustomToken: string
) {
const auth = getAuth(getFirebaseApp());
const customToken = await getValidCustomToken({
serverCustomToken,
refreshTokenUrl: '/api/refresh-token'
});
if (!customToken) {
throw new Error('Invalid custom token');
}
return signInWithCustomToken(auth, customToken);
}
Using Firebase Client SDKs
Firebase Client SDK exposes signInWithCustomToken (opens in a new tab) method that allows us to access current user using custom token.
Custom token can be obtained by calling getTokens function in server components
import {signInWithCustomToken} from 'firebase/auth';
import {getValidCustomToken} from 'next-firebase-auth-edge/lib/next/client';
import {doc, getDoc, getFirestore, updateDoc, setDoc} from 'firebase/firestore';
export async function doSomethingWithFirestoreClient(
serverCustomToken: string
) {
const auth = getAuth(getFirebaseApp());
// See https://next-firebase-auth-edge-docs.vercel.app/docs/usage/client-side-apis#getvalidcustomtoken
const customToken = await getValidCustomToken({
serverCustomToken,
refreshTokenUrl: '/api/refresh-token'
});
if (!customToken) {
throw new Error('Invalid custom token');
}
const {user: firebaseUser} = await signInWithCustomToken(auth, customToken);
// Use client-side firestore instance
const db = getFirestore(getApp());
}