Closed Jkense closed 2 months ago
I have this question too. My idea is to setup an useEffect on the AuthProvider like this:
export function AuthProvider({ user, children }: Props) {
const [clientUser, setClientUser] = useState<User | null>(null);
useEffect(() => {
if (user?.customToken) {
signInWithCustomToken(auth, user.customToken).then(user => {
setClientUser(user.user);
});
}
}, [user?.customToken]);
// ....
}
Then i ensure that the the logged-in part of my app can only be reachable when clientUser
is defined
This works, but I'm not really sure if it makes any sense.
signInWithCustomToken(auth, user.customToken).then(user => { setClientUser(user.user); });
I'm having some problems with it. I have no idea on how to set the custom tokens in my app:
useEffect(() => {
setIsLoadingApp(true);
const unsubscribe = onAuthStateChanged(auth, async (userSigned) => {
if (!userSigned) {
setUser(null);
await fetch("/api/logout", {
method: "GET",
});
return;
}
const userData = await getUserData(userSigned, setPremiumCoins);
if (userData) {
setUser(userData);
firebaseLoginRedirect(userSigned);
if (analytics) {
setUserId(analytics, userData.id);
}
}
});
return () => unsubscribe();
}, []);
If I use setPersistence(auth, inMemoryPersistence) the app does not work
Hey @Jkense,
If your app makes a lot of database operations using Firebase Client SDK you should consider two approaches:
signInWithCustomToken
once, when the app first renders, and then rely on getFirebaseAuth().currentUser
setPersistence(auth, inMemoryPersistence)
to let Firebase store user credential in the browser. This may lead to consistency issues between server and client tokenI would go with 1.
approach. See official docs on how and when to use signInWithCustomToken
: https://firebase.google.com/docs/auth/web/custom-auth#authenticate-with-firebase
Hey @Gabrimarin,
You're approach looks correct, but you most likely don't need to store clientUser
in state.
Once you call signInWithCustomToken
user will be stored in getAuth().currentUser
for the whole session. I might miss some context though, so let me know if that works
Hey @rossanodr,
You should not use onAuthStateChanged
together with setPersistence(auth, inMemoryPersistence)
, unless you expect to handle null
on each page refresh.
You can follow two approaches mentioned above:
- getFirebaseAuth()
Thanks for the response.
getFirebaseAuth(authServerConfig).currentUser leads to the error Property 'currentUser' does not exist on type '{ verifyAndRefreshExpiredIdToken: (customTokens: CustomTokens, verifyOptions?: VerifyOptions | undefined) => Promise
I'm using "next-firebase-auth-edge": "^1.7.0-canary.2",
@rossanodr you're using getFirebaseAuth
from next-firebase-auth-edge
, which is a collection of advanced methods
I am referring to client-side getFirebaseAuth
from starter example: https://github.com/awinogrodzki/next-firebase-auth-edge/blob/ce1d5f4edcb6196f61abb1d98e2afa6d6d992aa0/examples/next-typescript-starter/app/auth/firebase.ts#L25
@rossanodr you're using
getFirebaseAuth
fromnext-firebase-auth-edge
, which is a collection of advanced methodsI am referring to client-side
getFirebaseAuth
from starter example:
thanks man you're amazing
@awinogrodzki When my token expires and I call getTokens() to get customToken, I get null, making it impossible to use getValidCustomToken() which requires a custom token as the input.
const customToken = await getValidCustomToken({ serverCustomToken, refreshTokenUrl: '/api/refresh-token' });
Hey @huangfulei!
What version of next-firebase-auth-edge
are you using? Custom tokens have been introduced since v1.6.0
Could you clear your browser cookies and login again? Let me know if it solved the issue.
Hey @huangfulei!
What version of
next-firebase-auth-edge
are you using? Custom tokens have been introduced sincev1.6.0
Could you clear your browser cookies and login again? Let me know if it solved the issue.
I'm using 1.7.0-canary.2 reproduce steps:
is that expected? or do I miss anything?
The behaviour you described is expected. When you set maxAge
to 5 seconds, browser will remove authentication cookies shortly after that time passes. Authentication cookies contain custom token, so if they are removed, getTokens
no longer has access to the token and returns null
The behaviour you described is expected. When you set
maxAge
to 5 seconds, browser will remove authentication cookies shortly after that time passes. Authentication cookies contain custom token, so if they are removed,getTokens
no longer has access to the token and returnsnull
@awinogrodzki any idea why this always returns the same existing custom token for me? const customToken = await getValidCustomToken({ serverCustomToken: user.customToken, refreshTokenUrl: "/api/refresh-token", });
I have /api/refresh-token endpoint configured in middleware, and no matter I pass a valid or expired customToken to it, it always returns the same value back to me.
Hey @huangfulei,
You can look at getValidCustomToken
function for clues:
export async function getValidCustomToken({
serverCustomToken,
refreshTokenUrl,
checkRevoked
}: GetValidCustomTokenOptions): Promise<string | null> {
// If serverCustomToken is empty, we assume user is unauthenticated and token refresh will yield null
if (!serverCustomToken) {
return null;
}
const token = customTokenCache.get(serverCustomToken);
const payload = decodeJwt(token);
const exp = payload?.exp ?? 0;
if (!checkRevoked && exp > Date.now() / 1000) {
return serverCustomToken;
}
const response = await fetchApi<{customToken: string}>(refreshTokenUrl);
if (!response?.customToken) {
throw new AuthError(
AuthErrorCode.INTERNAL_ERROR,
'Refresh token endpoint returned invalid response. This URL should point to endpoint exposed by the middleware and configured using refreshTokenPath option'
);
}
customTokenCache.set(serverCustomToken, response.customToken);
return response.customToken;
}
Basically, it will only fetch new custom token, if exp
claim of the current token is greater than current timestamp
You can use sites like jwt.io to debug your custom token and check what is the exp
claim of the token
and no matter I pass a valid or expired customToken to it, it always returns the same value back to me.
Both getValidCustomToken
and Refresh Token endpoint will return new token only if the previous one is expired. The expiration is checked against exp
claim.
What do you mean by expired customToken
? Is the exp
claim in the past, and you still receive old token?
@awinogrodzki I found the issue. the maxAge in the cookieSerializeOption is only for the id token, not the custom token. the custom toke is always expired in 1 hour, and if I set the maxAge for id token to less than 1 hour, then I would not be able to refresh the id token
@huangfulei maxAge
controls the lifetime of cookies rather than lifetime of token. Both id and custom tokens are valid for 1 hour. Currently, there is no way of changing the expiration date of those tokens. It is the same behaviour as observed in official firebase-admin library.
if I set the maxAge for id token to less than 1 hour, then I would not be able to refresh the id token
Yes, that is correct. This is expected behaviour. Middleware should not have access to refresh token after maxAge
has passed.
maxAge
describes the maximum duration of user session. If you set it to, let's say, 30 minutes, you should expect that user is logged out after 30 minutes, so refreshing token after 30 minutes should not be possible.
I will close the issue now, as I feel every question has been answered. Feel free to open new issues if otherwise. Cheers 🎉
I wrote this function to follow the pattern described in the starter example for client side code:
My current application is build with loads of database operations, it seems silly if I have to add this as a line at everyone of those operations. It that indeed the case? I am unsure if I am completely understanding the deeper functionality.