Skip to main content
The most valuable software your team runs is the software you build for yourselves — the lookup tool, the admin panel, the operations dashboard, the approval flow. floo is built to make that the easy path, not the hard one.

The problem with “just ship an internal tool”

Normally, shipping a tool for your team looks like this:
  1. Build the app.
  2. Figure out where to host it.
  3. Put auth in front of it.
  4. Figure out who gets access, and how they’re provisioned.
  5. Keep it running.
Steps 2–5 are the actual project. floo collapses them into one config file.

The floo flow

1. Build the app

Any web service (Node, Python, Go, Rust, static) with a Dockerfile.

2. Turn on managed auth

In floo.app.toml:
[app]
name = "team-directory"
access_mode = "accounts"

[services.web]
type = "web"
port = 3000
ingress = "public"

[auth]
redirect_uris = [
  "https://team-directory-dev.on.getfloo.com/callback",
  "https://team-directory.on.getfloo.com/callback"
]

3. Restrict sign-in to your team

In the dashboard, under the app’s Users tab, add your company email domain to the allowlist (e.g., acme.com). Only people with an @acme.com email can sign in.

4. Deploy

git push origin main
floo deploys watch --app team-directory
Once it’s live, share the URL. Teammates click sign-in, authenticate with their work email, and they’re in. No provisioning tickets, no new password, no access-control project.

What your app code does

When a signed-in user hits your app, floo attaches an Authorization: Bearer <jwt> header on every request. Verify it:
import { jwtVerify, createRemoteJWKSet } from 'jose';

const JWKS = createRemoteJWKSet(
  new URL(`https://api.getfloo.com/v1/auth/apps/${process.env.FLOO_APP_ID}/.well-known/jwks.json`)
);

export async function whoami(req) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  const { payload } = await jwtVerify(token, JWKS);
  return { id: payload.sub, email: payload.email, name: payload.name };
}
The JWT gives you sub (user ID), email, name, and avatar_url. That’s all you need to build a per-user tool. See managed auth for the full OAuth flow if you want server-side session handling instead.

Promoting to production

After the app is good in dev, promote it:
floo releases promote --app team-directory --tag v1
Your team now uses the production URL at team-directory.on.getfloo.com. The dev URL stays live for you to iterate without affecting the version your team relies on.

Who is using it

The app’s Users tab shows everyone who has signed in: email, first seen, last active, and how often. The org-level Users page rolls that up across every internal tool you’ve shipped. This is a side effect of using managed auth — you get usage data without building analytics.

Managed auth reference

The full OAuth flow, JWT claims, and refresh token handling.

Custom domains

Put the internal tool on tools.yourcompany.com instead of the default floo URL.