> ## Documentation Index
> Fetch the complete documentation index at: https://docs.frogbot.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth sign-in

> Add collection-scoped OAuth login with identity pieces.

Add OAuth pieces to a collection's `auth.signIn` array to let users sign in through a provider. FrogBot matches the local user by email, or creates one when the collection's [`create` access](#who-can-sign-up) allows it, and issues that collection's session.

## Enable Google sign-in

Install the identity-only Google piece:

```bash theme={"theme":"tokyo-night"}
pnpm add @frogbotai/piece-google
```

```ts theme={"theme":"tokyo-night"}
import { createGoogle } from '@frogbotai/piece-google'
import type { CollectionConfig } from 'frogbot'

export const google = createGoogle({
  oauth: {
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!
  }
})

export const Users: CollectionConfig = {
  slug: 'users',
  auth: { signIn: [google] },
  fields: []
}
```

Save this as `collections/Users.ts` and add `Users` to your root `collections`.

`createGoogle()` accepts these options:

| Option | Type | Default | Description |
| - | - | - | - |
| `oauth` | `{ clientId: string; clientSecret: string }` | Required for sign-in | Your Google OAuth app. Scopes that a product such as Gmail adds with its own `scopes` option never reach sign-in. |
| `scopes` | Scope names, or `({ defaultScopes }) => names` | None | Google identity has no optional scopes. Sign-in always requests the identity scopes below, whatever this lists. |
| `slug` | `string` | `'google'` | Instance slug used in the sign-in routes. Set a different slug to register two Google instances. |
| `auth` | `{ accessToken: string }` | — | Static credential for the piece's client. Sign-in doesn't use it. |

The Google piece requests `openid`, `https://www.googleapis.com/auth/userinfo.email`, and `https://www.googleapis.com/auth/userinfo.profile`. It has no product actions or product scopes. Its account lookup uses Google's userinfo endpoint and only supplies `account.email` for a provider-verified email.

The admin login page automatically renders methods from the collection selected by `admin.user`. With multiple auth collections, set `admin.user` explicitly. You can reuse the same Google instance in another collection's `auth.signIn`; each collection has its own routes and local users.

### The first user

While the admin user collection has no users, the admin sends everyone to `/admin/create-first-user`. With sign-in methods configured, that page shows the sign-in buttons below the password form, so the first person can also continue with Google. The first person to finish either one becomes the first user. With the [OAuth-only setup](#collection-requirements), the admin shows the login page instead, and the first person to sign in becomes the first user.

**On an empty deployed app, that can be anyone who opens the URL.** Sign in yourself before you share it.

## Register the callback

For the example above, register this redirect URI in your Google OAuth app:

```text theme={"theme":"tokyo-night"}
https://app.example.com/api/users/sign-in/google/callback
```

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/:collection/sign-in/:piece` | Start sign-in without requiring an existing session. |
| `GET` | `/api/:collection/sign-in/:piece/callback` | Verify the OAuth response, resolve the user, issue a session cookie, and continue to `returnTo`. |

For sign-in, `:piece` is the configured instance's `slug`, which defaults to the piece slug. Unknown methods return `404`. Callback origins use `serverURL` when configured, otherwise the request origin. Replace `/api` with your configured `routes.api` in requests and registered redirect URIs.

A custom login page can link to the start endpoint:

```html theme={"theme":"tokyo-night"}
<a href="/api/users/sign-in/google?returnTo=/dashboard">Continue with Google</a>
```

`returnTo` must resolve to the same origin as the callback. Without it, the admin user collection returns to `routes.admin`; other collections return to `/`. OAuth state expires after ten minutes, is bound to the initiating browser and exact login route, and can be used only once. PKCE is used when the piece recipe sets `pkce: true`.

## Identity and local verification

Every login matches by email within the target collection. Provider account IDs and connection rows are not login identifiers. FrogBot trims and lowercases the provider email, validates its format, and requires an unambiguous local match.

For piece authors, `oauth.account({ tokens, client, req })` returns `{ id, label, email? }`. **Only return `email` when the provider has verified that identity.** Omit it or reject the lookup when verification cannot be established. Core validates the email's format; the recipe is responsible for proving provider verification. A missing email cannot sign a user in.

| Local user state | Behavior |
| - | - |
| Existing user, `auth.verify` disabled | Sign in after the normal login checks. |
| Existing user, `auth.verify` enabled | Require `_verified === true`. OAuth does not verify an existing unverified account. |
| No matching user | If [`create` access](#who-can-sign-up) allows it, create a user with the normalized email and, unless the local strategy is off, a random password. When `auth.verify` is enabled, set `_verified: true`; suppress the verification email. |

User creation runs collection validation and hooks. Give required custom fields defaults or populate them in a hook. If creation fails, sign-in fails. Trashed users are excluded from identity lookup; duplicate or incompatible email identities are rejected.

Session issuance respects account lockout, collection session and cookie settings, and runs `beforeLogin` and `afterLogin`. The callback sets the session cookie and never returns provider tokens. It answers with a small page that continues to `returnTo` (a `<meta http-equiv="refresh">`) rather than a redirect: browsers mark the callback's load cross-site because it comes from the provider, and with `serverURL` set the session cookie doesn't count on a cross-site load, so a redirect's target would not see the user until a reload. The page's own navigation is same-origin, so `returnTo` sees the user on its first load.

For collections with sign-in methods, FrogBot coordinates OAuth and password login, logout, refresh, and password reset through the end of each operation. Operations serialize per collection to prevent concurrent requests from restoring revoked sessions. Use FrogBot's public API and normal auth endpoints; direct database operations bypass this coordination.

Persisted sessions require transactional storage. SQLite, PostgreSQL, and MongoDB replica sets are supported; session mutations fail rather than falling back to unsafe transactionless writes. Keep login hooks short. For long SQLite transactions, use an independent KV backend so the transaction cannot block renewal of the authentication lock.

The SQLite adapter enables transactions automatically when you configure sign-in methods. Explicitly disabling transactions with sign-in configured produces an initialization error.

## Who can sign up

Creating a user through sign-in follows the collection's `access.create`, the same rule as `POST /api/users`. It runs with no signed-in user, and `data` holds the normalized email. The default `create` access allows only a signed-in user, so **without `access.create`, only people who already have an account can sign in.**

```ts theme={"theme":"tokyo-night"}
// Anyone with a provider account
access: { create: () => true }

// Only your company's addresses
access: { create: ({ data }) => data?.email?.endsWith('@trend.com') === true }

// Only people an admin adds (the default)
access: {}
```

The rule also governs `POST /api/users`, so an open rule lets anyone create an account there too. An [`allow()`](https://docs.frogbot.ai/plugins/roles) rule needs a signed-in user, so it refuses every new person; open sign-up with a plain function. Refused people get a `403` with "This app doesn't let new people sign up. Ask an admin to add you." and nothing is created.

The first user is the exception, as with the create-first-user screen: while the collection has no users at all, counting trashed ones, the first person to sign in gets an account whatever `access.create` says. Sign-ins to a collection run one at a time, so two people signing in at once can't both become the first user.

On Dojo, the [Dojo plugin](https://docs.frogbot.ai/plugins/dojo.md#sign-in-with-dojo) adds "Continue with Dojo" to the user collection, and it follows these same rules.

## Collection requirements

* Each sign-in method needs factory OAuth app credentials and a recipe with an `account` function. Method slugs must be URL-safe and unique within the collection.

* Keep a unique, stored, non-localized email field. Sign-in requires email identity even if you also support usernames.

* For an OAuth-only collection, set `disableLocalStrategy: { enableFields: true }`. Identity and session fields remain, FrogBot authenticates the sign-in session cookie itself, and password login, forgot and reset password, unlock, and first-user registration are refused.

  ```ts theme={"theme":"tokyo-night"}
  export const Users: CollectionConfig = {
    slug: 'users',
    auth: {
      signIn: [google],
      disableLocalStrategy: { enableFields: true }
    },
    fields: []
  }
  ```

* `auth.strategies` authenticates later requests. `auth.signIn` configures interactive login methods.

## Share an app with Gmail connections

Gmail extends Google's OAuth recipe with mailbox scopes. Reuse the same app credentials while keeping login and Gmail authorization separate:

```ts theme={"theme":"tokyo-night"}
import { createGmail } from '@frogbotai/piece-gmail'
import type { ConnectionEntry } from 'frogbot'
import { google } from './collections/Users'

export const gmail = createGmail({ oauth: google.oauth })
export const connections: ConnectionEntry[] = [{ piece: gmail, oauth: true }]
```

Pass `connections` to your root config. Register both callback URIs in the shared Google app:

| Flow | Callback | Stored result |
| - | - | - |
| Google sign-in | `/api/users/sign-in/google/callback` | Local user and FrogBot session. |
| Gmail linking | `/api/connections/gmail/callback` | Owner-scoped, encrypted Gmail credential. |

Core auth and connections share private OAuth helpers, but their state intents and public callbacks are separate. **Login never stores provider tokens or creates connections; linking never logs a user in.** Users link Gmail separately in [Settings → Linked accounts](https://docs.frogbot.ai/connections/overview.md#link-an-account-in-settings).

For Sheets, Drive, and Calendar, use `createGoogleSheets`, `createGoogleDrive`, and `createGoogleCalendar` with the same `oauth: google.oauth` pattern. Each needs its own `connections` entry and product callback. The identity-only `google` instance does not grant product access. See [Google product connection setup](https://docs.frogbot.ai/connections/overview.md#configure-linking).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.