Skip to main content
When you set access_mode = "accounts", floo provides a hosted OAuth flow powered by WorkOS. Your app’s users can sign in with email, Google, GitHub, and more — no auth infrastructure to build.

Quickstart

This is the exact sequence. Each step depends on the previous one.
# 1. Configure auth in floo.app.toml (see config section below)
# 2. Deploy so the auth endpoints are provisioned
git push origin main

# 3. Get your app ID (needed for OAuth URLs)
floo apps list --json | jq '.data.apps[] | select(.name == "my-app") | .id'

# 4. Set FLOO_APP_ID so your app can reference it
#    (auto-injected if your app has managed services like postgres/redis/storage;
#     set it manually otherwise)
floo env set FLOO_APP_ID=<app-id> --app my-app
floo redeploy --app my-app
After step 4, your app can use the OAuth endpoints below.

Domain naming convention

Your app’s public URL follows this pattern:
EnvironmentDomain
dev<app-name>-dev.on.getfloo.com
production<app-name>.on.getfloo.com
Use these exact hostnames when registering redirect URIs. If your app has a custom domain, use that instead.

Example config

[app]
name = "my-app"
access_mode = "accounts"

[auth]
redirect_uris = [
  "http://localhost:3000/callback",
  "https://my-app-dev.on.getfloo.com/callback",
  "https://my-app.on.getfloo.com/callback"
]
Deploy with git push (or floo redeploy). The auth endpoints are live as soon as the deploy completes. No separate WorkOS account is needed — floo manages this for you.

OAuth flow

All auth endpoints are under https://api.getfloo.com/v1/auth/apps/{app_id}.

1. Start login

Redirect your user’s browser to:
GET https://api.getfloo.com/v1/auth/apps/{app_id}/authorize?redirect_uri=https://my-app-dev.on.getfloo.com/callback
The redirect_uri must exactly match one of the URIs registered in your [auth] config — including the protocol, host, port, and path.

2. Handle the callback

After the user authenticates, floo redirects back to your redirect_uri with a one-time exchange code:
https://my-app-dev.on.getfloo.com/callback?code=<exchange_code>

3. Exchange code for tokens

From your backend, exchange the code for an access token and refresh token:
curl -X POST https://api.getfloo.com/v1/auth/apps/{app_id}/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type": "authorization_code", "code": "<exchange_code>"}'
Response:
{
  "access_token": "<jwt>",
  "refresh_token": "<opaque_token>",
  "expires_in": 3600,
  "user": {
    "id": "<uuid>",
    "email": "user@example.com",
    "name": "Jane Doe",
    "avatar_url": "https://..."
  }
}

4. Verify the JWT

The access_token is an RS256-signed JWT. You can verify it locally using the public keys:
GET https://api.getfloo.com/v1/auth/apps/{app_id}/.well-known/jwks.json
JWT claims:
ClaimDescription
subapp user ID (UUID)
emailuser’s email address
nameuser’s display name
isshttps://auth.getfloo.com
audyour app ID
iatissued at
expexpiration

5. Refresh tokens

When the access token expires, use the refresh token to get a new one:
curl -X POST https://api.getfloo.com/v1/auth/apps/{app_id}/token \
  -H "Content-Type: application/json" \
  -d '{"grant_type": "refresh", "refresh_token": "<refresh_token>"}'
Refresh tokens are single-use — each refresh returns a new refresh token (rotation).

6. Logout

Revoke the refresh token when the user logs out:
curl -X POST https://api.getfloo.com/v1/auth/apps/{app_id}/session/logout \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "<refresh_token>"}'

Convenience endpoint

If you don’t want to decode the JWT yourself, use the session endpoint:
curl https://api.getfloo.com/v1/auth/apps/{app_id}/session/me \
  -H "Authorization: Bearer <access_token>"
Returns the authenticated user’s info.

Constructing redirect URIs in your app

Your app needs to generate the correct redirect_uri when starting the login flow. Behind floo’s edge proxy, your app receives the public hostname in the X-Forwarded-Host header. Node.js / Express:
app.get('/login', (req, res) => {
  const host = req.headers['x-forwarded-host'] || req.headers.host;
  const redirectUri = `https://${host}/callback`;
  res.redirect(
    `https://api.getfloo.com/v1/auth/apps/${process.env.FLOO_APP_ID}/authorize?redirect_uri=${encodeURIComponent(redirectUri)}`
  );
});
Python / FastAPI:
@app.get("/login")
async def login(request: Request):
    host = request.headers.get("x-forwarded-host", request.headers.get("host"))
    redirect_uri = f"https://{host}/callback"
    return RedirectResponse(
        f"https://api.getfloo.com/v1/auth/apps/{os.environ['FLOO_APP_ID']}/authorize"
        f"?redirect_uri={quote(redirect_uri)}"
    )
This works correctly in all environments (local dev, dev deploy, production) because the redirect URI is derived from the actual request hostname rather than hardcoded.

Troubleshooting

INVALID_REDIRECT_URI

The redirect URI your app sends to the authorize endpoint must exactly match one of the URIs in your [auth] redirect_uris config. Common causes:
  1. Wrong hostname — dev deploys use <app>-dev.on.getfloo.com, not <app>.on.getfloo.com
  2. Wrong protocol — deployed apps must use https://, not http://
  3. Trailing slash mismatch/callback is not the same as /callback/
  4. Not yet deployed — redirect URI changes in floo.app.toml only take effect after a deploy
The error message includes the attempted URI and the registered list so you can spot the mismatch.

NO_REDIRECT_URIS

You set access_mode = "accounts" but didn’t add [auth] redirect_uris in your config, or you haven’t deployed since adding them.

Access modes

ModeDescriptionPlanBest for
publicNo auth, anyone can accessAllMarketing sites, open APIs
passwordShared app passwordPro+Private demos, client previews
accountsPer-user auth via hosted OAuthPro+Apps with named end users
ssoEnterprise SSO via SAML/OIDC (coming soon)EnterpriseEnterprise apps

Password-protected apps

floo apps password my-app

Environment overrides

Override access mode per environment:
[app]
name = "my-app"
access_mode = "accounts"

[environments.dev]
access_mode = "public"
Resolution order: [environments.dev].access_mode wins over [app].access_mode.

Config File Spec

Full reference for all config fields and precedence.

Team Access

Org membership, invites, and permissions.