Skip to content

FastAPI

Add SweatStack to your FastAPI app in three steps. After setup, every authenticated request gives you a ready-to-use API client—no token management required.

1. Install

uv add 'sweatstack[fastapi]'

2. Get your credentials

Before writing code, grab two things:

  1. Client ID and SecretCreate an application and set the redirect URI to:

    http://localhost:8000/auth/sweatstack/callback
    

  2. Session secret — Generate a Fernet key for encrypting cookies:

    python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    

3. Create your app

Create app.py:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from sweatstack.fastapi import (
    configure, instrument, AuthenticatedUser, OptionalUser, urls
)

configure(  # (1)!
    client_id="your-client-id",
    client_secret="your-client-secret",
    session_secret="your-generated-fernet-key",
    app_url="http://localhost:8000",
)

app = FastAPI()
instrument(app)  # (2)!


@app.get("/")
def home(user: OptionalUser):  # (3)!
    if user:
        return HTMLResponse(f"""
            Welcome, {user.user_id}!
            <a href='/activities'>My activities</a>
            <form method='POST' action='{urls.logout()}'><button>Logout</button></form>
        """)
    return HTMLResponse(f"<a href='{urls.login()}'>Login with SweatStack</a>")


@app.get("/activities")
def activities(user: AuthenticatedUser):  # (4)!
    return user.client.get_activities(limit=10)  # (5)!
  1. Configures OAuth credentials. For production, use environment variables instead.
  2. Adds login, logout, and callback routes to your app. See Routes added for the full list.
  3. OptionalUser works for both logged-in and anonymous visitors. See Protecting routes.
  4. AuthenticatedUser requires login—anonymous users get redirected automatically.
  5. Every user object includes a pre-configured client for API calls. See the Python client docs for available methods.

Run it:

uv run fastapi dev app.py

Open http://localhost:8000. Click the login link, authenticate with SweatStack, and you're back with full API access.


How it works

Routes added

instrument() adds these routes to your app:

Route Purpose
GET /auth/sweatstack/login Starts OAuth flow
GET /auth/sweatstack/callback Completes OAuth flow
POST /auth/sweatstack/logout Ends the session
POST /auth/sweatstack/select-user/{user_id} Switch to another user's view
POST /auth/sweatstack/select-self Switch back to your own view

What happens on each request

When a user visits a protected route:

  1. Not logged in — Redirected to /auth/sweatstack/login
  2. Logged in — Your handler receives a user object with:
    • user.user_id — The authenticated user's ID
    • user.client — A configured SweatStack client ready for API calls

Tokens live in encrypted, httponly cookies. No database needed.


Protecting routes

Choose the right dependency based on whether login is required:

AuthenticatedUser — login required

from sweatstack.fastapi import AuthenticatedUser

@app.get("/dashboard")
def dashboard(user: AuthenticatedUser):  # (1)!
    return user.client.get_activities()
  1. Anonymous visitors are redirected to login automatically. To return a 401 instead, set redirect_unauthenticated=False in configure().

OptionalUser — login optional

from sweatstack.fastapi import OptionalUser

@app.get("/")
def home(user: OptionalUser):  # (1)!
    if user:
        return {"message": f"Hello, {user.user_id}!"}
    return {"message": "Hello, guest!"}
  1. user is None when not logged in, otherwise a full user object.

Building login/logout UI

Use the urls helper to generate auth URLs:

from fastapi.responses import HTMLResponse
from sweatstack.fastapi import OptionalUser, urls

@app.get("/")
def home(user: OptionalUser):
    if user:
        return HTMLResponse(f"""
            <p>Welcome, {user.user_id}!</p>
            <form method="POST" action="{urls.logout()}">
                <button>Logout</button>
            </form>
        """)
    return HTMLResponse(f"""
        <a href="{urls.login()}">Login with SweatStack</a>
    """)

Available URL helpers

Method Returns
urls.login() /auth/sweatstack/login
urls.login(next="/dashboard") Login, then redirect to /dashboard
urls.logout() /auth/sweatstack/logout
urls.select_user(user_id) Switch to viewing another user
urls.select_user(user_id, next="/dashboard") Switch user, then redirect
urls.select_self() Switch back to your own view

The next parameter controls where users land after the action completes.


User switching (delegation)

For coaching platforms and multi-user apps, SweatStack supports viewing data on behalf of another user. The logged-in user (coach) can switch to view an athlete's data while staying logged in as themselves.

Additional dependencies

Beyond AuthenticatedUser and OptionalUser, delegation adds:

Dependency Returns
AuthenticatedUser Always the logged-in user (the coach)
SelectedUser The currently viewed user (athlete or self)
OptionalSelectedUser Same as above, but None if not logged in

Example: coach dashboard

from sweatstack.fastapi import AuthenticatedUser, SelectedUser, urls
from fastapi.responses import HTMLResponse

@app.get("/athletes")
def athletes(user: AuthenticatedUser):
    accessible = user.client.get_users()  # (1)!

    links = "".join(
        f"""<form method="post" action="{urls.select_user(u.id, next='/dashboard')}">
            <button>{u.display_name}</button>
        </form>"""
        for u in accessible if u.id != user.user_id
    )
    return HTMLResponse(f"<h1>My Athletes</h1>{links}")


@app.get("/dashboard")
def dashboard(user: SelectedUser, principal: AuthenticatedUser):  # (2)!
    activities = user.client.get_activities(limit=5)

    back_btn = ""
    if user.user_id != principal.user_id:  # (3)!
        back_btn = f"""<form method="post" action="{urls.select_self()}">
            <button>Back to my view</button>
        </form>"""

    return HTMLResponse(f"{back_btn}<pre>{activities}</pre>")
  1. Returns users who have granted this coach access to their data.
  2. Use both dependencies together: user is whoever is currently selected, principal is always the logged-in coach.
  3. Show a "back" button only when viewing someone else's data.

Configuration

Environment variables

For production, use environment variables instead of hardcoding secrets:

export SWEATSTACK_CLIENT_ID="your-client-id"
export SWEATSTACK_CLIENT_SECRET="your-client-secret"
export SWEATSTACK_SESSION_SECRET="your-fernet-key"
export APP_URL="https://yourapp.com"

Then call configure() with no arguments:

configure()  # (1)!
  1. Reads all values from environment variables automatically.
Variable Description
SWEATSTACK_CLIENT_ID OAuth client ID
SWEATSTACK_CLIENT_SECRET OAuth client secret
SWEATSTACK_SESSION_SECRET Fernet key for cookie encryption
APP_URL Your app's public URL

All options

configure(
    # Required (or use environment variables)
    client_id="...",
    client_secret="...",
    session_secret="...",
    app_url="http://localhost:8000",

    # Optional
    scopes=["profile", "data:read"],  # (1)!
    cookie_max_age=86400,  # (2)!
    auth_route_prefix="/auth/sweatstack",  # (3)!
    redirect_unauthenticated=True,  # (4)!
)
  1. OAuth scopes to request. Defaults cover most use cases.
  2. Session lifetime in seconds. Default is 24 hours.
  3. Change the prefix for all auth routes.
  4. Set to False to return 401 instead of redirecting to login.

Next steps