# Social Providers

Set up sign-in with Google, LinkedIn, GitHub, Microsoft, Apple, or an OpenID Connect provider of your own.

A social provider puts a button on your hosted sign-in and register pages. You create an OAuth app at the provider, paste its credentials into the console, and give the provider the callback URL the console shows you. Nothing changes in your app's code.

Step by step, with screenshots: [Google](/sign-in-setup/google), [GitHub](/sign-in-setup/github), [Microsoft](/sign-in-setup/microsoft), [LinkedIn](/sign-in-setup/linkedin), [Apple](/sign-in-setup/apple), [a custom OpenID Connect provider](/sign-in-setup/custom-oidc) and [Google One Tap](/sign-in-setup/google-one-tap).

## Adding a provider

1. Create an OAuth app at the provider. Each provider's section below says where.
2. In the console, add the provider under **User Management → Access → Authentication → Methods** and fill in the fields it asks for.
3. Save. The console then shows the callback URL to register with the provider (for Apple, a domain as well). Add it to the app you created in step 1.
4. Tick the provider on your client in the **Settings** tab, as with any other method.

Every callback URL has this shape:

```text
https://<your BuildBase server>/api/v1/auth/social/<slug>/<orgId>/<methodId>/callback
```

Copy it from the console rather than building it by hand. Providers check it exactly, so a trailing slash or a different host fails the sign-in. A method's callback URL never changes once it is set up, so a Google, LinkedIn, GitHub or Microsoft method you added earlier needs nothing new at the provider.

- **Secrets are write-only.** Client secrets and Apple's private key are stored encrypted and never shown again. The console shows a saved client secret as its first and last five characters, and a private key only as saved. Leave the field as it is to keep the stored value, or paste a new one to replace it.
- **Scopes are editable** on every provider. Each starts with the defaults listed below, and a provider saved with no scopes uses those. Keep the scopes that return the person's email address: a new account is created with it.
- **Every provider counts** toward your plan's social provider limit, Apple and the custom provider included.

## Google

| Create the app at                                                                      | The console asks for     | Default scopes                                                                                                 |
| -------------------------------------------------------------------------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| [Google Cloud console, Credentials](https://console.cloud.google.com/apis/credentials) | Client ID, client secret | `https://www.googleapis.com/auth/userinfo.email`, `https://www.googleapis.com/auth/userinfo.profile`, `openid` |

Create an **OAuth client ID** of type **Web application**, and add the callback URL under **Authorized redirect URIs**.

### Google One Tap

One Tap lets someone who is already signed in to Google in their browser sign in to your app with one click, without leaving your sign-in page. It is off until you turn it on.

1. In the console, edit your Google method and set **Google One Tap** to **On**.
2. In Google Cloud, open the same OAuth client and add your sign-in page's origin under **Authorized JavaScript origins**. The console shows it beside the callback URL on the Google method. With a verified custom auth domain it is `https://` and that domain; on the shared domain it is your sign-in page's address up to `/v1/login`. It is not your BuildBase server's URL. If you verify a custom auth domain later, add that origin too.

The hosted sign-in and register pages then offer One Tap as well as the Google button, which stays where it was. Nothing changes in your app's code.

- **It is Google's prompt, not ours.** Google draws it, and only for someone signed in to Google in that browser. For anyone else nothing appears, and the Google button works as it always has. Someone who closes the prompt is not shown it again straight away; Google decides when.
- **FedCM.** The page asks for the browser's own sign-in prompt (FedCM) where the browser has it, so in Chrome and other browsers with FedCM the prompt is the browser's account chooser and does not depend on third-party cookies. Where a browser has neither FedCM nor third-party cookies, Google may not show the prompt at all.
- **HTTPS only.** Google shows One Tap on HTTPS pages, and on `localhost` while you test.
- **The same sign-in as the button.** A One Tap sign-in finds the account the way the Google button does (see [How sign-in finds an account](#how-sign-in-finds-an-account)) and carries on exactly as a Google sign-in does: a second step if your [two-step sign-in](/authentication/two-step) policy asks social sign-ins for one, then the "Trust this device?" screen and the passkey offer.

Each prompt carries a single-use nonce that lasts 10 minutes, which ties Google's answer to that page. When a One Tap sign-in is refused, the sign-in page says why above its form, and the Google button is still there:

| `reason`                                                               | Meaning                                                                                                        |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `invalid_credential`                                                   | Google's answer could not be verified for your client ID.                                                      |
| `nonce_invalid`                                                        | The prompt was used already, or it was shown more than 10 minutes ago. Reloading the page starts again.        |
| `blocked`, `email_unverified`, `signup_disabled`, `signup_unavailable` | The same refusals as any social sign-in, listed under [When a sign-in is refused](#when-a-sign-in-is-refused). |

## LinkedIn

| Create the app at                                                   | The console asks for     | Default scopes               |
| ------------------------------------------------------------------- | ------------------------ | ---------------------------- |
| [LinkedIn developer apps](https://www.linkedin.com/developers/apps) | Client ID, client secret | `openid`, `profile`, `email` |

Add the **Sign In with LinkedIn using OpenID Connect** product to the app, which grants those scopes, and add the callback URL under **Authorized redirect URLs for your app** on the **Auth** tab.

## GitHub

| Create the app at                                                   | The console asks for     | Default scopes            |
| ------------------------------------------------------------------- | ------------------------ | ------------------------- |
| [GitHub developer settings](https://github.com/settings/developers) | Client ID, client secret | `user:email`, `read:user` |

Create an **OAuth App** and put the callback URL in **Authorization callback URL**.

## Microsoft

| Create the app at                                                                                                    | The console asks for             | Default scopes                            |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------- | ----------------------------------------- |
| [Azure portal, App registrations](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) | Client ID, client secret, tenant | `openid`, `profile`, `email`, `User.Read` |

Register an application with the callback URL as a **Web** redirect URI, then add a client secret under **Certificates & secrets**.

**Tenant** decides whose accounts can sign in. The default, `common`, accepts work, school and personal Microsoft accounts. A directory ID or domain accepts accounts from that directory only.

## Sign in with Apple

| Create the app at                                                                              | The console asks for                            | Default scopes  |
| ---------------------------------------------------------------------------------------------- | ----------------------------------------------- | --------------- |
| [Apple developer, Identifiers](https://developer.apple.com/account/resources/identifiers/list) | Services ID, Team ID, Key ID, private key (.p8) | `name`, `email` |

This needs an Apple Developer Program membership. Then, under **Certificates, Identifiers & Profiles**:

1. Register an **App ID** with **Sign in with Apple** enabled, if you do not have one already.
2. Register a **Services ID**. Its identifier, such as `com.example.signin`, is the **Services ID** the console asks for. It is the client ID for the web, and it is not the App ID.
3. Under **Keys**, create a key with **Sign in with Apple** enabled, linked to that App ID. Download the `.p8` file, which Apple lets you do once, and note the key's **Key ID**.
4. Your **Team ID** is under **Membership details** in your developer account.
5. Add Apple in the console with those four values and save. Then open the Services ID, enable **Sign in with Apple**, and under **Configure** pick the App ID and add the domain and the return URL the console shows.

Paste the whole `.p8` file into **Private key**, from `-----BEGIN PRIVATE KEY-----` to the end. There is no client secret field: Apple's client secret is a token signed with your key, and BuildBase signs it for you.

**It needs a public HTTPS callback.** Apple accepts only HTTPS return URLs on a public domain, never `localhost` or an IP address, so Sign in with Apple cannot be tested on a server running on your laptop. Test it on a deployed server, such as a staging one. Apple also returns to the callback with a form `POST` rather than a redirect, so anything in front of a self-hosted server must let `POST` requests through to that path.

**The name arrives once.** Apple sends the person's name only the first time they authorize your Services ID, and BuildBase saves it then. Later sign-ins carry an ID and an email address, and no name. To see the first authorization again while testing, remove your app under **Sign in with Apple** in the settings of the Apple Account you test with.

**Hidden email addresses.** A person can choose **Hide My Email**, and your app then gets an address at `privaterelay.appleid.com` that forwards to their real inbox. Two things follow:

- Apple forwards mail to that address only from senders you register with it. Under **Certificates, Identifiers & Profiles → Services → Sign in with Apple for Email Communication**, register the domain or address your app's email is sent from. Until you do, magic links, sign-in codes and every other email to those people bounce.
- The relay address matches no account the person made another way, so their first Apple sign-in does not find an account they already have. Where sign-up is open, it makes a new one.

## A custom OpenID Connect or OAuth 2.0 provider

For an identity provider BuildBase has not built, such as Keycloak or one of your own, add a custom provider. It is one button on your sign-in page, for a provider that speaks OpenID Connect, or OAuth 2.0 with a user info endpoint.

- **One per app.** A second one is refused with "This app already has a custom provider. Edit that one instead."
- **A plan feature, checked when you add it.** The custom provider is the `auth-custom-oidc` plan feature. A plan that switches it off refuses the add with a `402`. The check fails open: while your plan does not name the feature at all, or BuildBase cannot read your plan, the add goes through. Sign-in never checks the plan, so a provider already added keeps working.

| Field                     | What to give it                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Button label              | Required. What the button on your sign-in page says, such as "Acme ID"                                              |
| Issuer URL                | For an OpenID Connect provider. BuildBase reads its endpoints from the issuer's `/.well-known/openid-configuration` |
| Client ID, client secret  | Required. From the client you register with the provider                                                            |
| Authorization URL         | Only for a provider without discovery, with the next two                                                            |
| Token URL                 | Only for a provider without discovery                                                                               |
| User info URL             | Only for a provider without discovery                                                                               |
| Icon URL                  | A square image over HTTPS. Without one, or if it fails to load, the button shows a generic key                      |
| Trust its email addresses | `never` by default. See below                                                                                       |

The default scopes are `openid`, `profile` and `email`.

**Discovery or three endpoints.** Give the issuer URL, and BuildBase reads the rest from the provider's discovery document. For a provider without discovery, such as a plain OAuth 2.0 server, leave the issuer empty and give the authorization, token and user info URLs instead. Saving with neither is refused with "Give an issuer URL, or all three endpoints." If the discovery document cannot be read, the save is refused with `discovery_failed`.

**Every URL BuildBase calls must be public.** BuildBase refuses an issuer, token, JWKS or user info URL that resolves to a private or internal address, including the ones a discovery document names, with `url_not_public`. The authorization URL is not checked: BuildBase never calls it, only sends the person's browser there. A provider on a private network has to be reachable at a public address before it can be used. A self-hosted install may use addresses on its own private network; cloud metadata and link-local addresses are refused everywhere.

**What BuildBase reads from it.** With discovery, the id_token is checked first: its signature against the provider's JWKS (RS256, ES256 or PS256), the issuer, the audience (your client ID) and the sign-in's nonce. The authorization request carries a PKCE challenge, and the client authenticates at the token endpoint with HTTP Basic unless the provider supports only `client_secret_post`. Without discovery, the profile comes from the user info URL, with the access token, and the client secret goes in the form.

From the id_token or user info, BuildBase reads:

| Field                                                                  | Used for                                                                           |
| ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `sub`, or `id` for a plain OAuth 2.0 provider                          | The provider's own user ID, which finds the account on every later sign-in         |
| `email`                                                                | The account's address. A provider that sends none cannot create an account         |
| `email_verified`                                                       | Whether the address is proven, when trust is set to "When it sends email_verified" |
| `name`, else `given_name` and `family_name`, else `preferred_username` | The name on a new account                                                          |

When the id_token carries no email, user info is asked as well, and it must name the same `sub`. A provider that uses other field names for these cannot be used yet.

**Email trust** decides whether this provider can sign someone into an account that already exists with the same email address:

| Setting                        | What it means                                                                                                                                                                                          |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Never (default)                | It cannot sign into an account that already exists with the same email. It can create new accounts, and once someone has signed in with it, it finds that account again by the provider's own user ID. |
| When it sends `email_verified` | It can sign into an existing account when it says, on that sign-in, that the address is verified (`email_verified: true`).                                                                             |
| Always                         | Every address it sends is treated as verified.                                                                                                                                                         |

Trust a provider only if it checks that people own the addresses it sends. Trusting one that lets anyone type in any address lets anyone sign into anyone's account. `never` is the default for that reason: BuildBase knows nothing about a provider you added yourself.

Edit the custom provider rather than deleting it and adding a new one. People who signed in with it are linked to that provider, and a new one starts with no links, so those people are found again only by email, which needs the trust above.

## How sign-in finds an account

When someone comes back from a provider, BuildBase looks for their account in this order:

1. **By the provider's own user ID.** Anyone who has signed in with this provider before is found this way, whatever their email address is now, here or at the provider.
2. **By a verified email address.** If no account has that ID linked yet, BuildBase looks for an account with the same email, but only when the provider vouches for the address: a built-in provider when it says the address is verified, and a custom provider as its email trust setting says. A match links the provider to the account, so the next sign-in is found by step 1.
3. **A new account**, when neither finds one, if sign-up is open or the sign-in started from an invitation, and your plan has room for another user.

Because step 1 comes first, a person who signs in with a social provider can change their email address in your app and still reach the same account next time.

## When a sign-in is refused

A sign-in that fails after the provider sends the person back returns to the page it started from, with `error` and `reason` added to the URL. The hosted sign-in page shows the message, with the form under it so the person can try again.

| `reason`             | Meaning                                                                                                                                         |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_error`     | The provider answered with an error, or the person cancelled there.                                                                             |
| `provider_failed`    | The provider sent the person back, but BuildBase could not finish with it: exchanging the code or reading the profile failed.                   |
| `email_unverified`   | An account with this email already exists, and this provider cannot sign into it, because it does not vouch for the address.                    |
| `email_missing`      | The provider shared no email address, so there is no account to find and none can be created.                                                   |
| `blocked`            | The account is blocked.                                                                                                                         |
| `signup_disabled`    | There is no account for this address and sign-up is invite-only. See [Closing public sign-up](/authentication/overview#closing-public-sign-up). |
| `signup_unavailable` | There is no account for this address and the app cannot take new users right now.                                                               |
| `signup_restricted`  | There is no account for this address, and it is blocked or not on the allowlist. See [Who can sign up](/authentication/signup-access).          |
| `waitlist_required`  | There is no account for this address, sign-up is by waitlist, and the address has no approved entry.                                            |

A sign-in that waited too long is the exception. Each one is good for 10 minutes and answers one callback, so a provider that sends the person back later, or a second time, finds nothing left to finish, nor the page it started from. BuildBase then sends the browser to your hosted sign-in page, `/v1/login?org=<orgId>&reason=session_expired`, on your sign-in domain (your verified custom domain, or BuildBase's), and that page says the sign-in expired, so the person starts again from your app. Nothing about the expired attempt is in the address. A callback carrying a sign-in that is still live but belongs to another org, method or provider is refused with a plain `401`: that is a tampered link or a provider app shared between methods, not a slow sign-in.

When `provider_failed` keeps happening, check the client secret first: a mistyped secret, or one that has expired at the provider (Microsoft's do), fails the code exchange. For Apple, check the key, Key ID and Team ID.

## Next steps

- [Overview](/authentication/overview) - every sign-in method and how they fit together.
- [Sign In & Sign Out](/authentication/sign-in-sign-out) - `useSaaSAuth()` return values and usage patterns.
- [Bot Protection](/authentication/bot-protection) - Turnstile on the hosted sign-up and sign-in forms.
- [Connected Accounts](/authentication/connected-accounts) - let people connect more providers to their account, and remove one.
- [Importing Users](/authentication/importing-users) - bring users over from another provider with their social sign-ins.
