SmartStore AI — Phase 5 Implementation Guide

Authentication

What got built

backend/app/auth.py          — decode_token(), get_or_create_user(), get_current_user dependency
backend/app/api/me.py         — a real protected endpoint demonstrating the dependency
backend/tests/test_auth.py

ios/SmartStoreAI/AuthManager.swift   — Firebase sign-in (anonymous), token refresh
ios/SmartStoreAI/APIClient.swift      — updated to attach the Bearer token to every request
ios/SmartStoreAI/ChatViewModel.swift   — updated to refresh the token before every send()

Key design decisions

decode_token and get_or_create_user are separated from the FastAPI dependency itself. This is what made it possible to test the actual logic that matters — first-login provisioning, duplicate-prevention — against the real database, without needing real Firebase credentials. The Firebase verification call itself is Google's infrastructure; testing that it works is Google's job, not this test suite's.

First-login provisioning is idempotent — verified directly, not assumed. test_get_or_create_user_provisions_new_user calls the function twice with the same firebase_uid and confirms exactly one User row exists afterward, not two. This matters because a token gets verified on every single request — if provisioning weren't idempotent, a user's second message would create a second, orphaned account.

Anonymous sign-in on the iOS side, deliberately, not email/password. Every shopper gets a real, stable Firebase UID immediately, with zero sign-up friction — and because everything downstream keys off the token, not the sign-in method, swapping in email/Google/Apple sign-in later touches only AuthManager, nothing else.

/ask is intentionally NOT yet protected by get_current_user. That wiring happens in Phase 6, paired directly with the RBAC permission check — "must be authenticated" and "is authorized for this specific action" are different concerns that belong together at the route level, not split across two phases of half-finished protection.

Verified test results

tests/test_auth.py::test_decode_token_with_valid_token PASSED
tests/test_auth.py::test_decode_token_with_invalid_token_raises PASSED
tests/test_auth.py::test_get_or_create_user_provisions_new_user PASSED
tests/test_auth.py::test_me_endpoint_without_auth_header_returns_401 PASSED
tests/test_auth.py::test_me_endpoint_with_valid_auth_returns_user PASSED

All 17 tests across Phases 0-5 pass together — confirmed by running the full suite, not just this phase's file in isolation.

Setting up real Firebase (you'll need to do this yourself)

  1. Create a project at console.firebase.google.com
  2. Enable Anonymous sign-in under Authentication → Sign-in method
  3. Download the service account JSON (Project Settings → Service Accounts) → set FIREBASE_CREDENTIALS_PATH in .env
  4. Add the GoogleService-Info.plist to your Xcode project, add the Firebase iOS SDK via Swift Package Manager
  5. Initialize Firebase in SmartStoreAIApp.swift with FirebaseApp.configure() before the WindowGroup

None of this can be verified in this sandbox — it requires a real Firebase project, which is the one genuinely external dependency in this entire phase.

What's next

Phase 6 — RBAC & Multi-Store Isolation wires get_current_user into /ask for real, adds the role-permission map, and adds Postgres Row-Level Security as a database-enforced backstop.