Firebase Authentication for Web Apps: Email, Google Sign-In, Tokens and Securing Routes

How do you add login to a web project without building password hashing, sessions, and reset emails yourself? Firebase Authentication handles identity - email/password, Google sign-in, and token issuance - as a managed service. This guide works through the Auth REST API with curl and Python (sign up, sign in, token refresh), verifying ID tokens to actually secure routes, security rules vs backend verification, custom claims for roles, and the pre-viva auth checklist.

Written by Projectech16 min readPublished
For B.E./B.Tech Computer Science and IT final-year students adding login to web projects Topics: Firebase Authentication, REST API, Python requests, JWT, OAuth, Firestore
Illustration of a login screen on a laptop with a glowing key, user profile avatars, shield and fingerprint motifs over cloud symbols.
Illustration generated for this guide.
In this guide

Nearly every final-year web project needs login: a quiz app needs to know whose score it is recording, a lost-and-found portal needs to know who posted the item, an admin panel needs to know who is allowed in. Building authentication yourself means password hashing, session storage, password-reset emails, and protecting against the standard attacks — weeks of security-sensitive work that has nothing to do with your project's actual idea. Firebase Authentication gives you email/password login, Google sign-in, and session handling as a managed service, and a working login flow is an afternoon's work instead of a month's.

This guide shows the Firebase Auth setup through the console, then works entirely with the Firebase Auth REST API using Python requests and curl — no JavaScript SDK snippets to paste blindly. That is deliberate: the REST API exposes exactly what is happening (exchange credentials for tokens, verify tokens server-side), which is what you need to understand to secure routes properly and to explain the design in your viva. It pairs naturally with the guide on connecting an IoT project to the cloud, which covers Firebase's database side.

What Firebase Auth gives you (and what it does not)

Firebase Authentication handles identity: who the user is. It stores user accounts, verifies passwords, manages email verification and password resets, issues signed ID tokens proving "this request is from user X", and handles OAuth flows for Google, GitHub, and other providers. The free tier (Spark plan) covers email/password and most social providers at no cost — phone-number authentication has a free quota (10,000 verifications per month on the free tier) and is the one to watch if your project uses OTP login.

What it does not do: it does not decide what user X is allowed to do. That is authorization, and it is your job — enforced through Firebase Security Rules (if you use Firestore/Realtime Database) or by verifying ID tokens in your own backend. Students routinely confuse the two: "I added Firebase Auth so my data is secure" is false until the rules or the backend actually check the token. This guide treats that distinction as the main event.

Setup: console steps

  1. Create a project at the Firebase console. The project ID becomes part of your API endpoints — pick something readable.
  2. In Build → Authentication → Sign-in method, enable Email/Password (and Google if you want one-click sign-in — enable it now even if you wire the button later; enabling is free and takes a minute).
  3. In Project Settings → General, find the Web API Key. This key identifies your Firebase project to Google's servers. It is meant to be public — it ships in client apps — but restrict it in the Google Cloud console to your domains and to the Identity Toolkit API, so a leaked key cannot be reused elsewhere for other Google services.
  4. Note the Auth domain (your-project.firebaseapp.com) — you will need it for OAuth redirect configuration.

That is the whole console setup. Everything below is API calls you can run from your laptop to understand the flow before writing a line of frontend code.

The REST API: sign up and sign in

Firebase's Identity Toolkit REST API is the honest view of authentication: you POST credentials, you get tokens back. Try it with curl first — seeing the raw exchange makes the token model concrete:

curl -X POST "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@college.edu","password":"test-password-123","returnSecureToken":true}'

The response contains three things that matter:

{
  "idToken": "eyJhbGciOi...",
  "refreshToken": "AEu4IL2...",
  "expiresIn": "3600",
  "localId": "abc123...",
  "email": "demo@college.edu"
}
  • idToken — a signed JWT proving this user authenticated. Expires in 3600 seconds (one hour). Your backend verifies this on every protected request.
  • refreshToken — long-lived; exchanges for a fresh ID token when the hour expires, without asking for the password again.
  • localId — the user's UID. Use this as the key for the user's data in your database, not the email (emails change; UIDs do not).

Signing in is the same shape against a different endpoint:

curl -X POST "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@college.edu","password":"test-password-123","returnSecureToken":true}'

And refreshing an expired ID token:

curl -X POST "https://securetoken.googleapis.com/v1/token?key=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"grant_type":"refresh_token","refreshToken":"YOUR_REFRESH_TOKEN"}'

Do this once by hand. When your frontend later "just calls the SDK", you will know it is performing exactly these three exchanges — and when login breaks, you will debug the exchange instead of staring at the UI.

The same flow in Python

For a Python backend (Flask/Django) or a quick test script, the requests version is clearer than curl:

import requests

API_KEY = "YOUR_API_KEY"
BASE = "https://identitytoolkit.googleapis.com/v1"

def sign_up(email, password):
    resp = requests.post(
        f"{BASE}/accounts:signUp",
        params={"key": API_KEY},
        json={"email": email, "password": password, "returnSecureToken": True},
    )
    resp.raise_for_status()
    return resp.json()  # idToken, refreshToken, localId, expiresIn

def sign_in(email, password):
    resp = requests.post(
        f"{BASE}/accounts:signInWithPassword",
        params={"key": API_KEY},
        json={"email": email, "password": password, "returnSecureToken": True},
    )
    resp.raise_for_status()
    return resp.json()

def refresh(refresh_token):
    resp = requests.post(
        "https://securetoken.googleapis.com/v1/token",
        params={"key": API_KEY},
        json={"grant_type": "refresh_token", "refreshToken": refresh_token},
    )
    resp.raise_for_status()
    return resp.json()["id_token"]

Keep the API key in an environment variable in real code, never hardcoded in a file you commit — and if your project uses Git, the Git and GitHub guide covers keeping secrets out of repositories.

Verifying ID tokens: the step that makes routes actually secure

The frontend sends the ID token with each request (conventionally as an Authorization: Bearer <token> header). Your backend must verify it — not just check that it exists. Verification means: the token's signature is valid (signed by Google, for your project), it has not expired, and the audience matches your project ID. The Firebase Admin SDK does this in a few lines per language; conceptually it fetches Google's public keys, checks the signature, and returns the decoded claims including the UID.

The request lifecycle for a protected route is therefore:

  1. Client authenticates once → holds ID token (1-hour life) + refresh token.
  2. Client calls your API with Authorization: Bearer <idToken>.
  3. Your backend verifies the token, extracts the UID, and uses it to scope the data: "return the quizzes created by UID abc123", not "return all quizzes".
  4. When the ID token expires, the client refreshes it silently with the refresh token and retries.

The failure mode to internalise: a backend that trusts a UID sent in the request body ("here are my quizzes, my UID is abc123") without verifying the token is not authenticated at all — anyone can send any UID. For a project like the online quiz maker with live leaderboard, this is the difference between "scores are trustworthy" and "anyone can submit any score as any user". Verify the token, derive the UID from it, never accept it from the client.

Securing routes: the decision table

Approach How it works Use when
Firebase Security Rules (Firestore / Realtime DB) Rules like "allow read/write only if request.auth.uid matches the document owner" evaluated by Google on every request Your app reads/writes Firebase databases directly from the frontend — no custom backend
Backend token verification Your server verifies the ID token per request (Admin SDK) and applies its own authorization logic You have a Node/Python/PHP backend with business rules (roles, admin panels, complex queries)
Custom claims Admin SDK stamps roles (e.g. admin: true) onto the token; rules or backend check the claim Role-based access: admins vs students vs faculty in a campus lost-and-found portal
Anonymous auth upgrade Users start anonymous, link an email/Google account later without losing data Try-before-signup flows — let users build something, then ask them to register to keep it

For most final-year projects, the honest architecture is one of the first two rows, not both: either go all-in on Firebase (Auth + Firestore + Security Rules, minimal backend) or use Firebase Auth purely as the identity provider in front of your own backend. Mixing both without a clear boundary is where security holes breed.

A minimal Firestore rule that enforces ownership looks like this in concept: allow reads and writes on a user's document collection only when the request's authenticated UID equals the document's owner field. Write it, then test it with the rules playground in the console using an unauthenticated request — the request must be denied. If you cannot demonstrate a denied request, you have not tested security.

Google sign-in: what changes

Enabling Google as a provider does not change the token model — after the OAuth flow, you still get an ID token and a UID. What changes is UX (one click, no password to forget) and account linking: if a user first signs up with email and later uses Google with the same address, Firebase can link them into one account instead of creating a duplicate. Two practical notes: configure the OAuth consent screen in the Google Cloud console (it shows your project name to users — set it properly, not the auto-generated placeholder), and add your production domain to the authorized domains list in the Firebase console, or the OAuth redirect will be rejected on the deployed site while working fine on localhost.

Session handling on the frontend: the pattern

However you build the frontend, the session pattern is the same:

  • On app load, check for a stored session (the SDKs persist it in browser storage automatically). Show a loading state — not the login page — while the check resolves, or logged-in users will see the login screen flash on every refresh.
  • Protect routes by checking authentication state before rendering, and redirect unauthenticated users to login. But remember: frontend route guards are UX, not security. The real enforcement is the token verification on the backend or the security rules on the database. A user who bypasses your JavaScript route guard and calls the API directly must still be stopped server-side.
  • Handle the expired-token case: API call returns 401 → refresh the token → retry the call once → if that fails, send the user to login. Implement this in one shared API wrapper, not scattered across every call.
  • Sign-out clears the local session and, if you keep server-side sessions, tells the backend to invalidate them.

Email verification and password reset: do not skip these

Firebase provides both as managed flows: send a verification email after signup, send a password-reset email on request. Wire them in — "email verification" is a checkbox feature that examiners ask about ("how do you know the email is real?"), and password reset is the feature every demo user tries. The implementation is a single API call each; the UI is a "verify your email" banner and a "forgot password" link. Also decide the policy: can unverified users use the app, or is verification required? For a quiz or lost-and-found project, letting users in unverified but gating actions (posting, publishing) on verification is the pragmatic middle ground.

The Admin SDK: roles and user management from your backend

The client-side REST API covers what users do to themselves. The Firebase Admin SDK covers what your backend does to users: creating accounts, disabling them, and stamping roles via custom claims. Custom claims are the clean way to implement roles — set them once from a trusted server environment, and they ride along inside every ID token the user mints afterwards:

import firebase_admin
from firebase_admin import auth, credentials

cred = credentials.Certificate("service-account.json")
firebase_admin.initialize_app(cred)

# Grant the admin role to a user (do this from an admin script, never from the frontend)
auth.set_custom_user_claims("USER_UID_HERE", {"admin": True})

# Later, in your request handler, after verifying the ID token:
decoded = auth.verify_id_token(id_token)
if decoded.get("admin"):
    # allow admin-only operation

The service-account JSON is the master key to your Firebase project — it lives only on the server, never in the frontend, never in Git. Two subtleties students miss: custom claims propagate to the ID token only when a new token is minted (the user must sign out and back in, or refresh, to pick up a new role — a freshly-promoted admin who "still can't access the panel" is almost always holding a pre-promotion token), and claims have a 1000-byte size limit, so store roles (admin, faculty, student), not data.

Testing auth without touching production: the Emulator Suite

Developing against your live Firebase project means every test signup pollutes the production user list and every rule experiment risks real data. The Firebase Emulator Suite runs Auth (and Firestore) locally: start it, point your app at localhost, and create/delete users freely. The workflow:

  1. Install the Firebase CLI, run the emulators for auth (and firestore if you use it).
  2. Configure your app to use the emulator in development (an environment flag — production builds must never point at localhost).
  3. Write a throwaway script that creates five test users (student, faculty, admin, unverified, disabled) and exercise every login path, every role gate, and every error message against them.
  4. Test the security rules against these users with the emulator's rules playground before deploying anything.

This is also how you produce the screenshots for your report: a clean local environment with realistic test accounts, rather than screenshots of your production console with two real users named "test".

Real error codes and what they mean

Error Meaning Fix
EMAIL_EXISTS Account already registered Offer sign-in or password reset instead of showing a raw error
INVALID_PASSWORD / INVALID_LOGIN_CREDENTIALS Wrong credentials (Firebase deliberately returns a generic code to avoid revealing which half was wrong) Show "Incorrect email or password" — do not distinguish
TOO_MANY_ATTEMPTS_TRY_LATER Rate-limited after repeated failures Back off; this is brute-force protection working as intended
USER_DISABLED Account disabled in the console Check the Users panel; re-enable or handle gracefully
TOKEN_EXPIRED / 401 from your backend ID token older than one hour Refresh with the refresh token and retry once
API key not valid Key restricted or wrong project Check the key in Project Settings; verify API restrictions in the Cloud console allow the Identity Toolkit API
OAuth redirect rejected on production Domain not in authorized domains Add the production domain in Authentication → Settings → Authorized domains

Auth checklist before the viva

  • Email/password signup, login, logout, and password reset all work on the deployed URL, not just localhost (OAuth authorized domains updated).
  • Google sign-in works on the deployed URL; the consent screen shows the real project name.
  • Protected backend routes reject requests with missing, malformed, and expired tokens (test all three with curl — send no header, send garbage, send an old token).
  • Security rules (if using Firestore directly) deny an unauthenticated read and a cross-user write — demonstrated, not assumed.
  • The UID used for data scoping comes from the verified token, never from a client-supplied field.
  • API key is restricted by HTTP referrer/domain and to the Identity Toolkit API in the Cloud console.
  • Service-account JSON is on the server only; not in Git, not in the frontend bundle, not in the report PDF.
  • Unverified-email policy decided and implemented; "forgot password" link present and working.
  • The expired-token → refresh → retry flow works (wait an hour or mint a short-lived test token; do not discover this during the demo).

Putting it together

The afternoon version: enable Email/Password (and Google) in the console, run the curl signup/signin/refresh cycle once to see the token exchange, wire the frontend to obtain and store tokens, verify ID tokens on every protected backend route (or write ownership-based security rules if you are all-in on Firebase), add email verification and password reset, and restrict the API key. That is a complete, defensible authentication system — the kind that lets your viva focus on what your project does, because nobody has to ask whether the login is real. More web project concepts that need exactly this are in the Web Development branch hub.

Passwordless sign-in: the email-link option

Firebase also supports passwordless login: the user enters their email, receives a link, clicks it, and is signed in — no password to choose, forget, or leak. For a student project this has real appeal: no password storage concerns at all, and the demo never stalls on "I forgot the test password". The flow via the REST API:

  1. Your app calls the accounts:sendOobCode endpoint with requestType: "EMAIL_SIGNIN" and the email address. Firebase sends the sign-in link.
  2. The user clicks the link, which lands on your site with the sign-in code in the URL.
  3. Your app completes sign-in with the code, receiving the same ID token / refresh token pair as password login.

Two practical notes: the link must be configured to open in the same browser/device context (continuing sign-in on a different device fails unless you handle cross-device completion), and email deliverability matters — test with real college email addresses, since aggressive spam filters eat sign-in emails. Passwordless is an excellent fit for low-friction projects (event registrations, feedback portals); for anything where users expect a traditional account, offer it alongside email/password rather than instead of it.

Multi-factor authentication: the one-paragraph version

Firebase supports multi-factor authentication (typically a second factor via SMS or authenticator app after the primary sign-in). For most final-year projects this is beyond scope — but if your project handles anything sensitive (an admin panel controlling IoT hardware, a billing system like the inventory and GST billing project), enabling MFA for admin accounts specifically is a defensible, high-value addition. The pattern: require MFA only for the admin role (via custom claims), not for every student user — security where it matters, convenience everywhere else. Mention it in the report's security section even if you do not implement it; naming the option shows you thought about threat models.

One-line summary for the report

"Authentication is provided by Firebase Authentication (email/password and Google OAuth); the backend verifies the Firebase ID token on every protected request and derives the user identity from the verified token; role-based access uses Firebase custom claims." One sentence, three verifiable claims, zero hand-waving — exactly what the authentication section of a project report should sound like.

More project guides

More in Web Development