@hackage / firebase-hs

Firebase Auth, Firestore, and Servant integration for Haskell

Latest0.3.0.0

About

Metadata

  • Last updated , by aoinoikaz
  • License BSD-3-Clause
  • Categories Web Development, Databases
  • Maintained by: devon.tomlin@novavero.ai

  • Lottery factor: 1

Links

Installation

Tested Compilers

  1. 9.12.2
  2. 9.12.1
  3. 9.10.3
  4. 9.10.2
  5. 9.10.1
  6. 9.8.4
  7. 9.8.3
  8. 9.8.2
  9. 9.8.1
  10. 9.6.7
  11. 9.6.6
  12. 9.6.5
  13. 9.6.4
  14. 9.6.3
  15. 9.6.2
  16. 9.6.1

Package Flags

Use the -f option with cabal commands to enable flags

    wai (off by default)

    Enable WAI auth middleware (Firebase.Auth.WAI)

    servant (off by default)

    Enable Servant auth combinator (Firebase.Servant)

Readme

firebase-hs

CI Hackage License

Firebase for Haskell:

  • Auth: Firebase ID token (JWT) verification against Google's public keys, with RS256 via crypton and automatic key caching
  • Firestore: CRUD, structured queries, and atomic transactions over the REST API
  • WAI / Servant: auth middleware and an auth combinator, each behind an optional cabal flag

Full API documentation lives on Hackage.

Install

build-depends: firebase-hs

The web integrations are off by default; enable the ones you use:

cabal build -f wai      # Firebase.Auth.WAI
cabal build -f servant  # Firebase.Servant

Auth

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))

Build one KeyCache at startup and share it across threads; keys refresh automatically per Google's Cache-Control header.

A token is accepted only if every one of these holds:

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 / issued-at exp in the future, iat in the past, within clock skew
Subject sub non-empty (becomes the Firebase UID)

Roles live in custom claims, set by the Admin SDK's setCustomUserClaims:

if hasClaim "admin" user then handleAdmin user else refuse

Firestore

import qualified Data.Map.Strict as Map
import Firebase.Firestore

main :: IO ()
main = do
  fs <- newFirestore (ProjectId "my-project") (AccessToken "ya29...")

  let path = DocumentPath (CollectionPath "users") (DocumentId "alice")
  _ <- createDocument fs (CollectionPath "users") (DocumentId "alice")
         (Map.fromList [("name", StringValue "Alice"), ("age", IntegerValue 30)])
  _ <- updateDocument fs path ["age"] (Map.fromList [("age", IntegerValue 31)])
  doc <- getDocument fs path
  print doc

Build one Firestore handle and share it; it holds a pooled connection manager. Access tokens expire, so swap in a fresh one with withToken rather than rebuilding the handle.

Queries compose with (&); subcollections are addressed by path:

import Data.Function ((&))

result <- runQuery fs $
  query (CollectionPath "users")
    & where_ (fieldFilter "age" OpGreaterThan (IntegerValue 18))
    & orderBy "age" Ascending
    & limit 10

Transactions read with the transaction ID and return the writes to commit. On any failure the transaction is rolled back, and losing a contention race surfaces as TransactionAborted:

result <- runTransaction fs ReadWrite $ \txnId -> runExceptT $ do
  doc <- ExceptT (getDocumentInTransaction fs txnId path)
  pure [mkUpdateWrite (fsProject fs) path (applyDebit 100 (docFields doc))]

WAI

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)

firebaseAuth additionally stores the verified FirebaseUser in the request vault for lookupFirebaseUser to read downstream.

Servant

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)

Build and Test

cabal build all -f wai -f servant --enable-tests --ghc-options="-Werror"
cabal test

BSD-3-Clause. Maintained by Gondola Bros Entertainment.