Skip to content

Identity Platform / Firebase Auth

floci-gcp emulates the Google Identity Toolkit API (identitytoolkit.googleapis.com v1) wire-compatibly with the official Firebase Auth emulator: same paths, same unsigned JWTs, same error strings. Anything that works against firebase emulators:start --only auth is the compatibility target.

Configuration

Variable Default Description
FLOCI_GCP_SERVICES_FIREBASEAUTH_ENABLED true Enable/disable Firebase Auth

Endpoint

Set FIREBASE_AUTH_EMULATOR_HOST=localhost:4588. Firebase SDKs then prefix requests with the API hostname as a path, which floci-gcp serves directly on its single port:

Method Path
POST /identitytoolkit.googleapis.com/v1/accounts:signUp | :signInWithPassword | :signInWithCustomToken | :lookup | :update | :delete (client, ?key= any non-empty API key)
POST /identitytoolkit.googleapis.com/v1/projects/{project}/accounts[:lookup\|:update\|:delete\|:batchDelete] (admin, Authorization: Bearer owner)
GET /identitytoolkit.googleapis.com/v1/projects/{project}/accounts:batchGet (admin listUsers)
POST /identitytoolkit.googleapis.com/v1/projects/{project}:createSessionCookie (admin, Authorization: Bearer owner)
POST /securetoken.googleapis.com/v1/token (refresh; JSON or form-urlencoded)
DELETE /emulator/v1/projects/{project}/accounts (test helper: delete all users)

Tokens

  • ID tokens are unsigned JWTs (alg: none, empty signature) with the claims the Admin SDK's emulator-mode verifier checks: iss=https://securetoken.google.com/{project}, aud={project}, sub/user_id, auth_time, firebase.{identities, sign_in_provider}. Custom claims (from setCustomUserClaims or custom-token claims) are merged into the payload.
  • Refresh tokens follow the emulator's base64-JSON record format; POST /securetoken.../token with grant_type=refresh_token re-issues tokens.
  • Token revocation: revokeRefreshTokens sets validSince; tokens with iat < validSince fail with TOKEN_EXPIRED (wall-clock exp is intentionally not enforced, like the emulator).
  • Custom tokens: Admin SDK JWTs (signed or unsigned) and the emulator's strict-JSON form ({"uid": "...", "claims": {...}}) are both accepted.
  • Session cookies: createSessionCookie re-issues a verified ID token's payload with a fresh iat/exp window and iss=https://session.firebase.google.com/{project}. validDuration is in seconds and must fall in [300, 1209600] (5 minutes – 14 days), else INVALID_DURATION. Like the official emulator, the value is first put through JavaScript Number() coercion and then || MAX, so anything coercing to NaN or 0 (absent, 0, false, "", or a string such as "3600s") silently falls back to the 14-day maximum, while anything coercing to another number (0.5, true, "0x10") reaches the range check and is rejected there. Radix prefixes ("0x1000"), exponent notation ("3.6e3") and ECMAScript whitespace (which includes NBSP) are read the way Number() reads them. There is no verification endpoint as the Admin SDKs verify session cookies locally, and checkRevoked goes through accounts:lookup.

Quick Start

// export FIREBASE_AUTH_EMULATOR_HOST=localhost:4588
FirebaseApp app = FirebaseApp.initializeApp(FirebaseOptions.builder()
        .setProjectId("floci-local")   // the emulator's default project
        .setCredentials(new EmulatorCredentials())
        .build());
FirebaseAuth auth = FirebaseAuth.getInstance(app);

auth.createUser(new UserRecord.CreateRequest()
        .setUid("alice").setEmail("alice@example.com").setPassword("secret123"));
auth.setCustomUserClaims("alice", Map.of("role", "admin"));
String customToken = auth.createCustomToken("alice");
// client exchanges it at accounts:signInWithCustomToken, then:
FirebaseToken decoded = auth.verifyIdToken(idToken);

// server-rendered apps: swap the ID token for an HttpOnly session cookie
String cookie = auth.createSessionCookie(idToken,
        SessionCookieOptions.builder().setExpiresIn(TimeUnit.HOURS.toMillis(1)).build());
FirebaseToken session = auth.verifySessionCookie(cookie, true);
# client sign-up (any non-empty API key)
curl -X POST 'http://localhost:4588/identitytoolkit.googleapis.com/v1/accounts:signUp?key=fake-api-key' \
  -H 'Content-Type: application/json' \
  -d '{"email":"alice@example.com","password":"secret123"}'

# refresh
curl -X POST 'http://localhost:4588/securetoken.googleapis.com/v1/token?key=fake-api-key' \
  -d 'grant_type=refresh_token&refresh_token=<refreshToken>'

# session cookie (validDuration is seconds, not a duration string)
curl -X POST 'http://localhost:4588/identitytoolkit.googleapis.com/v1/projects/floci-local:createSessionCookie' \
  -H 'Content-Type: application/json' -H 'Authorization: Bearer owner' \
  -d '{"idToken":"<idToken>","validDuration":3600}'

Scope and deviations

  • Client API-key calls resolve to the emulator's default project (floci-gcp.default-project-id), mirroring the official emulator's single-project model.
  • Phase 1 covers email/password, anonymous, and custom-token flows plus the admin user CRUD surface (create/lookup/update/delete/batchGet/batchDelete). Not yet implemented: OOB codes (email verification / password reset), email-link, IdP, phone, MFA, passkeys, tenants (including the tenant-scoped projects/{p}/tenants/{t}:createSessionCookie), and the legacy v3 relyingparty paths: these return 404 (the official emulator returns 501 for unimplemented operations).
  • Passwords are stored in the emulator's literal fakeHash:salt=...:password=... format. This is a development fixture, not a security boundary, and is identical to the official emulator.
  • securetoken responses report project_id as the project number, the same number Resource Manager reports for the default project (the Firebase emulator hardcodes "12345").