Application & framework security

How do I detect the client IP on Firebase?

Firebase sits behind Google proxies, so X-Forwarded-For is spoofable and their published IPs drift. The Arcjet v1 SDK sees FIREBASE_CONFIG and trusts x-fah-client-ip. You do not need a proxies list on Firebase.

6 min read
In short: Firebase sits behind Google proxies, so X-Forwarded-For is spoofable and their published IPs drift. The Arcjet v1 SDK sees FIREBASE_CONFIG and trusts x-fah-client-ip. You do not need a proxies list on Firebase.

How do I detect the client IP on Firebase?

Read the platform header Firebase sets after its own proxy, and only after you know the process is running on Firebase. The Arcjet SDK does that for you: it sees a non-empty FIREBASE_CONFIG, treats the request as Firebase, and uses x-fah-client-ip instead of guessing from X-Forwarded-For.

You do not download Google's published IP list. You do not populate proxies with Firebase or Cloud Run CIDRs. Those lists drift. Platform detection is the v1 path.

import arcjet, { slidingWindow } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
// No proxies entry. FIREBASE_CONFIG selects the Firebase header.
rules: [slidingWindow({ mode: "LIVE", interval: 60, max: 60 })],
});
export async function POST(req: Request) {
const decision = await aj.protect(req);
if (decision.isDenied()) {
return Response.json({ error: "Denied" }, { status: 429 });
}
// Your handler
}

The Go SDK does not read FIREBASE_CONFIG, so name the platform on the client instead. That is the same decision, made explicitly:

aj, err := arcjet.NewClient(arcjet.Config{
Platform: arcjet.PlatformFirebase,
Rules: []arcjet.Rule{
arcjet.SlidingWindow(arcjet.SlidingWindowOptions{
Mode: arcjet.ModeLive,
Interval: time.Minute,
MaxRequests: 60,
}),
},
})

Platform also accepts PlatformFlyIo, PlatformVercel, PlatformRender, PlatformRailway, and PlatformCloudflare. Set it whenever the environment variables a platform normally exposes are absent, which is the usual case for a Go service behind a CDN. The Go SDK is pre-release; pin an exact version.

Rate limits, bot rules, and Shield are only as good as the identity they key on. On Firebase the identity before login is the real client IP. Getting that IP wrong means one attacker shares a budget with a whole region, or bypasses the budget by spoofing X-Forwarded-For. The same platform-header pattern is how Fly exposes Fly-Client-IP; see deploy an Arcjet-protected app to Fly.io.

Why can't I trust X-Forwarded-For on Firebase?

Every hop in front of your function can append to X-Forwarded-For. The client can also send the header already populated. MDN's rule is: trust only the suffix that your proxies added. Firebase App Hosting puts a CDN, load balancers, and Cloud Run in that chain. The two proxy addresses Arcjet observed in testing changed without notice.

If you take the left-most IP, you take whatever the client wrote. If you take the right-most IP, you often get a Google or Fastly hop, not the browser. Neither is a stable client identity.

proxies in v1 still works the way it did in earlier SDKs. You pass IPs or CIDRs. Arcjet walks X-Forwarded-For from the right and skips addresses you listed.

const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [],
proxies: [
"203.0.113.100", // One hop you control
"203.0.113.0/24", // A range you control
],
});

Use proxies for a load balancer you run. Do not use it as your Firebase strategy. You would be chasing Google's published ranges.

Should I maintain a static list of Google proxy IPs?

No, not as the primary mechanism. Google publishes ranges at https://www.gstatic.com/ipranges/goog.json and cloud.json. Those files change as Google adds and retires prefixes. A serverless function that fetches them on every cold start adds latency and a dependency on gstatic.com. A list bundled in the SDK is stale the day after Google publishes a new prefix, and it is only current if you upgrade the package.

Static Google IP listPlatform detection
What you trust

Published CIDRs, then the remaining X-Forwarded-For hop

FIREBASE_CONFIG is set, then x-fah-client-ip

Spoof resistance

Good only while the list is complete. A new Google prefix that you do not yet trust can surface the wrong hop

The header is ignored unless the process is on Firebase. A client cannot set FIREBASE_CONFIG

Operational costFetch on boot, or ship SDK updates whenever Google republishesNone. The platform injects the env var and the header
Serverless fitPoor. Cold starts repeat the downloadGood. No extra I/O
When to use itA proxy you control, with stable addressesFirebase App Hosting and Firebase Functions

The proxies option remains the right tool for your reverse proxy. It is the wrong tool for a CDN whose ranges Google updates without paging you.

How does platform detection work?

Arcjet looks at environment variables the host sets. A non-empty FIREBASE_CONFIG means Firebase. FLY_APP_NAME means Fly. VERCEL means Vercel. RENDER means Render. You do not set these in .env.local. If you copy FIREBASE_CONFIG into a laptop env file, you would trust x-fah-client-ip on traffic that never passed Firebase.

The header is only safe after that check. Anyone can send x-fah-client-ip: 1.2.3.4. Without platform confirmation that value is attacker-controlled.

You can also force the lookup with @arcjet/ip if you need the address outside protect():

import ip from "@arcjet/ip";
export function clientIpFor(request: Request) {
return ip(request, { platform: "firebase" });
}

The main SDK already does this when it detects Firebase. You need the explicit platform option only when the env var is missing (unusual on Firebase) or when you call @arcjet/ip yourself.

How do Firebase App Hosting and Cloud Functions differ?

Both can set FIREBASE_CONFIG. Both sit behind Google infrastructure. The hop count and the header you can trust are not the same.

Firebase App HostingCloud Functions (gen 2) / Cloud Run
Typical stackCDN + Cloud Run serving a Next.js or Angular appCloud Run serving a single function
Client IP header

x-fah-client-ip (App Hosting). Confirmed by Firebase support; treat it as the platform header, not a public contract you parse yourself

No App Hosting header. Cloud Functions documents X-Forwarded-For. The right-most hop is Cloud Run, not the browser, if a CDN sits in front

Arcjet behavior

Detects Firebase and uses the platform header. No proxies list

Same detection when FIREBASE_CONFIG is present. Prefer the SDK over hand-parsing XFF

Direct URL risk

The Cloud Run URL is not the public entry. Clients should hit App Hosting

If callers can hit the *.run.app URL, they skip the CDN and can spoof CDN-only headers

App Hosting is the Next.js-shaped product (framework-aware builds, CDN, Cloud Run). Functions is still the right place for background HTTP and event handlers. Install @arcjet/next or @arcjet/node in either. Call protect() in the handler. For algorithm choice once the IP is correct, use the rate limiting guide.

If a dashboard decision shows a Google IP or an empty IP, the SDK did not detect Firebase or the header was absent. Check FIREBASE_CONFIG in the function environment (Firebase console or firebase functions:secrets:access is the wrong tool; print Boolean(process.env.FIREBASE_CONFIG) from a debug route you remove before production). Do not invent a proxies list to paper over a missed detection.

Frequently asked questions

How do I detect the client IP on Firebase?

Let the SDK detect a non-empty FIREBASE_CONFIG and read x-fah-client-ip. Do not parse the left-most X-Forwarded-For hop and do not bundle goog.json.

Does the v1 proxies option still work?

Yes. Pass IPs or CIDRs for a load balancer you control. Do not use proxies as your Firebase strategy. Google's published ranges change without notice.

Why is a static Google IP list a bad fit for Functions?

A cold start that fetches goog.json adds latency. A list shipped in the SDK is stale until you upgrade. Platform detection needs no extra I/O.

How do App Hosting and Cloud Functions differ for client IP?

App Hosting adds a CDN and sets x-fah-client-ip. Functions gen 2 on Cloud Run document X-Forwarded-For; the right-most hop is often Cloud Run, not the browser. Arcjet uses FIREBASE_CONFIG on both.

Can a client spoof x-fah-client-ip?

They can send the header. Arcjet ignores it unless FIREBASE_CONFIG is set by the platform. Do not copy FIREBASE_CONFIG into a laptop .env file.

Application security in your code

Protect your application with Arcjet

Get rate limits, bot detection, and attack blocking in your request handlers.