Skip to content
Back to skills

Auth Setup

ASecurity

Use when configuring OAuth providers (Google/GitHub/Apple/X/etc.), setting up post-login auth hooks, tuning JWT lifetimes, or generating service API keys

  • 533 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added June 1, 2026
ai-agentsgogitapidatabasefrontendci/cdsecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned June 1, 2026

npx -y skills add butterbase-ai/butterbase-skills --skill auth-setup --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Auth Setup?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Auth Setup
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/butterbase-ai-auth-setup/badge)](https://www.skillsdirectory.com/skills/butterbase-ai-auth-setup)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: auth-setup
description: Use when configuring OAuth providers (Google/GitHub/Apple/X/etc.), setting up post-login auth hooks, tuning JWT lifetimes, or generating service API keys
---

# Butterbase Auth Setup

Two umbrella tools cover end-user authentication:

- **`manage_oauth`** — provider configuration (Google, GitHub, Apple, X, custom)
- **`manage_auth_config`** — auth hooks, JWT lifetimes, service key generation

For broad app build-out, see also `butterbase-skills:build-app`. This skill is the deep dive.

---

## 1. The role model

Every request runs under one of three database roles:

| Auth header | Role | `current_user_id()` | RLS |
|-------------|------|---------------------|-----|
| _none_ | `butterbase_anon` | NULL | enforced; default deny |
| End-user JWT (issued by `manage_oauth` or email login) | `butterbase_user` | user UUID | enforced |
| Service key (`bb_sk_*`) | `butterbase_service` | NULL | bypassed |

Auth is what transforms a request into the right role. RLS is what filters the data. Both must be configured.

---

## 2. Configure an OAuth provider

```js
manage_oauth({
  app_id: "app_abc123",
  action: "configure",
  provider: "google",
  client_id: "123456789.apps.googleusercontent.com",
  client_secret: "GOCSPX-...",
  redirect_uris: ["https://api.butterbase.ai/auth/app_abc123/oauth/google/callback"]
  // scopes / authorization_url / token_url / userinfo_url / provider_metadata are auto-filled for built-in providers
})
```

**Built-in providers (URLs and scopes pre-filled):** `google`, `github`, `discord`, `facebook`, `linkedin`, `microsoft`, `apple`, `x`.

**Custom providers:** pass `authorization_url`, `token_url`, `userinfo_url`, and `scopes` explicitly.

### Redirect URI format

```
https://api.butterbase.ai/auth/{app_id}/oauth/{provider}/callback
```

Register **this exact URI** in the provider's developer console. Mismatch is the most common reason OAuth flows fail.

### Provider quirks

| Provider | Quirk |
|----------|-------|
| `apple` | Requires `provider_metadata: { teamId, keyId, privateKey }`. Apple only returns the user's name on **first** auth and uses POST callback (handled automatically). |
| `x` | Does not return email. Butterbase synthesises `{username}@users.noreply.x.local` for the user record. |
| `facebook` | Default scopes `email`, `public_profile`. |
| `google` | Standard. |
| `github` | Standard. |

### List, update, delete

```js
manage_oauth({ app_id, action: "get" })                          // list all providers (secrets redacted)
manage_oauth({ app_id, action: "get", provider: "google" })      // single provider
manage_oauth({ app_id, action: "update", provider: "google", client_secret: "new-secret" })
manage_oauth({ app_id, action: "delete", provider: "google" })   // disables future logins; existing sessions valid until expiry
```

### Frontend flow

```
GET https://api.butterbase.ai/auth/{app_id}/oauth/{provider}?redirect_to=https://yourapp.com/auth/callback
```

User signs in at the provider, gets bounced back to `redirect_to` with `access_token` and `refresh_token` as query params. The Butterbase SDK wraps this:

```ts
await client.auth.signInWithOAuth({ provider: "google" });
const { user, accessToken } = await client.auth.getSession();
```

---

## 3. Tune JWT lifetimes

```js
manage_auth_config({
  app_id: "app_abc123",
  action: "update_jwt",
  accessTokenTtl: "15m",       // formats: "15m", "1h", "2h", "1d"
  refreshTokenTtlDays: 30      // integer days
})
```

Defaults: 15-minute access tokens, 7-day refresh tokens.

| Use case | `accessTokenTtl` | `refreshTokenTtlDays` |
|----------|------------------|------------------------|
| High-security (banking, admin) | `5m`–`15m` | `1`–`7` |
| Standard SaaS | `15m` (default) | `30` |
| Low-friction consumer apps | `1h` | `90` |

**Important:** changes apply only to **new** tokens. Active tokens keep their original expiration — there is no global revoke. Treat TTL changes as forward-looking only.

---

## 4. Auth hooks (run code after every login)

A post-auth function is a deployed Butterbase function invoked **fire-and-forget** after every successful auth event (OAuth login, email login, email signup).

### Wire it up

```js
// 1. Deploy the function first (see butterbase-skills:function-dev)
deploy_function({
  app_id: "app_abc123",
  name: "after-auth",
  code: postAuthHandlerCode,
  trigger: { type: "http", config: { auth: "none" } }
})

// 2. Register it as the auth hook
manage_auth_config({
  app_id: "app_abc123",
  action: "configure_auth_hook",
  post_auth_function: "after-auth"
})

// To remove the hook later: pass post_auth_function: null
```

The function **must already exist** when you configure the hook.

### Payload shape

The function receives a POST with this body:

```json
{
  "event": "oauth_login | login | signup",
  "user": {
    "id": "uuid",
    "email": "...",
    "provider": "google | github | email | ...",
    "display_name": "...",
    "avatar_url": "..."
  },
  "isNewUser": true,
  "provider": "google"
}
```

The function runs as `butterbase_service` (RLS bypassed, `ctx.user` is `null`). Use `body.user.id` to know who just logged in.

### Common uses

```ts
// after-auth/index.ts
export async function handler(req, ctx) {
  const { user, isNewUser, event } = await req.json();

  if (isNewUser) {
    // 1. Create profile row
    await ctx.db.query(
      "INSERT INTO profiles (user_id, display_name) VALUES ($1, $2) ON CONFLICT DO NOTHING",
      [user.id, user.display_name]
    );

    // 2. Send welcome email (via env-stored API key)
    ctx.waitUntil(sendWelcomeEmail(ctx.env.RESEND_API_KEY, user.email));
  }

  // 3. Audit log on every login
  await ctx.db.query(
    "INSERT INTO login_log (user_id, event, provider) VALUES ($1, $2, $3)",
    [user.id, event, ctx.user ?? null]
  );

  return new Response("ok", { status: 200 });
}
```

> Auth hooks are fire-and-forget. Don't return data the user needs — they won't see it. Use them for side effects only.

---

## 5. Service keys (`bb_sk_*`)

Service keys grant **full access** to all your apps and bypass RLS. Treat them like passwords.

### Generate

```js
manage_auth_config({
  action: "generate_service_key",
  name: "CI/CD pipeline"
})
// → { key: "bb_sk_a1b2c3...", key_id, prefix, name, created_at }
```

> The full key is returned **once**. Store it immediately in your secret manager — you cannot retrieve it again. If you lose it, generate a new one and revoke the old.

### List & revoke

```js
manage_api_keys({ action: "list" })
manage_api_keys({ action: "revoke", key_id: "uuid-..." })
```

`list` returns metadata only (prefix, name, last_used_at), never the secret. `revoke` is **immediate and irreversible**.

### Rotation workflow

1. `manage_auth_config` (`generate_service_key`) — create the new key.
2. Update CI/CD, MCP config, scripts to use the new key.
3. Verify with a smoke test (e.g. `manage_app` `list`).
4. `manage_api_keys` (`revoke`) — kill the old key.

Do steps 1–3 **before** step 4 to avoid downtime.

---

## 6. Anti-patterns

| Don't | Do |
|-------|----|
| Hardcode `bb_sk_*` keys in client code or commit them to git | Store in env vars / secret manager |
| Reuse one OAuth app between dev, staging, prod | Separate OAuth apps per environment, with their own redirect URIs |
| Use a service key from frontend code "for convenience" | Frontends use end-user JWTs; service keys are server-only |
| Increase `accessTokenTtl` to "fix" frequent re-auth | Use the refresh token; SDK handles this automatically |
| Forget that `manage_oauth` `delete` only stops *new* logins | Existing sessions remain valid until they expire — rotate JWT keys via support if you need a hard kill |
| Put critical logic in the auth hook | Hooks are fire-and-forget. Errors don't surface to the user. Keep them to side effects. |
| Log the full service key in audit / debug output | Log only the prefix (`bb_sk_a1b2c3`) — secrets must never appear in logs |

---

## 7. Quick reference

| Task | Tool |
|------|------|
| Add Google OAuth | `manage_oauth` (`configure`) |
| List OAuth providers | `manage_oauth` (`get`) |
| Set post-login hook | `manage_auth_config` (`configure_auth_hook`) |
| Change JWT lifetimes | `manage_auth_config` (`update_jwt`) |
| Create service key | `manage_auth_config` (`generate_service_key`) |
| List service keys | `manage_api_keys` (`list`) |
| Revoke service key | `manage_api_keys` (`revoke`) |

---

If a `docs/butterbase/00-state.md` exists in the working directory, prefer invoking via `/butterbase-skills:journey-auth` so the journey orchestrator stays in sync.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.