---
title: "Connect an upstream identity provider | FoxAuth"
description: "Let a bucket's users sign in through Google, Microsoft, Apple, GitHub or any OpenID provider, and decide which accounts such a sign-in may create or link."
canonical: "https://foxauth.dev/docs/administer/federation/"
lastmod: "2026-10-12T23:12:26+03:00"
---
Connect an upstream identity provider

A user bucket can carry upstream identity providers. Each enabled one becomes a button on that bucket’s
sign-in page. The user authenticates at the provider, and this server, acting as the provider’s
relying party, turns the assertion it gets back into an ordinary local session. Everything after that
point is the same as a password sign-in: the same second-factor rules, the same consent, the same
tokens.

Providers are configured per bucket. Bucket A’s Google application is not bucket B’s, and whoever
administers a bucket configures its providers without needing a super administrator. The one exception
is the administrators’ bucket, whose providers only a super administrator configures; see
Administrators signing in through your provider.

What you need first

The federation.enabled
setting on, which serves the sign-in legs of the flow. The management routes
are not gated by it, so you can prepare a provider before switching it on, and
remove one you stopped trusting after switching it off.

Recognised providers need only their own valuesSection titled “Recognised providers need only their own values”

Four providers are recognised by name: Google, Microsoft, Apple and GitHub. Choose one
from the catalogue when adding a provider, and you are asked only for the values that provider issues:

Provider
What you supply

Google
Client ID, client secret

Microsoft
Directory (tenant) ID, application (client) ID, client secret

Apple
Services ID, Team ID, Key ID, the key file (.p8)

GitHub
Client ID, client secret

The catalogue fills in the rest: issuer, scopes, which claim holds the email address, whether that
provider’s verified addresses can be trusted, and the steps to perform at the provider. Nothing is
stored about where a provider came from, so a Google provider you configured by hand earlier is
recognised as Google all the same.

Three of the four needed more than a row of data, and each difference is visible to an operator:

Microsoft’s tenant is who may sign in. common admits any Microsoft account, personal ones
included; organizations admits any work or school account; one directory’s ID admits only that
organisation. The server checks the tid claim against it, because with a multi-organisation endpoint
every organisation’s tokens verify correctly. Microsoft does not always send an email claim, so the
fallback preferred_username is used only when it looks like an address.

Apple issues no secret. It issues a signing key, and the credential presented to Apple is derived
from that key on every exchange, so there is nothing to expire six months from now. Apple sends a
person’s name once, on their first authorization, and never again.

GitHub does not speak OpenID Connect for sign-in. The server exchanges the code, reads the
profile through GitHub’s API and drops the token. The subject is the numeric account id, never the
login, which can be renamed and claimed by somebody else, and the address used is the primary one
GitHub marks verified.

Any other OpenID Connect provider, such as Okta, Keycloak, Auth0 or Ping, is configured by its issuer.
The server reads its discovery document, refuses one that names an endpoint which is not https, and
checks that the document’s issuer is the one you entered.

On every sign-in, the server also checks which issuer answered. When the provider sends the RFC 9207
iss parameter with its response, it must match the issuer the sign-in started with. When the
provider’s discovery document says it sends one, a response without it is refused. This stops a
response from one provider from being accepted as another’s.

A link to an account records the provider and the person’s subject there, not the issuer. So changing a
provider’s issuer, or a Microsoft provider’s tenant, is refused while anyone is linked to it, because the
same subject at another issuer is another person. To move to a new issuer, add a new provider.

Register the callback address before you testSection titled “Register the callback address before you test”

Open the bucket in the console, go to Identity providers, and choose New identity provider.
A side panel walks you through choosing the provider, registering this bucket at it, pasting its
values and deciding who may sign in.

Copy The redirect address to register from the panel. After the provider is created, its page
shows the same address as Callback address. It is the bucket’s issuer followed by
/federation/callback: https://auth.example.com/acme/federation/callback for a bucket at a path,
https://login.acme.example/federation/callback for a bucket with a hostname.

Register exactly that address at the provider. Providers compare it character for character.

Choose Try sign-in on the provider’s page before anyone relies on it. See
Try a provider before anyone relies on it.

The address belongs to the bucket’s address, so changing the bucket’s path or hostname changes it, and
the provider will then refuse every sign-in with a redirect mismatch on its own page. A bucket that has
no address yet gets a callback built from its record id. The console warns about this when you copy it,
because that address stops matching as soon as the bucket is given a path.

Who gets an account, and who gets linkedSection titled “Who gets an account, and who gets linked”

First-time users decides whether a sign-in from someone with no account here creates one:
Create an account for them, or Only people who already have an account here. The side panel
that adds a provider has no default for it and cannot finish until you choose. Email domains narrows
it further. Leave it empty and an enabled Google button creates an account for anyone with a Google
account, which is the classic way this feature is misconfigured. A provider’s page asks before saving a
change that lets more people in.

When a sign-in arrives, the server works through the same questions in the same order every time, and
the order is what keeps one person’s address from opening another person’s account:

Is this upstream identity already linked to an account? Then that account signs in, if it is
active and not locked. The link is the identity from then on; the email address only matters the
first time.

Is the provider bound to a SCIM connection? Then the connection’s correlation rule names one
provisioned account or the sign-in is refused, and none of the email steps below runs. See
Provision users with SCIM.

Is there an email address in the claim you named? No address, no sign-in. Corporate providers
often put it in upn rather than email.

Is its domain allowed? This is checked before looking for an existing account, so the server
never reveals whether an account exists for an address the provider may not speak for.

Does an account with that address already exist? It is linked only if Link existing accounts
by verified email is on and the token says email_verified is exactly true. Even then, an
account that so far holds only a password is not linked on the spot: the user is sent to sign in to
that account with its password, and second factor where the bucket requires one, and the link is
made when that sign-in completes. Without this, anyone who registered a victim’s address first would
inherit the victim’s first federated sign-in.

Otherwise, a new account is created or the sign-in refused, according to First-time users.

An account created this way has no usable password. Upstream access and refresh tokens are never kept.
The only claims copied from the provider are name, given_name, family_name, picture and locale.

Try a provider before anyone relies on itSection titled “Try a provider before anyone relies on it”

Try sign-in on a provider’s page opens the provider in a new tab. You sign in there, through the
bucket’s real callback, and the console then shows what the provider answered and what a real sign-in
would do: link an account, create one, or refuse, and why. It writes nothing: no account, no link, no
session. It works on a disabled provider, so you can test one before switching it on. It is in the
console only, because its result is shown to the browser session that started it, and an agent has none.

The order of the buttonsSection titled “The order of the buttons”

The sign-in page shows a bucket’s enabled providers in an order you choose. Move up and Move
down on the Identity providers list change it one place at a time. An agent uses
federation_provider_move. A provider added later appears after the ones already ordered.

Turning a provider off, and unlinkingSection titled “Turning a provider off, and unlinking”

Disable… keeps the provider’s credentials, so taking it out of service during an incident costs
nothing to undo. Deleting it removes its credentials, and also every link to it. A provider created
later under the same id is a new provider, and nobody’s account is linked to it. To detach one person
from an upstream identity without touching the provider, Remove the link under Identity links on
the person’s Sign-in methods tab; their next federated sign-in then goes through the steps above
again.

An agent does the same through federation_catalogue_list, federation_provider_create (pass the
catalogue entry and only the values it asks for), federation_provider_get (one provider: its callback,
its logout options, its bound connection and how many people are linked), federation_provider_update,
federation_provider_delete and federation_identity_delete; see the
MCP tool reference.

Two options on a provider concern signing people out rather than in: accepting Okta’s global token
revocation, and accepting back-channel logout from Keycloak, Auth0 or Ping. They have a page of their own,
Sign-out from upstream providers.

Administrators signing in through your providerSection titled “Administrators signing in through your provider”

The administrators’ bucket takes identity providers like any other bucket, so administrators can sign in
to the console with your organisation’s provider. Only a super administrator configures them, on the
Administrators page under Identity providers.

Linking an existing administrator always asks for their password. In this bucket, the account
behind an address may be a super administrator, so no provider is trusted to link one on its own.
While password sign-in is off, no new link can be made at all.

A new administrator is created only while the console takes sign-ups. Such a sign-in creates an
administrator with a personal group, never the super administrator privilege.

The second factor stays the provider’s. The bucket’s authenticator requirement applies to
password sign-in only.

Password sign-in to the console can be turned off, but only from a session that came in through a
linked provider. While it is off, no change is accepted that would leave every super administrator
without a way in: disabling, narrowing or deleting their provider, deactivating them, or withdrawing
their privilege. If the provider itself fails, run bun run admin:password-sign-in on the server. It
turns password sign-in back on and records that in the audit trail.

An agent may change these providers too, but on the administrators’ bucket federation_provider_create,
federation_provider_update and federation_provider_move ask for confirmation, as
federation_provider_delete does on every bucket. An agent can never turn password sign-in off.

PreviousUpgradingNextProvision users from Entra ID or Okta with SCIM
