Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.alterauth.com/llms.txt

Use this file to discover all available pages before exploring further.

This is the recommended primary path for any app with logged-in users. Configure an Identity Provider, pass a user_token_getter at SDK construction, and stop tracking grant_id in the application database.

Why this is the default

Without JWT identityWith JWT identity
Store grant_id per user in the application DBBackend resolves grant from JWT sub claim
Keep that mapping in sync as users add/remove connectionsAuth source is the IDP — Alter follows it
Manually deprovision when a user leavesDeprovisioning the IDP user revokes their grants

Prerequisites

The pattern

Construct the SDK once. Pass a user_token_getter that returns the current request’s JWT. Call vault.request(...) with provider="..." instead of grant_id=....
from alter_sdk import App, HttpMethod

vault = App(
    api_key=ALTER_API_KEY,
    user_token_getter=lambda: get_jwt_from_current_request(),
)

# No grant_id. Backend resolves Alice's Google grant from her JWT.
response = await vault.request(
    HttpMethod.GET,
    "https://www.googleapis.com/calendar/v3/calendars/primary/events",
    provider="google",
)

Wiring user_token_getter

user_token_getter is called on every request. It should return the JWT for the currently active end user. Two common patterns:

FastAPI (Python)

The Python SDK ships a FastAPI helper that pulls the Authorization: Bearer … header into a ContextVar:
from fastapi import FastAPI, Depends
from alter_sdk import App
from alter_sdk.fastapi import AlterFastAPI, depends as alter_depends

vault = App(api_key=ALTER_API_KEY)
alter = AlterFastAPI(vault)

app = FastAPI()

@app.get("/calendar")
async def list_events(_=Depends(alter_depends(alter))):
    response = await vault.request("GET", "...", provider="google")
    return response.json()
See SDKs → Python → FastAPI.

Express / generic Node

Stash the JWT in AsyncLocalStorage per request, return it from the getter:
import { AsyncLocalStorage } from "node:async_hooks";
import { App } from "@alter-ai/alter-sdk";

const jwtStore = new AsyncLocalStorage<string>();
const vault = new App({
  apiKey: ALTER_API_KEY,
  userTokenGetter: () => jwtStore.getStore() ?? "",
});

app.use((req, _res, next) => {
  const jwt = req.headers.authorization?.replace(/^Bearer /, "") ?? "";
  jwtStore.run(jwt, next);
});

Provider disambiguation

provider="google" resolves the user’s grant for Google. When a user has multiple accounts on the same provider (work Gmail + personal Gmail), pass account= to disambiguate:
response = await vault.request(
    HttpMethod.GET, "...", provider="google", account="[email protected]",
)
When account is omitted and exactly one grant exists, it resolves. When multiple grants exist and no disambiguator is provided, the SDK raises AmbiguousGrantError — see SDK → Errors.

What happens on the wire

  1. SDK calls user_token_getter(), attaches the JWT to the outgoing call.
  2. Backend verifies the JWT signature against the configured IDP’s JWKS.
  3. Backend extracts the sub claim, looks up grants (app, user, provider).
  4. When exactly one grant matches, the backend retrieves and refreshes the token, injects it into the upstream call.
  5. The provider response is returned to application code unchanged.
The first time Alter sees a JWT for a new user, it lazily creates a user record and syncs group memberships from the JWT claims. Subsequent requests update the user’s profile.

Verifying a JWT directly

Sometimes a JWT must be verified in application code without making a provider call (e.g., in middleware that runs before any vault.request()):
sub = await vault.verify_user_token(jwt)
if sub is None:
    raise HTTPException(401, "Invalid token")
Returns the verified sub claim or null. Uses the same JWKS verification path the backend uses on every request.

When not to use this

  • Cron jobs / webhook handlers with no end user → grant_id identity.
  • CLIs / notebooks / agent bootstrapHeadless.
  • Per-call user override (rare): pass user_token=jwt directly to vault.request().

See also