Brod

Guide

Authentication for AI-generated apps

Your agent built the app. Now it needs a login. The safest login for an AI-built internal tool is one the agent never writes: sign-in happens in front of the app, and the app is told who is there.

Remove the login page and the users table. Read the signed-in person from the x-brod-* headers. Only editors and owners can approve; viewers just see the list. Then deploy to Brod and share it with the finance group as viewers.

get_workspace · deploy_app · set_access

Done. The approve endpoint now returns 403 unless x-brod-role is owner or editor. Sharing with the finance group needs your confirmation: open this link.

Why an agent-written login is risky

Ask a coding agent to "add login" and it will. You usually get one of these:

  • Users and passwords in the code

    A form that checks against a list of users and passwords in the code.

  • A login written from scratch

    A user table, password hashing and a session cookie, written from scratch.

  • A third-party library with pasted keys

    A third-party auth library wired up with keys pasted into the source.

Each can work on the day. The trouble is everything around it: password storage, reset emails, session expiry, removing someone who left, and checking every route rather than just the front page. And the next time the agent edits the app, it can break any of that without anyone noticing. For a tool ten people use, that is a lot of security code to own.

The alternative is to take sign-in out of the app completely.

Sign-in at the gateway

On Brod, every request to an app goes through Brod's gateway first. The gateway works out who the visitor is, checks the app's access mode and grants, and only then forwards the request. Someone the app is not shared with gets the same "App not found" page as an address with no app behind it. Their request never reaches your code.

  • One sign-in per workspace

    People sign in once per workspace with an emailed link, or with Google where the server has it set up. There are no user passwords. The link is valid for 15 minutes and opens a page with a Continue button, so mail scanners do not use it up. That one sign-in covers every app in the workspace.

  • A session per app

    Each app gets its own session cookie for its own host. Signing out of any app ends the platform session and every app session made from it. Removing someone from the workspace ends their access to every app on their next request.

The rule Brod gives builders and agents is short: do not build a login page or a user table. Put that line in your agent's instructions for the project.

Reading the signed-in person in the browser

Every app host serves a small client script. Load it, then ask who is there:

When nobody is signed in, /_brod/me answers 401 with the code signed_out and a sign-in path. Brod.signIn() and Brod.signOut() send the person to /_brod/signin and /_brod/signout.

The token field is the identity signed for this app with HMAC-SHA256. It is valid for 5 minutes.

<script src="/_brod/client.js"></script>
<script type="module">
  const me = await Brod.me();   // null when nobody is signed in
</script>

Brod.me() reads GET /_brod/me, which returns:

{ "user": { "id", "email", "name" }, "role", "groups": [...], "app", "workspace", "token" }

Use the browser value to show a name or hide buttons a person cannot use. Do not rely on it to protect data. Anything that changes data should be checked on the server.

Reading the signed-in person on the server

An app with server code (a _worker.js file) gets the identity as request headers:

HeaderWhat it holds
x-brod-user-idThe person's id
x-brod-user-emailTheir email
x-brod-user-nameTheir name, URI-encoded
x-brod-roleTheir role in this app
x-brod-groupsTheir groups, comma-separated
x-brod-identityThe signed identity token

The gateway strips any x-brod-* header a visitor sends before it adds its own, so the app can trust these. This example from Brod's docs stores a note against the person who wrote it:

// _worker.js
export default {
  async fetch(request, env, ctx) {
    const email = request.headers.get('x-brod-user-email');       // the signed-in person (absent for anonymous visitors)
    await env.DB.run('CREATE TABLE IF NOT EXISTS notes (by TEXT, text TEXT)');
    if (request.method === 'POST') await env.DB.run('INSERT INTO notes VALUES (?, ?)', [email, await request.text()]);
    const notes = await env.DB.query('SELECT * FROM notes');       // rows as objects
    console.log('notes', notes.length);                            // shows in get_logs
    return Response.json({ notes, stripe: Boolean(env.STRIPE_KEY) });
  },
};

Roles and groups

Each app declares its own roles, most rights first. The default is owner, editor and viewer. Grants give a role to a person (by email), a group, an email domain, or everyone in the workspace. The app's owner and workspace admins always get the top role. Groups are set up by workspace admins.

Brod tells the app the role. The app decides what it allows.

Brod does not enforce app roles inside the app. If only editors may approve an expense, the server code must read x-brod-role and refuse the change for anyone else. Brod also cannot yet block a path by role, so every route that writes data needs its own check.

One more server-side check: all apps share one site for browser purposes, so a cookie's SameSite=Lax setting does not stop a page in another app posting a form to yours. Apps that change data on POST should check the Origin header or use a CSRF token.

Who gets in at all: access modes

Roles decide what a person can do inside the app. The access mode decides who reaches it. There are six:

  • For an internal tool, people or workspace is usually right.
  • Note that signed_in is wider than it sounds.
  • Access can change at any time without a redeploy.
  • When an agent asks to widen access, Brod returns a confirmation link and nothing changes until a person opens it.

What to tell your agent

get_workspace tells the agent the workspace's groups, access modes and how an app reads the signed-in person, so it has what it needs before it writes the code.

Questions

Can I use company SSO such as Okta or Azure AD?

Not today. Sign-in is an emailed link or Google. SAML, OIDC with your own identity provider, SCIM and LDAP are on Brod's roadmap, not in the product.

Does a static app with no server code get sign-in?

Yes. The gateway checks access for every request, static files included, and the page can call Brod.me(). But a static app has no database, so anything that stores data needs server code.

Can a visitor fake the identity headers?

No. The gateway removes any x-brod-* header a visitor sends and sets its own.

What do people outside the company use?

Invite them as guests to one app, or set the app to password mode and share the address and password. Invitations from an agent wait for a person to confirm.

Your AI already built the app. Put it in your team's hands.

ModeWho can open the app
privateThe app's owner and workspace admins. The default for a new app.
peopleOnly the people, groups and email domains granted a role.
workspaceEveryone in the workspace (guests only if granted).
passwordAnyone with the address and the app's password, without an account.
signed_inAnyone signed in to Brod, in any workspace. On brod.sh, that means anyone with a brod.sh account.
publicAnyone, without signing in.