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:
-
Client ID and Secret — Create an application and set the redirect URI to:
http://localhost:8000/auth/sweatstack/callback -
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)!
- Configures OAuth credentials. For production, use environment variables instead.
- Adds login, logout, and callback routes to your app. See Routes added for the full list.
OptionalUserworks for both logged-in and anonymous visitors. See Protecting routes.AuthenticatedUserrequires login—anonymous users get redirected automatically.- Every user object includes a pre-configured
clientfor 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:
- Not logged in — Redirected to
/auth/sweatstack/login - Logged in — Your handler receives a user object with:
user.user_id— The authenticated user's IDuser.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()
- Anonymous visitors are redirected to login automatically. To return a 401 instead, set
redirect_unauthenticated=Falsein 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!"}
userisNonewhen 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>")
- Returns users who have granted this coach access to their data.
- Use both dependencies together:
useris whoever is currently selected,principalis always the logged-in coach. - 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)!
- 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)!
)
- OAuth scopes to request. Defaults cover most use cases.
- Session lifetime in seconds. Default is 24 hours.
- Change the prefix for all auth routes.
- Set to
Falseto return 401 instead of redirecting to login.
Next steps¶
- Browse the API reference to see available endpoints
- Read the Python client docs for all client methods
- Learn OAuth2 concepts if you want deeper understanding