Firebase iOS Backend with Auth, AWS S3 Files, and Optional FastAPI API (Part 1)

This document explains how to use Firebase as the backend for an iOS (Swift/SwiftUI) app, how to build a login system with Firebase Authentication, how to work with files stored in AWS S3, and how to add a Python backend (e.g., FastAPI) alongside Firebase.


Table of contents


What Firebase is (and what it isn’t)

Firebase is a backend-as-a-service (BaaS) platform that gives you ready-made building blocks for mobile apps, including:

Cloud Storage for Firebase is built on Google Cloud infrastructure and is intended for storing and serving user-generated files like images/videos. (Firebase)

Firebase is not a replacement for every kind of backend. Many real apps use Firebase + a custom API (e.g., FastAPI) when they need:


Typical iOS + Firebase architecture

A common “Firebase-first” architecture looks like this:

iOS App (Swift/SwiftUI)
   |
   |-- Firebase Auth (login)
   |-- Firestore (app data)
   |-- Firebase Storage (optional for files)
   |-- Cloud Functions (optional server logic)
   |
   +-- (Optional) Your API (FastAPI) for custom endpoints
         |
         +-- (Optional) AWS S3 for files

Key principle: Firebase Authentication becomes your identity layer, and everything else (Firestore rules, Storage rules, your API authorization) uses that identity.


Step 1: Create and connect your iOS app to Firebase

High-level steps:

  1. Create a Firebase project in the Firebase console.
  2. Register your iOS app (Bundle ID).
  3. Add the Firebase config file (commonly GoogleService-Info.plist) to your Xcode project.
  4. Install Firebase SDK (Swift Package Manager is commonly recommended).
  5. Initialize Firebase at app startup.

Firebase’s official setup guide for Apple platforms and installation methods are here. (Firebase)

Minimal iOS initialization (SwiftUI)

import SwiftUI
import FirebaseCore

@main
struct MyApp: App {
    init() {
        FirebaseApp.configure()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Step 2: Build a login system with Firebase Authentication

Firebase Authentication provides:

Firebase’s “Get started” guide for Auth on Apple platforms walks through setup and email/password sign-in. (Firebase)

In many iOS apps, consider offering at least one of:

You can enable/disable providers in Firebase Console → Authentication → Sign-in method.


Implementing Email/Password authentication

After you install the FirebaseAuth SDK and enable Email/Password in the console, your UI typically has:

Create account

import FirebaseAuth

func signUp(email: String, password: String, completion: @escaping (Result<User, Error>) -> Void) {
    Auth.auth().createUser(withEmail: email, password: password) { result, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let user = result?.user else {
            completion(.failure(NSError(domain: "Auth", code: -1, userInfo: [NSLocalizedDescriptionKey: "Missing user"])))
            return
        }
        completion(.success(user))
    }
}

Sign in

import FirebaseAuth

func signIn(email: String, password: String, completion: @escaping (Result<User, Error>) -> Void) {
    Auth.auth().signIn(withEmail: email, password: password) { result, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let user = result?.user else {
            completion(.failure(NSError(domain: "Auth", code: -1, userInfo: [NSLocalizedDescriptionKey: "Missing user"])))
            return
        }
        completion(.success(user))
    }
}

Auth state, sign out, password reset

Listen for auth state changes

This is useful to switch between “logged out UI” and “main app UI”.

import FirebaseAuth

var authStateHandle: AuthStateDidChangeListenerHandle?

func startAuthListener() {
    authStateHandle = Auth.auth().addStateDidChangeListener { _, user in
        if let user = user {
            print("Signed in:", user.uid)
        } else {
            print("Signed out")
        }
    }
}

Sign out

import FirebaseAuth

func signOut() throws {
    try Auth.auth().signOut()
}

Password reset email

import FirebaseAuth

func sendPasswordReset(email: String, completion: @escaping (Error?) -> Void) {
    Auth.auth().sendPasswordReset(withEmail: email) { error in
        completion(error)
    }
}

Security Rules: protect data by user identity

A major advantage of Firebase is that your database and storage can enforce access control without you writing your own authorization middleware—as long as you design your data model and rules correctly.

Firebase Security Rules integrate with Firebase Authentication and allow checks like request.auth.uid == .... (Firebase)

Example: Firestore rule to restrict each user to their own profile document

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Idea: store user profile data at:

So the rule naturally matches.


How to use files in AWS S3 from a Firebase-based app

Firebase Storage vs S3

Cloud Storage for Firebase is a natural fit if:

AWS S3 is a natural fit if:

There’s no problem mixing them—just be intentional about security and how the iOS app gets access.


Best-practice pattern: S3 presigned URLs

For most mobile apps using private S3 objects, the recommended approach is:

  1. User signs in with Firebase Auth in the iOS app
  2. iOS app calls your trusted backend (Cloud Functions or FastAPI)
  3. Backend verifies Firebase ID token (authenticates user)
  4. Backend generates S3 presigned URL
  5. iOS app uploads/downloads directly to S3 using that presigned URL

AWS has specific guidance on establishing guardrails and best practices for presigned URLs. (AWS Docs)

Why presigned URLs are the go-to approach

Data flow diagram

iOS App
  |
  | (A) Firebase Auth sign-in
  | -> gets Firebase ID token (JWT)
  |
  | (B) Call your API: "give me an upload URL"
  |    Authorization: Bearer <Firebase ID token>
  v
Your Backend (FastAPI or Cloud Function)
  |
  | verify Firebase ID token
  | authorize user -> choose S3 key (e.g. users/<uid>/<uuid>.jpg)
  | generate presigned URL
  v
iOS App
  |
  | (C) PUT/GET directly to S3 using presigned URL
  v
S3

Storing file metadata in Firestore

Even if the file lives in S3, it’s common to store metadata in Firestore, for example:

Then your UI can query Firestore, but actual bytes transfer goes to S3.

Firestore rules can ensure users only read/write metadata they own (or you can centralize metadata writes through your backend if you need stricter validation). (Firebase)


Using a Python (FastAPI) backend with Firebase

Yes—you can absolutely use FastAPI (or any Python backend) with Firebase, and it’s a very common pattern.

Core idea: Firebase Auth on the client, token verification on the server

Firebase documents this “verify ID tokens” flow for backends using the Admin SDK. (Firebase)


FastAPI example: verify Firebase ID tokens

Install:

pip install fastapi uvicorn firebase-admin

Example main.py:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

import firebase_admin
from firebase_admin import credentials, auth

app = FastAPI()
bearer_scheme = HTTPBearer()

## Initialize once at startup
## Use a service account JSON (keep it secret; do NOT commit it).
cred = credentials.Certificate("serviceAccountKey.json")
firebase_admin.initialize_app(cred)

def verify_firebase_token(
    creds: HTTPAuthorizationCredentials = Depends(bearer_scheme),
):
    token = creds.credentials
    try:
        decoded = auth.verify_id_token(token)
        return decoded  # includes 'uid' and other claims
    except Exception:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid or expired Firebase ID token",
        )

@app.get("/me")
def me(decoded=Depends(verify_firebase_token)):
    return {
        "uid": decoded.get("uid"),
        "claims": decoded,
    }

Why this works:

Tip: For higher security (e.g., admin ban/logout flows), also consider token revocation checks; Firebase discusses revocation detection in the same area of docs. (Firebase)


iOS example: call your FastAPI with a Firebase ID token

After the user signs in, retrieve an ID token and call your API.

Conceptually:

import FirebaseAuth

func callMyApi() {
    guard let user = Auth.auth().currentUser else { return }

    user.getIDTokenForcingRefresh(false) { token, error in
        guard let token = token, error == nil else { return }

        var request = URLRequest(url: URL(string: "https://api.example.com/me")!)
        request.httpMethod = "GET"
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        URLSession.shared.dataTask(with: request) { data, response, error in
            // handle response
        }.resume()
    }
}

When to use Custom Tokens

You usually do not need custom tokens if you’re happy with standard Firebase sign-in methods.

You consider custom tokens when:

Firebase Admin SDK supports creating custom tokens, which the client exchanges for a Firebase session. (Firebase)


Security & production checklist

1) Secure Firebase resources with Security Rules

2) Consider Firebase App Check

App Check helps ensure requests to Firebase services come from your genuine app (helps against abuse of API keys and scripted traffic). Firebase provides setup guidance for Apple platforms. (Firebase)

3) If using S3, prefer presigned URLs (avoid AWS keys on-device)

Follow AWS presigned URL guardrails:

4) Treat Firebase Auth as your identity provider everywhere

5) Use Cloud Functions when you want “Firebase-native” server logic

Callable functions can automatically include Auth/App Check context when invoked from Firebase client SDKs, which can simplify some backend calls. (Firebase)

(You can still keep FastAPI for heavier or Python-specific workloads.)


Suggested implementation roadmap

A practical way to build your first version:

  1. Create Firebase project + connect iOS app (SwiftPM + FirebaseApp.configure()). (Firebase)
  2. Implement Firebase Auth (start with Email/Password; add Apple later if needed). (Firebase)
  3. Create a Firestore users/{uid} profile doc on first login.
  4. Write Firestore Security Rules so users can only access their own data. (Firebase)
  5. If you need S3:

    • build a small backend endpoint: /s3/presign-upload and /s3/presign-download
    • verify Firebase token in the backend
    • generate presigned URLs with AWS SDK (AWS Docs)
  6. If you need custom APIs:

    • deploy FastAPI (containerized) and require Firebase ID tokens for all protected routes (Firebase)
  7. Add App Check and monitoring before scaling up. (Firebase)

If you want, I can extend this document with: