firebase-hs: Firebase Auth, Firestore, and Servant integration for Haskell

[ authentication, database, library, mit, web ] [ Propose Tags ] [ Report a vulnerability ]

Firebase Authentication (JWT verification), Firestore REST API client (CRUD, queries, transactions), and optional WAI middleware and Servant auth combinator. Verify ID tokens against Google's public keys, read and write Firestore documents, and protect any Haskell web server with Firebase auth — all from pure, composable Haskell.


[Skip to Readme]

Flags

Manual Flags

NameDescriptionDefault
wai

Enable WAI auth middleware (Firebase.Auth.WAI)

Disabled
servant

Enable Servant auth combinator (Firebase.Servant)

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Change log CHANGELOG.md
Dependencies aeson (>=2.0 && <2.3), base (>=4.17 && <5), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.8), http-client (>=0.7 && <0.8), http-client-tls (>=0.3 && <0.4), http-types (>=0.12 && <0.13), jose (>=0.11 && <0.13), lens (>=5.0 && <5.4), stm (>=2.5 && <2.6), text (>=2.0 && <2.2), time (>=1.12 && <1.15), transformers (>=0.5 && <0.7) [details]
Tested with ghc ==9.6.7
License MIT
Author Devon Tomlin
Maintainer devon.tomlin@novavero.ai
Uploaded by aoinoikaz at 2026-02-12T00:37:01Z
Category Web, Authentication, Database
Home page https://github.com/Gondola-Bros-Entertainment/firebase-hs
Bug tracker https://github.com/Gondola-Bros-Entertainment/firebase-hs/issues
Source repo head: git clone https://github.com/Gondola-Bros-Entertainment/firebase-hs -b main
Distributions
Downloads 0 total (0 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2026-02-12 [all 1 reports]

Readme for firebase-hs-0.1.0.0

[back to package description]

firebase-hs

Firebase for Haskell

Auth verification, Firestore CRUD, structured queries, atomic transactions, and a Servant auth combinator.

Quick Start · Firestore · Servant · API Reference

CI Hackage Haskell License


What is firebase-hs?

A pure Haskell library for Firebase services:

  • Auth — JWT verification against Google's public JWKs, with automatic key caching
  • Firestore — CRUD operations, structured queries, and atomic transactions via the REST API
  • Servant — One-liner auth combinator for Servant servers (optional flag)

Quick Start

Add to your .cabal file:

build-depends: firebase-hs

Verify a Token

import Firebase.Auth

main :: IO ()
main = do
  cache <- newTlsKeyCache
  let cfg = defaultFirebaseConfig "my-project-id"
  result <- verifyIdTokenCached cache cfg tokenBytes
  case result of
    Left err   -> putStrLn ("Auth failed: " ++ show err)
    Right user -> putStrLn ("UID: " ++ show (fuUid user))

Auth

Verification Rules

Check Rule
Algorithm RS256 only
Signature Must match a Google public key
Issuer https://securetoken.google.com/<projectId>
Audience Must equal your Firebase project ID
Expiry exp must be in the future (within clock skew)
Issued at iat must be in the past (within clock skew)
Subject sub must be non-empty (becomes the Firebase UID)

Key Caching

Keys are fetched lazily on first verification, cached per Google's Cache-Control: max-age, and refreshed automatically. Thread-safe via STM.

Error Handling

case result of
  Left (KeyFetchError msg) -> logError "Network issue" msg
  Left InvalidSignature    -> respond 401 "Invalid token"
  Left TokenExpired        -> respond 401 "Token expired"
  Left (InvalidClaims msg) -> respond 401 ("Bad claims: " <> msg)
  Left (MalformedToken _)  -> respond 400 "Malformed token"
  Right user               -> handleAuthenticated user

Firestore

CRUD Operations

import Firebase.Firestore

main :: IO ()
main = do
  mgr <- newTlsManager
  let pid = ProjectId "my-project"
      tok = AccessToken "ya29..."

  -- Create
  let fields = Map.fromList [("name", StringValue "Alice"), ("age", IntegerValue 30)]
  _ <- createDocument mgr tok pid (CollectionPath "users") (DocumentId "alice") fields

  -- Read
  let path = DocumentPath (CollectionPath "users") (DocumentId "alice")
  doc <- getDocument mgr tok pid path

  -- Update specific fields
  let updates = Map.fromList [("age", IntegerValue 31)]
  _ <- updateDocument mgr tok pid path ["age"] updates

  -- Delete
  _ <- deleteDocument mgr tok pid path
  pure ()

Structured Queries

Build queries with a pure DSL and (&) composition:

import Data.Function ((&))

let q = query (CollectionPath "users")
      & where_ (fieldFilter "age" OpGreaterThan (IntegerValue 18))
      & orderBy "age" Ascending
      & limit 10

result <- runQuery mgr tok pid q

Composite filters for complex conditions:

let q = query (CollectionPath "users")
      & where_ (compositeAnd
          [ fieldFilter "age" OpGreaterThan (IntegerValue 18)
          , fieldFilter "active" OpEqual (BoolValue True)
          ])

Atomic Transactions

Read-then-write operations that succeed or fail atomically:

result <- runTransaction mgr tok pid ReadWrite $ \txnId -> runExceptT $ do
  -- Reads within the transaction see a consistent snapshot
  d <- ExceptT $ getDocument mgr tok pid userPath
  let newBalance = computeNewBalance (docFields d)
  pure [mkUpdateWrite userPath newBalance]

Retry aborted transactions:

-- First attempt
result <- beginTransaction mgr tok pid ReadWrite
case result of
  Left (TransactionAborted _) ->
    -- Retry with the failed transaction ID for priority
    beginTransaction mgr tok pid (RetryWith txnId)

Firestore Value Types

Values mirror Firestore's tagged wire format:

data FirestoreValue
  = NullValue | BoolValue !Bool | IntegerValue !Int64
  | DoubleValue !Double | StringValue !Text | TimestampValue !UTCTime
  | ArrayValue ![FirestoreValue] | MapValue !(Map Text FirestoreValue)

Note: integers are encoded as JSON strings ({"integerValue":"42"}), not numbers. The JSON instances handle this transparently.


WAI Middleware

Protect any WAI-based server (Warp, Scotty, Yesod, Spock) with Firebase auth. Enable with the wai cabal flag:

cabal build -f wai

Simple Gate

Reject unauthenticated requests before they reach your app:

import Firebase.Auth (newTlsKeyCache, defaultFirebaseConfig)
import Firebase.Auth.WAI (requireAuth)
import Network.Wai.Handler.Warp (run)

main :: IO ()
main = do
  cache <- newTlsKeyCache
  let cfg = defaultFirebaseConfig "my-project-id"
  run 3000 $ requireAuth cache cfg myApp

With User Propagation

Store the authenticated user in the WAI vault for downstream handlers:

import Firebase.Auth.WAI (firebaseAuth, lookupFirebaseUser)

main = run 3000 $ firebaseAuth cache cfg myApp

myHandler req respond = case lookupFirebaseUser req of
  Just user -> respond (ok200 ("Hello, " <> fuUid user))
  Nothing   -> respond (err500 "unreachable")

Servant

Enable with the servant cabal flag:

cabal build -f servant

One-liner auth for any Servant server:

import Firebase.Auth (newTlsKeyCache, defaultFirebaseConfig)
import Firebase.Servant (firebaseAuthHandler)
import Servant.Server (Context (..))

main :: IO ()
main = do
  cache <- newTlsKeyCache
  let cfg = defaultFirebaseConfig "my-project-id"
      ctx = firebaseAuthHandler cache cfg :. EmptyContext
  runSettings defaultSettings (serveWithContext api ctx server)

The handler extracts the Bearer token, verifies it against Google's keys, and injects a FirebaseUser into your endpoint — or returns 401 with a descriptive error.


API Reference

Auth

verifyIdToken       :: Manager -> FirebaseConfig -> ByteString -> IO (Either AuthError FirebaseUser)
newKeyCache         :: Manager -> IO KeyCache
newTlsKeyCache      :: IO KeyCache
verifyIdTokenCached :: KeyCache -> FirebaseConfig -> ByteString -> IO (Either AuthError FirebaseUser)
parseCacheMaxAge    :: ResponseHeaders -> Maybe Int

Firestore

getDocument    :: Manager -> AccessToken -> ProjectId -> DocumentPath -> IO (Either FirestoreError Document)
createDocument :: Manager -> AccessToken -> ProjectId -> CollectionPath -> DocumentId -> Map Text FirestoreValue -> IO (Either FirestoreError Document)
updateDocument :: Manager -> AccessToken -> ProjectId -> DocumentPath -> [Text] -> Map Text FirestoreValue -> IO (Either FirestoreError Document)
deleteDocument :: Manager -> AccessToken -> ProjectId -> DocumentPath -> IO (Either FirestoreError ())
runQuery       :: Manager -> AccessToken -> ProjectId -> StructuredQuery -> IO (Either FirestoreError [Document])

Transactions

beginTransaction    :: Manager -> AccessToken -> ProjectId -> TransactionMode -> IO (Either FirestoreError TransactionId)
commitTransaction   :: Manager -> AccessToken -> ProjectId -> TransactionId -> [Value] -> IO (Either FirestoreError ())
rollbackTransaction :: Manager -> AccessToken -> ProjectId -> TransactionId -> IO (Either FirestoreError ())
runTransaction      :: Manager -> AccessToken -> ProjectId -> TransactionMode -> (TransactionId -> IO (Either FirestoreError [Value])) -> IO (Either FirestoreError ())

WAI Middleware

requireAuth        :: KeyCache -> FirebaseConfig -> Middleware
firebaseAuth       :: KeyCache -> FirebaseConfig -> Middleware
lookupFirebaseUser :: Request -> Maybe FirebaseUser

Servant

firebaseAuthHandler :: KeyCache -> FirebaseConfig -> AuthHandler Request FirebaseUser
extractBearerToken  :: Request -> Maybe ByteString
authErrorToBody     :: AuthError -> LBS.ByteString

Full Haddock documentation is available on Hackage.


Build & Test

cabal build                              # Build library
cabal test                               # Run all tests (40 pure tests)
cabal build --ghc-options="-Werror"      # Warnings as errors
cabal build -f wai                       # Build with WAI middleware
cabal build -f servant                   # Build with Servant combinator
cabal haddock                            # Generate docs

MIT License · Gondola Bros Entertainment