OAuth setup

SkillCommunication

The oauth-setup skill connects an OAuth 2.0 service such as Gmail or another Google API to your agent using the built-in oauth_authorize tool. It starts an authorization flow, gives the operator a click-to-authorize link, captures the returned code, and seals the tokens into the credential store.

Then ask your AI: use the OAuth setup skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Create a Desktop-app OAuth client in the provider console, enable the API you need, and keep the consent screen in Testing with yourself as a test user.

What your AI can do with it

  • Start an OAuth flow with oauth_authorize action start
  • Return a click-to-authorize link for the operator
  • Capture the redirect code and seal tokens in the credential store
  • List connected accounts with oauth_authorize action status
  • Fetch a live access token with automatic refresh
  • Call APIs such as Gmail send with the connected account

Getting started

  1. Create a Desktop-app OAuth client in the provider console, enable the API you need, and keep the consent screen in Testing with yourself as a test user.
  2. Give the agent the client id and client secret from that Desktop-app client.
  3. Start the flow with oauth_authorize action start, passing the provider, endpoints, client id, secret, and the real scopes.
  4. Send the returned authUrl to the operator to click, then call oauth_authorize action await with the flowId.
  5. If await returns status pending, tell the operator and call await again.

What this skill tells your AI

The instructions your AI receives, as published by spinabot/brigade in skills/oauth-setup/SKILL.md and read by Ahel’s review.

Use the oauth_authorize tool — NEVER hand-roll an http listener in bash. It opens a one-shot loopback listener on a free port (no EADDRINUSE, no port juggling), captures the code, and seals the tokens into the credential store.

The two mistakes to avoid (they cost a whole session once)

  1. Use a Desktop-app OAuth client, not a Web client. In Google Cloud Console → Credentials → Create OAuth client ID → Application type: Desktop app. Desktop clients accept ANY http://127.0.0.1:<port> loopback redirect with no redirect-URI registration. A Web client requires the exact redirect URI pre-registered, which is what causes redirect_uri_mismatch.
  2. Use the real scope. Gmail send is https://www.googleapis.com/auth/gmail.send (NOT https://gmail.com/..., which doesn't exist). Add openid https://www.googleapis.com/auth/userinfo.email if you want the account address auto-filled.

Flow

  1. Ask the operator to create a Desktop-app OAuth client (Gmail API enabled, consent screen in Testing with themselves as a test user) and paste the client id + secret.
  2. Start the flow:
    oauth_authorize({
      action: "start",
      provider: "google-gmail",
      authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
      tokenEndpoint: "https://oauth2.googleapis.com/token",
      userInfoEndpoint: "https://www.googleapis.com/oauth2/v3/userinfo",
      clientId: "<id>", clientSecret: "<secret>",
      scopes: ["https://www.googleapis.com/auth/gmail.send", "openid", "https://www.googleapis.com/auth/userinfo.email"],
      extraAuthParams: { access_type: "offline", prompt: "consent" }
    })
    
    access_type=offline + prompt=consent are required for a refresh token (otherwise re-auth is needed when the access token expires).
  3. Send the operator the returned authUrl to click.
  4. Await the redirect — it exchanges the code and seals the tokens:
    oauth_authorize({ action: "await", flowId: "<flowId from start>" })
    
    If it returns status pending, the operator hasn't clicked yet — tell them, then call await again.

Using a connected account (sending mail)

The tokens are sealed in the credential store — you can't read them from a file or the DB, and you shouldn't try. To use a connected account:

  1. Check what's connected: oauth_authorize({ action: "status" }) → lists each account (provider, email, scopes, expiry). No secrets.
  2. Get a usable token: oauth_authorize({ action: "token", provider: "google-gmail" }) → returns accessToken. It auto-refreshes from the sealed refresh token when the old one expired, so you always get a live token. (Pass provider only if more than one account is connected.)
  3. Call the API with it. Gmail send: POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send, header Authorization: Bearer <accessToken>, body { "raw": "<base64url RFC-822 message>" }.

Never cat the credential store, grep for the token, or hand-roll a refresh — action:"token" is the only retrieval path, and it keeps the refresh token + client secret sealed.

Signals

GitHub stars
11k
Forks
70
Last commit
Oct 2026

Questions

Why does the flow fail with redirect_uri_mismatch?
A Web OAuth client requires the exact redirect URI to be pre-registered. Use a Desktop-app client instead: it accepts any http://127.0.0.1:<port> loopback redirect with no redirect-URI registration.
Which scope should I use for Gmail send?
Use https://www.googleapis.com/auth/gmail.send. The scope https://gmail.com/... does not exist. Add openid and https://www.googleapis.com/auth/userinfo.email if you want the account address auto-filled.
Advanced
Item type
skill
Key
oauth-setup
Source
github.com/spinabot/brigade