# Sign in with Google, Apple and more

Source: https://dabadata.com/docs/sign-in-providers/ (HTML) - https://dabadata.com/docs/sign-in-providers/index.md (this page as markdown)

With dabadata's hosted sign-in, your app's users can sign in with an account they already have: Google, Apple, Microsoft, GitHub, GitLab, Discord, LinkedIn or Slack. Sign-in goes through **your own OAuth app** at each provider, so the provider's screens show your app's name and logo, not dabadata's. The result is an ordinary hosted user with the issuer `hosted`: rules, the trace and live subscriptions work exactly as after a password sign-in. It needs [hosted accounts](https://dabadata.com/docs/end-user-auth/index.md) turned on. For a quick start on a [dabadata site](https://dabadata.com/docs/sites/index.md), Google can also use [dabadata's Google app](https://dabadata.com/docs/sign-in-providers/index.md#dabadata-google), with no Google Cloud setup: it works on `*.dabadata.site` addresses only.

## Which providers

| Provider | What you need | Native apps ([`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native)) | The email is trusted when |
| --- | --- | --- | --- |
| [Google](https://dabadata.com/docs/sign-in-providers/index.md#google) `google` | Client id and client secret | Yes | Google says `email_verified` |
| [Apple](https://dabadata.com/docs/sign-in-providers/index.md#apple) `apple` | A Services ID only; no secret | Yes | Apple says `email_verified` (Hide My Email addresses too) |
| [Microsoft](https://dabadata.com/docs/sign-in-providers/index.md#microsoft) `microsoft` | Application (client) id and client secret; `tenant` | No | A personal Microsoft account, or a work account whose token carries `xms_edov` |
| [GitHub](https://dabadata.com/docs/sign-in-providers/index.md#github) `github` | Client id and client secret of an OAuth App | No | The account's primary email is verified on GitHub |
| [GitLab](https://dabadata.com/docs/sign-in-providers/index.md#gitlab) `gitlab` | Application id and secret, on gitlab.com | No | GitLab says `email_verified` |
| [Discord](https://dabadata.com/docs/sign-in-providers/index.md#discord) `discord` | Client id and client secret | No | The Discord account is verified |
| [LinkedIn](https://dabadata.com/docs/sign-in-providers/index.md#linkedin) `linkedin` | Client id and client secret | No | LinkedIn says `email_verified` |
| [Slack](https://dabadata.com/docs/sign-in-providers/index.md#slack) `slack` | Client id and client secret | No | Slack says `email_verified` |

A trusted email is what lets a provider account join an existing user. An email the provider does not vouch for signs nobody in: the answer is `email_unverified` ([Accounts and linking](https://dabadata.com/docs/sign-in-providers/index.md#linking)).

**Not yet:** Facebook (its login says nothing about whether the email was verified), X (it gives no email by default), Twitch, Spotify, Bitbucket, Notion, Zoom, an OpenID Connect provider of your choice, and SAML. Until then, a provider that issues JWTs can be trusted as an [issuer](https://dabadata.com/docs/identity/index.md#providers).

## How it works

1. The person presses "Continue with Google": on the hosted sign-in page, or a button of yours that calls [`db.auth.signInWithProvider("google")`](https://dabadata.com/docs/sign-in-providers/index.md#client).
2. The browser opens `https://dabadata.com/auth/<project id>/google?redirect=<your URL>`. dabadata keeps a sign-in state and sends the browser on to Google with your client id.
3. The person signs in at Google and agrees to share their name and email with your app.
4. Google sends the browser back to the callback URL, `https://dabadata.com/auth/<project id>/google/callback`. dabadata redeems the code with your client secret and reads who the person is.
5. That account is linked to a hosted user of the environment, an existing one or a new one ([Accounts and linking](https://dabadata.com/docs/sign-in-providers/index.md#linking)).
6. The browser goes back to your redirect URL with the tokens in the fragment: `https://tasks.example.com/#token=...&refreshToken=...&expiresAt=...&refreshExpiresAt=...`. Without a redirect, the hosted page says "You are signed in".
7. Your page calls `db.auth.completeLink()`: the client takes the tokens and clears the fragment, the same as after a hosted sign-in or a mailed link.

What is checked on the way:

- A sign-in state works once, for 10 minutes, and only in the browser that started it: an HttpOnly cookie ties the two together. A sign-in cannot be finished in another browser, or replayed.
- The code is bound to the state with PKCE where the provider supports it (Google, Microsoft, GitHub, GitLab).
- An ID token must name your client id as its audience, come from the provider's issuer, not be expired, and carry the state's nonce when it carries one. Apple's ID token passes through the browser, so it must carry the nonce and its signature is checked against Apple's keys too.
- The provider's addresses are fixed in dabadata, never read from your definition.

## Turn one on

**In the console:** open **Authentication** > **Sign-in methods** and pick the provider. The panel shows the [callback URL](https://dabadata.com/docs/sign-in-providers/index.md#callback) to register at the provider, for the environment you are in. Paste the client id and the client secret and save. The console stores the secret as the project secret `<PROVIDER>_CLIENT_SECRET` (`GOOGLE_CLIENT_SECRET`, `GITHUB_CLIENT_SECRET`, ...) and adds the provider to the definition. Do it per environment: test first, live when you are ready.

**With the API:** store the client secret as a [project secret](https://dabadata.com/docs/projects/index.md#secrets) of the environment first, then name it in `auth.users.providers` and apply the definition. The order matters: `PUT /v1/schema` refuses a provider whose secret is not set (`auth.users.providers.google: secret GOOGLE_CLIENT_SECRET is not set (store it with PUT /v1/project/secrets/GOOGLE_CLIENT_SECRET)`).

```bash
# 1. The client secret, as a project secret of this environment
curl -X PUT https://dabadata.com/v1/project/secrets/GOOGLE_CLIENT_SECRET \
  -H "Authorization: Bearer $DABADATA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value":"<the client secret from Google>"}'

# 2. The whole definition, with auth.users.providers: definition.json is { "definition": { ... } }
curl -X PUT https://dabadata.com/v1/schema \
  -H "Authorization: Bearer $DABADATA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d @definition.json
```

The `auth` part of that definition, with four providers:

```json
"auth": {
  "users": {
    "enabled": true,
    "defaultRole": "member",
    "redirectUrls": ["http://localhost:5173/", "https://tasks.example.com/"],
    "providers": {
      "google":    { "clientId": "1234567890-abc.apps.googleusercontent.com", "secret": "GOOGLE_CLIENT_SECRET",
                     "audiences": ["1234567890-ios.apps.googleusercontent.com"] },
      "apple":     { "clientId": "com.example.tasks.web", "audiences": ["com.example.tasks"] },
      "microsoft": { "clientId": "00000000-0000-0000-0000-000000000000", "secret": "MICROSOFT_CLIENT_SECRET",
                     "tenant": "common" },
      "github":    { "clientId": "Ov23liEXAMPLE0000000", "secret": "GITHUB_CLIENT_SECRET" }
    }
  }
}
```

| Key | Meaning |
| --- | --- |
| `clientId` | Required. Your OAuth app's client id at the provider, 1 to 200 characters without spaces. Google's ends in `.apps.googleusercontent.com`; Apple's is the Services ID. |
| `secret` | Hosted projects. The name of the project secret that holds the client secret. Required for every provider but Apple, which has none, and Google, which without one is accepted by [`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native) only (no button, not in `providers()`). |
| `secretEnv` | The default project of a self-hosted server only: the name of an environment variable that holds the client secret. Set one of `secret` and `secretEnv`. |
| `audiences` | Google and Apple only. Up to 10 more client ids that [`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native) accepts: the client ids of your Google iOS or Android clients, your iOS app's bundle id. Not the `clientId` again. |
| `tenant` | Microsoft only. `common` (the default), `organizations`, `consumers` or a tenant id ([which one](https://dabadata.com/docs/sign-in-providers/index.md#microsoft)). |

Unknown providers and unknown keys are errors. Live and test have their own definitions and their own secrets. Removing a provider stops new sign-ins with it; the accounts already linked keep the link, so adding it back lets them in again.

## The callback URL

Every provider asks where to send people back. That is the callback URL, one per environment and provider:

```
https://dabadata.com/auth/prj_example12345/google/callback          live
https://dabadata.com/auth/prj_example12345_test/google/callback     test
```

Each environment has its own callback URL. Register both at the provider, or make one OAuth app for test and one for live. Put the provider's id in place of `google`: `apple`, `microsoft`, `github`, `gitlab`, `discord`, `linkedin`, `slack`. On a self-hosted server it is `<DABADATA_PUBLIC_URL>/auth/<project name>/<provider>/callback`.

The steps below follow each provider's console at the time of writing. Providers move their menus around; the names of the fields stay much the same.

## Google

Callback URL: `https://dabadata.com/auth/<project id>/google/callback`

1. In the [Google Cloud console](https://console.cloud.google.com/), pick or create a project.
2. Set up the **OAuth consent screen** (in newer consoles **Google Auth Platform** > **Branding**): your app's name, a support email, your logo, your home page and privacy policy. Under **Authorized domains** add `https://dabadata.com` and your own domain.
3. Make the app **External** and publish it (**In production**). While it is in testing, only the test users you list can sign in.
4. **Credentials** > **Create credentials** > **OAuth client ID** > **Web application**. Under **Authorized redirect URIs** add the callback URL of each environment.
5. Copy the client id and the client secret into the console, or into `clientId` and the secret `GOOGLE_CLIENT_SECRET`.

dabadata asks for `openid email profile` only, scopes Google does not review. A logo may show only once Google has verified your app's branding; the name shows at once. For the Google button on your own page, or a mobile app, see [Native apps and your own buttons](https://dabadata.com/docs/sign-in-providers/index.md#native); an iOS or Android client id goes in `audiences`.

Your own Google app works on every address your app runs on: your `*.dabadata.site` address and your own domains.

## Google without your own app: dabadata's Google app

Instead of making a Google app, you can sign people in with dabadata's: Google's screen then says **dabadata site**, not your app's name. There is nothing to set up at Google.

> **Only on dabadata.site:** dabadata's Google app signs people in only when they go back to an `https://<name>.dabadata.site` address (or to the hosted sign-in page). **It does not work on your own domain.** Before you connect a custom domain to your site, switch to [your own Google app](https://dabadata.com/docs/sign-in-providers/index.md#google); the people who already signed in keep their accounts, because Google identifies a person the same way under every app.

- **In the console:** on the **Sites** page, press **Add Google sign-in**. It turns on Google with dabadata's app and adds your sites' addresses to the redirect URLs. Or open **Authentication** > **Sign-in methods** > **Google** and choose **dabadata's Google app**.
- **In the definition:** `"google": { "shared": true }` in `auth.users.providers`, with nothing else in it. There is no client id and no secret to store.

```
"providers": { "google": { "shared": true } }
```

While a project uses it, the console's Sites page says so on every site: the `dabadata.site` address shows "Google sign-in works", every custom domain shows "Google sign-in off here", and connecting a domain warns first.

- The hosted sign-in page shows "Continue with Google" only when the person will go back to a `*.dabadata.site` address. A sign-in that would end on another address is stopped before Google, with the error [`shared_google_domain`](https://dabadata.com/docs/sign-in-providers/index.md#errors).
- It works in the browser only. Native apps and Google's own button ([`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native)) need your own Google app.
- Sign-in runs through `auth.dabadata.site`, the address dabadata's Google app is registered with. There is no callback URL for you to register.

## Apple

Callback URL: `https://dabadata.com/auth/<project id>/apple/callback`. Apple posts the answer back to it (a form POST); dabadata takes care of that.

1. In your [Apple Developer](https://developer.apple.com/account/) account, open **Certificates, Identifiers & Profiles** > **Identifiers**. You need an App ID with **Sign in with Apple** on: your iOS app's, or a new one.
2. Add a **Services ID** (**Identifiers** > **+** > **Services IDs**). The description is your app's name; the identifier, say `com.example.tasks.web`, is the `clientId`.
3. Open the Services ID, tick **Sign in with Apple** and press **Configure**. Primary App ID: the one from step 1. **Domains and Subdomains**: `https://dabadata.com`. **Return URLs**: the callback URL of each environment.
4. Paste the Services ID in the console. There is no secret to add: dabadata checks Apple's signed ID token against Apple's public keys, so it needs no key file and no client secret.

- Apple tells your app the person's name only the first time they sign in to it. dabadata keeps it when it creates the user.
- A person may choose **Hide My Email**. You then get an address at `privaterelay.appleid.com`, which Apple counts as verified. To send mail to those addresses, register your sending domain with Apple's private email relay service.
- For Sign in with Apple in your iOS app, add the app's bundle id to `audiences` and use [`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native).

## Microsoft

Callback URL: `https://dabadata.com/auth/<project id>/microsoft/callback`

1. In the [Microsoft Entra admin center](https://entra.microsoft.com/), open **App registrations** > **New registration**. Name: your app's name.
2. Choose the **Supported account types**, and set `tenant` to match (table below).
3. **Redirect URI**: platform **Web**, the callback URL. Add the other environment's under **Authentication**.
4. **Certificates & secrets** > **Client secrets** > **New client secret**. Copy its **Value**, not its Secret ID. Client secrets expire: put a new one in before the date shown there.
5. For work and school accounts: **Token configuration** > **Add optional claim** > **ID** > `xms_edov` (why: below).
6. The **Application (client) ID** on the **Overview** is the `clientId`. Your logo goes under **Branding & properties**.

| Supported account types | `tenant` |
| --- | --- |
| Accounts in any organizational directory and personal Microsoft accounts | `common` (the default) |
| Accounts in any organizational directory (work and school only) | `organizations` |
| Personal Microsoft accounts only | `consumers` |
| Accounts in this organizational directory only (one company) | That directory's tenant id |

**Why `xms_edov`.** A work or school account's email is set by its organization's admins, and Microsoft does not check that the address is theirs. This is known as nOAuth: whoever runs any Microsoft Entra tenant can give an account there someone else's email, and an app that matches accounts by email lets them into that person's account. So dabadata trusts a work account's email only when the token says Microsoft verified that the email's domain belongs to the account's organization: the optional claim `xms_edov` is true. Without it, work accounts get `email_unverified`. Personal Microsoft accounts (outlook.com, hotmail.com and the like) are trusted as they are.

## GitHub

Callback URL: `https://dabadata.com/auth/<project id>/github/callback`

1. On GitHub, **Settings** > **Developer settings** > **OAuth Apps** > **New OAuth App**. For an organization, the same under the organization's settings.
2. Application name: your app's name. Homepage URL: your app. **Authorization callback URL**: the callback URL. An OAuth App holds one callback URL, so make one app for test and one for live.
3. **Generate a new client secret**, and copy it with the **Client ID**. Upload your logo on the same page.

dabadata asks for `read:user user:email` and uses the account's primary email; an unverified primary email gets `email_unverified`.

## GitLab

Callback URL: `https://dabadata.com/auth/<project id>/gitlab/callback`

1. On gitlab.com, **Preferences** (your user settings) > **Applications** > **Add new application**. A group has its own under the group's **Settings** > **Applications**.
2. Name: your app's name. **Redirect URI**: the callback URLs, one per line. Keep **Confidential** ticked. Scopes: `openid`, `email` and `profile`.
3. Save, then copy the **Application ID** (the `clientId`) and the **Secret**.

gitlab.com only for now; a self-managed GitLab is not supported yet.

## Discord

Callback URL: `https://dabadata.com/auth/<project id>/discord/callback`

1. In the [Discord Developer Portal](https://discord.com/developers/applications), **New Application** with your app's name. The icon goes under **General Information**.
2. **OAuth2** > **Redirects** > **Add Redirect**: the callback URL of each environment. Save.
3. On the same **OAuth2** page, copy the **Client ID**, and **Reset Secret** to get the client secret.

dabadata asks for `identify email`. A Discord account whose email is not verified gets `email_unverified`.

## LinkedIn

Callback URL: `https://dabadata.com/auth/<project id>/linkedin/callback`

1. In the [LinkedIn Developer portal](https://www.linkedin.com/developers/apps), **Create app**: your app's name, your company's LinkedIn Page and your logo.
2. **Products** > **Sign In with LinkedIn using OpenID Connect** > **Request access**.
3. **Auth** > **OAuth 2.0 settings** > **Authorized redirect URLs for your app**: add the callback URL of each environment.
4. On **Auth**, copy the **Client ID** and the **Primary Client Secret**.

## Slack

Callback URL: `https://dabadata.com/auth/<project id>/slack/callback`

1. At [api.slack.com/apps](https://api.slack.com/apps), **Create New App** > **From scratch**: your app's name and the workspace you build it in. The icon goes under **Basic Information** > **Display Information**.
2. **OAuth & Permissions** > **Redirect URLs** > **Add New Redirect URL**: the callback URL of each environment. **Save URLs**.
3. On the same page, under **User Token Scopes**, add `openid`, `email` and `profile`.
4. **Basic Information** > **App Credentials**: copy the **Client ID** and the **Client Secret**.
5. To let people from other workspaces sign in, turn on public distribution under **Manage Distribution**.

## What your users see

- **Your app at the provider.** The sign-in and consent screens show the name and logo of your OAuth app, because it is your OAuth app. dabadata's name is not on them.
- **The domain it returns to.** Some providers also show the domain the sign-in goes back to, which is `https://dabadata.com`, the host of the callback URL. Other hosted sign-in services work the same way, on their own domains. Sign-in on a domain of your own (a custom auth domain) is planned.
- **No page in between.** The callback only redirects: from the provider the person goes straight back to your redirect URL, signed in.
- **Native apps** that use [`idtoken`](https://dabadata.com/docs/sign-in-providers/index.md#native) never leave your app: the person sees the system's or the provider's own sign-in sheet, and your app hands the token over.

## Native apps and your own buttons

When your app gets an ID token from Google or Apple itself - Sign in with Apple on iOS, Google Sign-In on Android or iOS, the Google Identity Services button or One Tap on a web page - hand it to dabadata with the publishable key:

```bash
curl -X POST https://dabadata.com/v1/auth/users/idtoken \
  -H "X-Api-Key: pk_test_EXAMPLEexample0000000000" \
  -H "Content-Type: application/json" \
  -d '{"provider":"apple","idToken":"eyJraWQiOi...","nonce":"<the nonce your app made>","name":"Someone"}'
```

```json
{
  "tokens": { "token": "...", "expiresAt": "...", "refreshToken": "...", "refreshExpiresAt": "..." },
  "user": { "id": "usr_k3x9m2p7q4w8z1ab", "email": "someone@example.com", "name": "Someone", "role": "member",
            "emailVerified": true, "hasPassword": false, "createdAt": "2026-10-16T09:00:00.000Z" },
  "created": true
}
```

In the JavaScript client, `db.auth.signInWithIdToken` does the call and signs in like `signIn`. With the Google Identity Services button on your own page:

```js
// <script src="https://accounts.google.com/gsi/client" async></script> on the page
google.accounts.id.initialize({
  client_id: "1234567890-abc.apps.googleusercontent.com", // the clientId in auth.users.providers.google
  callback: async (response) => {
    const user = await db.auth.signInWithIdToken({ provider: "google", idToken: response.credential });
    console.log("signed in as", user.email);
  },
});
google.accounts.id.renderButton(document.getElementById("google-button"), { theme: "outline" });
```

- `provider` is `google` or `apple`, and it must be in `auth.users.providers` (else 400 `provider_not_enabled`). A Google entry without a `secret` is enough for this route.
- The token is checked in full: its signature against the provider's public keys, the issuer, the expiry, and the audience, which must be the `clientId` or one of `audiences`. A native app's token names its own client id (your iOS app's bundle id for Apple; the iOS client id for Google Sign-In on iOS), so add it to `audiences`.
- **Nonce.** Make a fresh random nonce for each sign-in and give it to the provider; it ties the token to that attempt. If the token carries a `nonce` claim, send the same nonce in the body: it must equal the claim, or its SHA-256 (hex) must. Sign in with Apple on iOS takes the SHA-256 of a nonce, so give Apple the hash and dabadata the raw nonce. A token without a nonce claim needs none.
- `name` is used only when the user is created. Apple gives your app the name on the first sign-in only, so pass it then.

Sign in with Apple in a Swift app, in short:

```swift
import AuthenticationServices
import CryptoKit

let rawNonce = UUID().uuidString + UUID().uuidString
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = SHA256.hash(data: Data(rawNonce.utf8)).map { String(format: "%02x", $0) }.joined()
// ... run an ASAuthorizationController; in its delegate, with the credential:
let idToken = String(data: credential.identityToken!, encoding: .utf8)!
let name = [credential.fullName?.givenName, credential.fullName?.familyName].compactMap { $0 }.joined(separator: " ")
// POST /v1/auth/users/idtoken { "provider": "apple", "idToken": idToken, "nonce": rawNonce, "name": name }
```

## In your app

For the browser flow the JavaScript client has two methods: `db.auth.providers()` says which providers are on, and `db.auth.signInWithProvider(provider, { redirectUrl })` leaves the page for the provider. Back on your page, `db.auth.completeLink()` takes the tokens, as it does for mailed links.

```js
await db.auth.providers();
// { providers: ["google", "github"], page: "https://dabadata.com/auth/prj_example12345_test/" }
```

One HTML file with a button for each provider that is on:

```html
<!doctype html>
<meta charset="utf-8">
<title>Tasks</title>

<p id="who">Loading...</p>
<div id="buttons"></div>
<button id="signout" hidden>Sign out</button>

<script src="https://dabadata.com/client/dist/dabadata.iife.js"></script>
<script>
  const db = dabadata.createClient({
    url: "https://dabadata.com",
    key: "pk_test_EXAMPLEexample0000000000", // your test publishable key
  });
  const $ = (id) => document.getElementById(id);
  const labels = { google: "Google", apple: "Apple", microsoft: "Microsoft", github: "GitHub",
                   gitlab: "GitLab", discord: "Discord", linkedin: "LinkedIn", slack: "Slack" };

  async function show(user) {
    $("who").textContent = user ? `Signed in as ${user.email}` : "Not signed in";
    $("signout").hidden = !user;
    $("buttons").replaceChildren();
    if (user) return;
    const { providers } = await db.auth.providers();
    for (const p of providers) {
      const b = document.createElement("button");
      b.textContent = `Continue with ${labels[p]}`;
      // The redirect URL must be one of auth.users.redirectUrls.
      b.onclick = () => db.auth.signInWithProvider(p, { redirectUrl: location.origin + "/" });
      $("buttons").append(b);
    }
  }

  db.auth.onAuthChange((user) => show(user));
  $("signout").onclick = () => db.auth.signOut();

  // Back from the provider with #token=... ? Finish signing in.
  db.auth.completeLink()
    .then(() => db.auth.user())
    .then(show)
    .catch((err) => { alert(err.message); db.auth.user().then(show); });
</script>
```

- `redirectUrl` must be in `redirectUrls`. Left out, it is the current page's URL without its fragment, which must then be listed exactly. A `?mode=app` on it changes nothing here: the tokens come in the fragment either way.
- If something goes wrong, the person lands on the hosted sign-in page with the error and the buttons; a second try still brings them back to your `redirectUrl`.
- `signInWithProvider` rejects with `code` `provider_not_enabled` when the provider is not in `providers()`, and `unsupported` outside a browser (in an app, use [`signInWithIdToken`](https://dabadata.com/docs/sign-in-providers/index.md#native)).
- The hosted sign-in and sign-up pages show "Continue with ..." for the same providers, so a link to `https://dabadata.com/auth/<project id>/?redirect=...` needs no code at all.

## Accounts and linking

Each sign-in with a provider ends at one hosted user of the environment. Which one:

- **Same person, same account.** The first sign-in links the provider account to a user. Every later sign-in with that provider account is that user, even if the email at the provider changes; the user keeps their email.
- **A trusted email joins an existing account.** When the environment already has a user with that email - signed up with a password, invited, or signed in with another provider - the provider account is linked to them. So one person can use Google, GitHub and a password for one account. The user list shows the linked providers in `providers`.
- **An email the provider does not vouch for signs nobody in**, new or existing: `email_unverified`. A provider account without an email: `email_missing`.
- **A never-confirmed account is taken over safely.** If the matching user never confirmed their address, someone may have signed up with it and a password of their own. The person who just proved the address through the provider gets the account: the password and name from that sign-up are dropped, every session of the account is signed out, and the address counts as confirmed (as with [the sign-up secret](https://dabadata.com/docs/end-user-auth/index.md#pre-registration)).
- **New people get a new account** when `signup` is `"open"`: role `defaultRole`, the address confirmed, the name from the provider, no password. `profiles` and `onSignUp` run as for any sign-up. A password can be added later, with "forgot password" or `db.auth.update({ password })`.
- **Invite-only.** With `signup: "invite"`, only people who already have a user get in (you added them, or someone invited them); anyone else gets `signup_closed`. An invited person who signs in with a provider under the invited address accepts the invite that way.
- **Disabled users stay out**: `user_disabled`.

## Errors

The browser flow ends on the hosted sign-in page with `?error=<code>`, which the page shows as a sentence. `idtoken` answers the code as JSON, `{ "error": "<code>" }`.

| Code | Where | When | What to do |
| --- | --- | --- | --- |
| `provider_cancelled` | `?error=` | The person cancelled or declined at the provider. | Nothing; they can try again. |
| `provider_failed` | `?error=` | Anything else on the way: the sign-in state was unknown, used, older than 10 minutes or from another browser; the provider refused the code; the token or profile did not check out. The reason is logged, never shown. | Check the client id, the client secret and the registered callback URL; then try again in one browser. |
| `email_missing` | `?error=`, `idtoken` 403 | The provider account shared no email. | The person signs in another way, or adds an email at the provider. |
| `email_unverified` | `?error=`, `idtoken` 403 | The provider does not vouch for the email: not verified there, or a Microsoft work account without `xms_edov`. | The person verifies the email at the provider. For Microsoft, add the [optional claim](https://dabadata.com/docs/sign-in-providers/index.md#microsoft). |
| `signup_closed` | `?error=`, `idtoken` 403 | No user has that email, and `signup` is `"invite"`. | Add or invite the person. |
| `user_disabled` | `?error=`, `idtoken` 403 | The user is disabled. | Enable them, if they should get in. |
| `on_signup_failed` | `?error=`, `idtoken` 409 | The `onSignUp` row could not be inserted; no user was made. | Fix the [`onSignUp`](https://dabadata.com/docs/end-user-auth/index.md#on-signup) table or values. |
| `invalid_token` | `idtoken` 401 | The ID token's signature, issuer, audience, expiry or nonce did not check out. | Check `clientId`, `audiences` and the nonce; get a fresh token. |
| `provider_not_enabled` | `idtoken` 400; `signInWithProvider` | The provider is not in `auth.users.providers` (for `signInWithProvider`: its browser flow is not on, such as Google without a secret). | [Turn it on](https://dabadata.com/docs/sign-in-providers/index.md#enable). |
| `shared_google_domain` | `?error=` | Google uses [dabadata's Google app](https://dabadata.com/docs/sign-in-providers/index.md#dabadata-google), and the person would go back to an address that is not `*.dabadata.site`, such as your own domain. | Set up [your own Google app](https://dabadata.com/docs/sign-in-providers/index.md#google) for that domain. |
| `provider_not_enabled` | `idtoken` 400 | Google uses dabadata's Google app, which is for the browser only. | Use your own Google app for native sign-in. |
| `validation` | `idtoken` 400 | The body: unknown fields, `provider` not `google` or `apple`, `idToken` over 8 KB, `nonce` over 200 characters, a bad `name`. | Fix the call. |
| `rate_limited` | `idtoken` 429; the start URL answers 429 as text | Over the [limits](https://dabadata.com/docs/sign-in-providers/index.md#limits). | Wait `Retry-After` seconds. |

The start URL `/auth/<env>/<provider>` answers a plain-text 404 when hosted accounts or that provider are off, and 400 for a `redirect` that is not in `redirectUrls`. With hosted accounts off, `idtoken` and `providers` answer 404 like every `/v1/auth/users/` route.

## Limits

| What | Limit (per environment) |
| --- | --- |
| Starting a sign-in, `/auth/<env>/<provider>` | 20 per IP address per 15 minutes |
| `POST /v1/auth/users/idtoken` | 30 per IP address per 15 minutes |
| A sign-in state | 10 minutes, used once |

Every route is in the [HTTP API reference](https://dabadata.com/docs/api/index.md#eu-providers).
