# Set up agent and app access

Let AI agents and other companies' apps act for one of your users, with that person's approval, through BuildBase's OAuth 2.0 server.

Your users may want an AI agent, such as Claude, ChatGPT or Cursor, or another company's app to work in your app for them: read their notes, file a report, start a job. BuildBase is an **OAuth 2.0 server** for every app, so the agent can ask for that access the standard way. The person signs in on your hosted sign-in page, sees a consent screen that names the agent and what it wants, and presses **Allow access**. The agent then acts as that person and can never do more than they can. They can take the access back at any time from your app.

> **Note:**
  This is not a way for people to sign in, and it adds no button to your sign-in
  page. People still sign in with the methods you already offer. This page is
  about what they let an agent or an app do afterwards.


> **Note:**

- A BuildBase plan that includes the OAuth 2.0 server, for the client the agent uses, and one that includes agent readiness, for the switches on the **Agent readiness** tab. Your plan's limit on connected agents applies to each new one a person allows.
- A token address on your own server (the **Application token URL**), reachable from the internet. BuildBase asks it for the agent's token after each approval, so your server decides what the token is.
- For agents that call tools in your app, an MCP server on your side. The SDK has one: see [MCP and agents](/reference/mcp-agents).



## Step by step

There are two ways for an agent to get a client: you create one for an agent or app you know, or you let agents register themselves. Both use the consent screen.

### 1. Create a client for the agent

1. In the BuildBase console, press **Users** in the left-hand bar (the **User management** area), then **Authentication** under **Access**, and the **Settings** tab.
2. Press **Create auth client**. Give it a **Title**, such as `Acme Assistant`: it is the name people see on the consent screen. Choose the **OAuth 2.0 provider** type, fill in the **Application token URL** (`https://app.acme.example/api/agent/token`) and the **Application profile URL**, and press **Create**.
3. Copy the client ID and the **Client secret** from the next dialog. It is shown once. Your token address checks every request from BuildBase with this secret. (The dialog is the same as for a [device client](/sign-in-setup/device-code#step-by-step).)
4. Press **Edit** on the new client. Under **Client kind**, choose **Agent (third-party)**. That is what makes people see the consent screen. An **Application (first-party)** client is your own software and signs people in without one.

![Client kind in the Edit auth setting sheet for Acme Assistant: Agent (third-party) chosen, for AI agents and external integrations that need the person to approve on a consent screen](/images/sign-in-setup/agent-access/console-client-kind.webp)

5. Fill in the **Application revoke URL** (`https://app.acme.example/api/agent/token/revoke`). When a person takes access back, BuildBase tells your server there, so it can stop accepting the tokens it gave out. It is optional, but without it a token you minted keeps working until it runs out. Keep **Require PKCE** ticked: agents prove each sign-in with a one-time secret of their own.

![The OAuth 2.0 settings of Acme Assistant: Application token URL and Application revoke URL on app.acme.example, Require PKCE, and Device code sign-in off](/images/sign-in-setup/agent-access/console-client-urls.webp)

6. Under **Redirect URLs**, press **Add redirect URL** and paste the address the agent gave you, such as `https://assistant.acme.example/oauth/callback`. People are sent back there after they allow or deny.

![Redirect URLs for Acme Assistant: https://assistant.acme.example/oauth/callback, where the person is sent back after they allow or deny](/images/sign-in-setup/agent-access/console-client-redirect.webp)

7. Under **Methods**, tick how people sign in before they approve, such as **Email and password**, and press **Update**.

Give the agent its **client ID**, its **secret** if it runs on a server it controls, and your OAuth 2.0 addresses (from the client's **Edit** sheet, under **OAuth 2.0 addresses**).

### 2. Turn on agent readiness

Open the **Agent readiness** tab. The card at the top explains the three steps: agents discover where your users sign in, get a client, and ask for consent.

![The Agent readiness tab: How AI agents connect to your app, in three steps: Discover, Get a client, Consent, where the agent acts as that user and never beyond their permissions](/images/sign-in-setup/agent-access/console-how-agents-connect.webp)

In the **Agent access** card:

| Setting                                    | What it does                                                                                                                                                                                         |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Agent readiness**                        | Publishes where your users sign in, so an agent can find your OAuth 2.0 addresses by itself                                                                                                          |
| **Let agents register themselves**         | Agents such as Claude or ChatGPT connect to your app on their own, with no client to create for each. A person still signs in and approves. Off until you turn it on (Dynamic Client Registration)   |
| **Base client for self-registered agents** | Every agent that registers itself is copied from this client: its token, profile and revoke URLs, its sign-in methods and its secret, so your server checks every agent with the one secret it holds |
| **Allow agent self-signup**                | Something else: lets your own server make user accounts for bots. Leave it off unless you run bots that need accounts. See [Agent accounts](/reference/mcp-agents#agent-accounts-without-a-browser)  |

Turn on **Agent readiness** and, if you want, **Let agents register themselves** with `Acme Assistant` as the base client, then press **Save changes**.

![The Agent access card: Agent readiness on, Allow agent self-signup off, Let agents register themselves on, and Acme Assistant picked as the base client for self-registered agents, with Save changes](/images/sign-in-setup/agent-access/console-agent-access.webp)

Further down, **Discovery URLs** lists the addresses agents read, each with a copy button. An MCP server built with the SDK points agents to them, so you rarely send them anywhere yourself.

![The discovery addresses agents read: the authorization server metadata, the issuer, and the client registration endpoint, each with a copy button](/images/sign-in-setup/agent-access/console-discovery.webp)

Self-registered agents do not crowd the **Settings** tab: they are hidden from the list until you pick **Self-registered by agents** in its **Source** filter. Each one is read-only and follows its base client.

![The clients on the Settings tab: Acme Assistant with the type Agent, and Acme CLI and Acme Analytics with the type App, each with its masked client ID and secret](/images/sign-in-setup/agent-access/console-clients.webp)

### 3. Your token address

After each approval, BuildBase asks your **Application token URL** for the token the agent will use, signed with the client's secret. The SDK has the whole handler:

```ts
// app/api/agent/token/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { handleAppTokenRequest, mintAgentToken } from '@buildbase/sdk';

export async function POST(request: NextRequest) {
  const { status, body } = await handleAppTokenRequest({
    authorization: request.headers.get('authorization'),
    clientSecret: process.env.BUILDBASE_AGENT_CLIENT_SECRET!,
    mintToken: (claims) =>
      mintAgentToken({ claims, secret: process.env.SYSTEM_SECRET! }),
  });
  return NextResponse.json(body, { status });
}
```

The revoke address has a matching `handleAppRevokeRequest({ authorization, clientSecret, onRevoke })`. [MCP and agents](/reference/mcp-agents) covers the MCP server and tools that use the token.

## Scopes

A scope is a name for one kind of access, such as `notes:read`. BuildBase passes the scope names an agent asks for through to the consent screen and to your token address, and your app decides what each one allows. The standard ones are said in words on the consent screen:

| Scope            | The consent screen says                   |
| ---------------- | ----------------------------------------- |
| `openid`         | Confirm who you are                       |
| `profile`        | See your name and profile                 |
| `email`          | See your email address                    |
| `offline_access` | Stay connected when you are not using it  |
| Your own         | The name as written, such as `notes:read` |

Name your own scopes in your MCP server's scope catalog, so agents can find them, and check them in each tool with `requiredScopes`.

## What people see

The agent opens your hosted sign-in page in the person's browser. They sign in as usual, then see the consent screen, named after the agent: what it will be able to do, **Allow access** and **Deny**.

![Acme Assistant wants to access your account: this will allow Acme Assistant to confirm who you are, see your name and profile and your email address, stay connected when you are not using it, and notes:read, with Allow access and Deny](/images/sign-in-setup/agent-access/hosted-consent.webp)

**Allow access** sends them back to the agent's redirect URL with a one-time code, which the agent exchanges for its tokens. **Deny** sends them back with `error=access_denied`, and nothing is recorded.

A self-registered agent's consent screen shows the name the agent gave itself:

![Notes Helper, an agent that registered itself, wants to access your account: confirm who you are, see your name and profile, and notes:read, with Allow access and Deny](/images/sign-in-setup/agent-access/hosted-consent-self-registered.webp)

On a phone:

![The consent screen on a phone for Notes Helper: Review the requested access, the list of what it may do, then Allow access and Deny](/images/sign-in-setup/agent-access/hosted-consent-phone.webp)

People are asked once for each scope an agent wants. When the same agent comes back for the same scopes, the consent screen is not shown again. When it asks for more, it is shown again. To be asked about everything again, the person disconnects the agent first. The consent screen follows the person's language, in any of the 8 languages the sign-in page speaks.

### Check it yourself

The sign-in page preview has no consent screen. To see the real one, have the agent connect, or open an authorization address in a browser. It needs a PKCE pair:

```bash
VERIFIER=$(openssl rand -hex 32)
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
echo "https://api.console.buildbase.app/api/v1/auth/oauth2/authorize?org=$BUILDBASE_ORG_ID&client_id=$AGENT_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fassistant.acme.example%2Foauth%2Fcallback&scope=openid%20profile%20notes%3Aread&state=check&code_challenge=$CHALLENGE&code_challenge_method=S256"
```

## In your app (SDK)

People see and take back the access they gave on the **Connected agents** screen of your app's settings dialog. Each agent shows the access it was given and when, with **Disconnect**.

![The Connected agents screen in the app: Acme Assistant and Notes Helper, each with the access it was given, when, and a Disconnect button](/images/sign-in-setup/agent-access/sdk-connected-agents.webp)

**Disconnect** asks first, naming the agent, and **Cancel** leaves it connected.

![The question Disconnect asks: "Disconnect Acme Assistant? It loses access to your account at once. You can connect it again at any time." with Cancel and Disconnect](/images/sign-in-setup/agent-access/sdk-disconnect-confirm.webp)

Once confirmed, it takes effect at once: the agent's grant and every token it holds for that person are deleted, its refresh token stops working, and BuildBase calls your **Application revoke URL** so your server can stop accepting the tokens it minted.

![Connected agents after Disconnect: only Notes Helper is left; Acme Assistant lost its access at once](/images/sign-in-setup/agent-access/sdk-after-disconnect.webp)

Open it from your own button, or put the screen anywhere with `ConnectedAgents`:

```tsx
// components/agent-access.tsx
import { ConnectedAgents, useSaaSAuth } from '@buildbase/sdk/react';

export function ManageAgentsButton() {
  const { openWorkspaceSettings } = useSaaSAuth();
  return (
    <button onClick={() => openWorkspaceSettings('connected-agents')}>
      Connected agents
    </button>
  );
}

export function AgentAccessPage() {
  return ;
}
```

`useConnectedAgents()` gives `agents`, `loading`, `error` and `revoke(clientId)` for a screen of your own. Pass `mcp={{ url: 'https://app.acme.example/mcp', name: 'Acme' }}` to `SaaSOSProvider` to add a **Connect an agent** guide to the screen. To hide the screen, pass `ui={{ settings: { sections: { 'connected-agents': false } } }}`.

## Check it works

- [ ] An authorization request for your agent client shows the consent screen after sign-in, named after the client.
- [ ] **Allow access** returns a code to the redirect URL, and the token exchange answers with an `access_token` your server minted.
- [ ] The person's **Connected agents** screen lists the agent.
- [ ] After **Disconnect**, the agent's refresh token is refused and your revoke address was called.

## Things to watch out for

- **People are asked again only for new access.** An agent that comes back for the same scopes gets them without the consent screen. One that asks for more shows it again. To be asked about everything again, the person disconnects the agent first.
- **A self-registered agent chooses its own name.** Nothing checks it, so anyone can register an agent named after a well-known one. Leave **Let agents register themselves** off unless any agent may ask your users for access.
- **An agent's refresh token works once.** Each refresh gives a new one. Using an old one again, for example from two copies of the agent at once, ends every token in that chain, and the person connects the agent again. An agent's refresh token also expires after 30 days unused.
- **If your token address fails during a refresh, the agent has to reconnect.** BuildBase spends the refresh token before it asks your server for the new access token.
- **Your token address has 10 seconds and gets no redirects.** BuildBase does not follow a redirect, so an address that redirects, such as `http://` to `https://`, fails with `503 temporarily_unavailable`.
- **Without a revoke URL, disconnecting does not stop tokens you minted.** They keep working until they expire.

## If something goes wrong

| What you see                                                                                      | Why                                                                                                          | What to do                                                                     |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| No consent screen; the agent gets a code straight away                                            | The client is **Application (first-party)**, or the person already allowed this agent everything it asks for | Choose **Agent (third-party)**. To ask again, the person disconnects the agent |
| `error=invalid_request`, "code_challenge is required (PKCE)", at the redirect URL                 | **Require PKCE** is on and the request has no `code_challenge`                                               | Send a PKCE pair, as every agent should                                        |
| "Connecting new agents is not available right now."                                               | Your plan's limit on connected agents is reached                                                             | Raise the plan, or have people disconnect agents they no longer use            |
| `503 temporarily_unavailable`, "The application server failed to issue a token. Try again later." | Your **Application token URL** did not answer with a token                                                   | Check the route is reachable and checks with this client's secret              |
| `400 invalid_grant`, "Invalid refresh token"                                                      | The person disconnected the agent                                                                            | The agent asks the person to connect again                                     |
| The agent cannot register itself                                                                  | **Let agents register themselves** is off, or no base client is picked                                       | Turn it on and pick a base client on the **Agent readiness** tab               |

## Next steps

- [MCP and agents](/reference/mcp-agents) - the MCP server, tools, scopes and the token in your app.
- [Device code sign-in](/sign-in-setup/device-code) - the same OAuth 2.0 server, for devices without a browser.
- [Authentication overview](/authentication/overview#oauth-2-0-authorization-server) - every part of the OAuth 2.0 server.
- [Set up sign-in, step by step](/sign-in-setup/overview) - every sign-in guide, and the order to set them up in.
