Theory
Who are you, and can you do this?
FestConnect must know who each user is (authentication) and protect actions only certain users may take. Building secure login yourself, hashing passwords, managing sessions, is hard and easy to get dangerously wrong. So FestConnect uses Firebase Authentication to handle it.
Firebase manages sign-up and login; on success it hands the app a token proving who the user is. The Angular front end sends that token with requests, and the Express backend verifies it before allowing protected actions. This closing lesson covers that flow, the security backbone of the app.
Theory
Firebase Auth and the token (a JWT)
Firebase Authentication provides ready-made sign-up and login (email and password, among others), so you never store raw passwords yourself, Firebase does it securely.
When a user logs in, Firebase issues the client an ID token, which is a JWT (JSON Web Token): a compact, signed token that carries the user's identity. Because it is signed with a secret only Firebase and your server trust, it cannot be forged or tampered with without detection. The Angular app receives this token on login and will send it along with each request to prove the user's identity. Think of the JWT as a tamper-proof ID card the user carries after logging in.
Practical
Angular: register, log in, and send the token
// Using the Firebase Auth SDK in Angular
import { getAuth, createUserWithEmailAndPassword,
signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
// register
await createUserWithEmailAndPassword(auth, email, password);
// login
const cred = await signInWithEmailAndPassword(auth, email, password);
const token = await cred.user.getIdToken(); // the JWT
// send it with an API request so the server can verify who you are:
this.http.get('/api/profile', {
headers: { Authorization: `Bearer ${token}` },
}).subscribe(/* ... */);Practical
Express: verify the token before serving a protected route
const admin = require('firebase-admin'); // Firebase Admin SDK
// middleware: verify the token, or reject with 401
async function requireAuth(req, res, next) {
const header = req.headers.authorization || '';
const token = header.replace('Bearer ', '');
try {
req.user = await admin.auth().verifyIdToken(token); // trust only if valid
next();
} catch {
res.status(401).json({ error: 'Not authenticated' });
}
}
app.get('/api/profile', requireAuth, (req, res) => res.json(req.user));This example runs in Gri-Learn on the web, where you can edit it and see the output.
Formula
Never trust the client; always verify on the server
The golden rule of security here: the front end can claim anything, so the server must verify. Anyone can alter what runs in a browser, so a request saying 'I am the admin' means nothing until the server checks the token.
That is exactly what the Firebase Admin SDK does on the Express side: verifyIdToken(token) cryptographically confirms the token is genuine and unexpired before the protected route runs. A missing or invalid token is rejected (401). Hiding a button in the UI is not security; verifying the token on the server is. Guard every protected route this way.
Quiz
After a user logs in via Firebase, how does the Express backend know a request to a protected route really comes from that authenticated user?
- It trusts whatever the Angular app claims in the request
- It verifies the ID token (JWT) sent with the request using the Firebase Admin SDK (verifyIdToken); an invalid token is rejected
- It checks whether the button was visible in the UI
- It cannot know; any request is allowed
Show the answer
It verifies the ID token (JWT) sent with the request using the Firebase Admin SDK (verifyIdToken); an invalid token is rejected
The backend verifies the ID token (a signed JWT) that the client sends with the request, using the Firebase Admin SDK's verifyIdToken, which cryptographically confirms the token is genuine and identifies the user; a missing or invalid token is rejected with 401. Option A violates the core rule, never trust the client's claims, because anyone can forge a request body, only a verified token proves identity. Option C is a UI detail, not security: hiding a button does not stop someone crafting the request directly, so it proves nothing. Option D is wrong: with token verification the server absolutely can (and must) establish who is calling. Verify the token on the server; do not trust client claims.
Think first
Why is a signed JWT trustworthy when it travels through the untrusted browser?
The token passes through the user's browser, which could be tampered with. Why can the server still trust it? Then tap.
Show the answer
Because the JWT is cryptographically SIGNED, so any tampering breaks the signature and the server detects it, the token's trustworthiness comes from the maths, not from trusting the browser. When Firebase issues an ID token, it signs the token's contents (who the user is, when it expires) with a secret key. The server, using the Firebase Admin SDK, checks that signature against Firebase's public keys during verifyIdToken. If even one character of the token, say, changing the user id to 'admin', is altered in the browser, the signature no longer matches the (now different) contents, and verification FAILS, so the forged token is rejected. An attacker cannot produce a valid signature for altered contents because they do not have the secret signing key; only Firebase can create genuinely signed tokens. This is why it is safe to let the token travel through the untrusted browser and be sent by the client: the server is not trusting the browser to be honest, it is mathematically verifying that the token was issued by Firebase and has not been changed. The verification also checks EXPIRY, so old tokens stop working, limiting the damage if one leaks. This is the whole point of signed tokens: trust is established by cryptographic proof, not by trusting the transport. A broken signature is a rejected request, which is why 'verify the token' is real security while 'the browser said so' is not. Signatures make the token tamper-evident, so the server can trust it even from an untrusted place.
Summary
Key takeaways
- Firebase Authentication handles user sign-up and login (email/password and more), so you do not store passwords yourself.
- On login, Firebase issues the client an ID token, which is a signed JWT carrying the user's identity.
- The Angular front end sends the token with requests (Authorization: Bearer <token>) to prove who the user is.
- The Express backend verifies the token with the Firebase Admin SDK (verifyIdToken) before serving a protected route; invalid means 401.
- The golden rule: never trust the client's claims; always verify on the server.
- A signed JWT is trustworthy through the untrusted browser because tampering breaks its signature and verification (and expiry) catches it.
- Memory hook: Firebase logs users in and issues a JWT; the server verifies it before trusting the request.