I'm currently using Firebase to authenticate my users in a React/Node app, but I also want to store additional user data in my own database and I'm doing so by storing the Firebase uid on each user and I wanted to get some input on my implementation to make sure I'm on the right track.
My frontend code is as follows:
- This is used as an
onClickon a "Continue with Google" button:
const googleSignIn = async () =>
signInWithPopup(auth, new GoogleAuthProvider());
- When the above popup promise completes,
auth.onAuthStateChangedis triggered in the followinguseEffect, which (on login/signup) would trigger the functionapplicationAuthentication, passing in the user object returned from Firebase:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(firebaseUser => {
if (!firebaseUser) {
return dispatch(logUserOut());
}
return applicationAuthentication(firebaseUser);
});
return unsubscribe;
}, []);
- The
applicationAuthenticationlooks as follows:
const applicationAuthentication = async (firebaseUser: User) => {
try {
const idToken = await firebaseUser.getIdToken();
const { data } = await axios.get('/api/users/authenticate/signin', {
headers: {
Authorization: `Bearer ${idToken}`
}
});
const { user, error } = data;
if (error) {
throw new Error(error.message);
}
dispatch(logUserIn({ user, accessToken: idToken }));
} catch (error: any) {
dispatch(setUserError(error.message));
console.log(error.message);
}
};
In my node express server, the following happens at the route /api/users/authenticate/signin; this is where I communicate with my own database by using the data access methods findUserByFirebaseUID and createUser using the uid from the token to check if the user exists, and if not, creating a new one (note the middleware that's checked first as noted below):
usersRouter.get(
'/authenticate/signin',
async (req: Request, res: Response, next: NextFunction) => {
try {
const uid = res.locals.uid; // set by token middlewear function
let firstLogin = false;
let user = await findUserByFirebaseId(uid);
if (!user) {
firstLogin = true;
user = await createUser(uid);
}
res.json({ user, firstLogin });
} catch (error) {
next(error);
}
}
);
Which uses the following authenticate middleware function to authenticate the user with firebase-admin:
const authenticate = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const idToken = req.headers.authorization.split(' ')[1];
const decodedToken = await admin.auth().verifyIdToken(idToken);
if (decodedToken) {
const { uid } = decodedToken;
res.locals.uid = uid;
return next();
}
return res.status(400).json({ message: 'Unauthorized Request' });
} catch (error) {
next({ message: 'Invalid Token' });
}
};
app.use(authenticate);
Does this overall flow of using the uid to check my own database seem correct? And am I implementing the token middleware correctly?
I'd love to hear any thoughts on this!
CodePudding user response:
Yes, the way you pass the ID token from the client to the server, and then decode it (in the middleware) on your server to securely determine the UID is similar to how Firebase's own services do this.
If you pass the ID token to other requests to to authorize them, consider keeping a cache of recent raw and decoded ID tokens, to prevent having to decode them on each request.
