@glydi/passkey-firebase
v0.1.0
Published
Drop Glide passkeys onto an app that already uses Firebase Auth. Mints a Firebase custom token from a verified passkey, plus an Express adapter and a client sign-in helper.
Downloads
56
Readme
@glydi/passkey-firebase
Drop Glide passkeys onto an app that already uses Firebase Auth — without
replacing anything. A passkey login becomes another door into the same Firebase
session: your existing verifyIdToken middleware, custom-claim roles, and user
records keep working untouched.
passkey verified → mint Firebase custom token → signInWithCustomToken → normal Firebase session
(Glide) (this package, server) (this package, client) (your app, unchanged)Three tree-shakeable entry points:
| Import | Side | Purpose |
|---|---|---|
| @glydi/passkey-firebase/server | backend | createFirebaseBridge({ auth }) → an onAuthSuccess that mints a custom token and returns it in the response |
| @glydi/passkey-firebase/express | backend | toExpressHandler(handler) — adapts Glide's Web-standard handler to Express (req, res) |
| @glydi/passkey-firebase/client | browser | completePasskeySignIn(auth, result) — exchanges the minted token via signInWithCustomToken |
Peers (install what you use): firebase-admin (server), firebase (client),
express (express adapter), @glydi/passkey-server.
Server (Express)
import { getAuth } from "firebase-admin/auth";
import { Router } from "express";
import { createGlideServer, createPasskeyRouteHandler } from "@glydi/passkey-server";
import { createFirebaseBridge } from "@glydi/passkey-firebase/server";
import { toExpressHandler } from "@glydi/passkey-firebase/express";
const glide = createGlideServer({ rpName, rpID, origin, store }); // store: your GlideStore
const glideHandler = toExpressHandler(
createPasskeyRouteHandler({
server: glide,
getSessionId, // read/set the glide_sid cookie
getUserId: async (req) => req.user?.uid, // verified Firebase UID (register-*)
onAuthSuccess: createFirebaseBridge({ auth: getAuth() }),
}),
);
const router = Router();
// Login: public — the passkey IS the proof.
router.post("/authenticate-begin", glideHandler);
router.post("/authenticate-finish", glideHandler);
// Register (add-a-passkey): behind your existing auth — links to the signed-in UID.
router.post("/register-begin", requireAuth, glideHandler);
router.post("/register-finish", requireAuth, glideHandler);Identity rule: your
GlideStoremust setuser.idto the Firebase UID.createFirebaseBridgemints the token for exactly that id.
Client
import { getAuth } from "firebase/auth";
import { PasskeyButton } from "@glydi/passkey-react";
import { completePasskeySignIn } from "@glydi/passkey-firebase/client";
<PasskeyButton
mode="signin"
endpoints={{
authenticateBegin: `${API}/api/v1/passkey/authenticate-begin`,
authenticateFinish: `${API}/api/v1/passkey/authenticate-finish`,
registerBegin: `${API}/api/v1/passkey/register-begin`,
registerFinish: `${API}/api/v1/passkey/register-finish`,
}}
fetchOptions={{ credentials: "include" }} // carries the glide_sid cookie
onSuccess={(result) => completePasskeySignIn(getAuth(), result)}
/>After completePasskeySignIn, Firebase's onIdTokenChanged fires and your app
proceeds exactly as if the user signed in any other way.
Cross-origin note
If your web app and API are on different domains, the glide_sid cookie must be
SameSite=None; Secure, the client must send credentials: "include" (set above),
and the API's CORS must allow credentials and echo the web origin. rpID is the
browser domain (where the button renders), independent of where the API runs.
