Skip to content

Social login

Purpose: let your users sign in to your application with an account they already have. You configure a connector per provider in your dashboard; the provider's button appears on your tenant's hosted sign-in page; we run the whole flow and hand your application a signed-in user, exactly as if they had typed a password.

Built-in connectors exist for Google, GitHub, Discord, and Apple. Any other provider that speaks OpenID Connect — an enterprise IdP, another platform, your own — is a generic OIDC connector: give us its issuer URL and we take the rest from the provider's own discovery document.

How an account is matched

  • A linked identity is keyed by the provider's stable subject identifier, never by email. An email address can change hands; the provider-and-subject pair cannot. Someone who changes their email at the provider keeps their account with you.

  • A verified email collision links automatically. When a provider asserts a verified email that equals the verified email of an existing account, the identity is attached to that account and the sign-in proceeds. If either side is unverified — the provider did not vouch for the address, or the account never confirmed it — the sign-in is refused instead of guessing, because an unverified match is how account takeover by email squatting works.

  • An unknown identity provisions a new account, subject to your tenant's sign-up mode. A closed tenant refuses self-serve provisioning the same way it refuses the sign-up form. A waitlist-only tenant captures the provider's email onto the waitlist instead of refusing — sending the same confirmation email and landing on the same you're-on-the-list page as the join form, idempotently — but only when the provider asserts that email as verified. Without a verified address there is nothing safe to put on a list, so a waitlist-only tenant refuses exactly as the form does. And a waitlist invitation can be redeemed through a provider button when the provider asserts a verified email equal to the invited address.

  • Signed-in users connect and disconnect providers themselves on the hosted /security page. Connecting or disconnecting a sign-in method emails the account owner, and the last remaining way to sign in cannot be disconnected — set a password or connect another provider first.

Linked accounts in your application

Send the person's access token to GET /api/account/linked-accounts on your tenant's host. The response contains identities and hasPassword. Each identity includes its opaque id, provider key, provider displayName, email when available, and createdAt. Raw provider subjects, tokens and connector secrets are omitted. Linked identities remain listed when their provider is disabled; available providers are not part of this response.

DELETE /api/account/linked-accounts/{identityId} disconnects that person's identity and returns 204. It shares the hosted page's last-method guard, audit and owner notification. The last usable sign-in method returns 409 with reason: "last_method"; an unknown, malformed or another person's ID returns the same 404. Disabled providers do not count as a remaining usable method. The existing notification is attempted for an owner with a verified email; delivery failure does not undo the unlink.

These caller-only operations require no management scope or dashboard seat. Machine credentials are refused, and the operator host answers 404. A mutation accepts no body or an empty object; no subject or tenant selector is accepted. To connect another account, send the person to the tenant's hosted /security page. There is no application-supplied return address for that flow.

Setting up a connector

In the dashboard: ProvidersAdd provider. Pick the provider type, then paste the client ID and client secret from the provider's own console. Apple's inputs are different: its client ID is the Services ID configured for Sign in with Apple on the web (not the App ID), it has no client secret — in its place you paste the contents of the .p8 private key Apple lets you download once — and it also asks for your Team ID and Key ID. The form shows the exact callback URL to register with the provider:

https://{your-tenant-host}/auth/social/{provider}/callback

Providers match that URL exactly, so every origin your sign-in page is served on needs its own registration in the provider's console. For a generic OIDC connector the URL segment is the connector's row id rather than a type name, because a tenant may have several OIDC connectors; built-in types are limited to one connector each.

Client secrets are sealed with authenticated encryption at rest, and reads — dashboard and API alike — report only whether a secret is stored, never its value.

Each connector has its own enabled toggle, so you can stage one without offering it: a disabled connector keeps its configuration and its linked identities, and its button simply does not render.

Generic OIDC

A generic OIDC connector needs the provider's issuer URL. Configuration is read from the provider's own discovery document ({issuer}/.well-known/openid-configuration), so there are no endpoint URLs to transcribe. By default the flow requests the openid, email, and profile scopes; set the connector's scopes to override that.

The issuer must be a public https URL. We refuse issuers that resolve to private, loopback, or otherwise internal addresses — at save time and again on every outbound call — because your issuer URL is a URL our servers fetch.

Turning social sign-in on and off

Two switches have to agree before a button renders on the sign-in page: the tenant-wide social sign-in method (dashboard: SecuritySign-in methods), and the connector's own enabled toggle. The tenant-wide method is also writable through the management API:

MethodPathScope
PATCH/api/identity/tenants/{id}identity:tenants:write

The authMethods object you send is merged into the current setting — a switch you omit keeps whatever it is set to, so a request that changes the social switch can name social alone and leave the rest of your policy untouched (send false to turn it off):

TypeScript
const tenantId = Deno.env.get("UDIBO_TENANT_ID");
const response = await fetch(
  `https://www.udibo.com/api/identity/tenants/${tenantId}`,
  {
    method: "PATCH",
    headers: {
      authorization: `Bearer ${Deno.env.get("UDIBO_API_TOKEN")}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({ authMethods: { social: true } }),
  },
);
if (!response.ok) throw new Error(`update failed: ${response.status}`);

password, emailCode and emailLink keep their current values here — the request says nothing about them. Send all four if you would rather state the whole policy; the result is the same when the values agree. A request that would leave every method off is refused, checked against the merged result.

{id} is your tenant's id — the same one in your dashboard URL — and the token must hold administrative authority over that tenant. Connector rows themselves are managed in the dashboard; there is no connector management API today.

Administrator credentials required. These management calls use the credential associated with an authorized Udibo dashboard session. A machine credential issued to an application in your own tenant is not accepted on /api/identity/…. Use the dashboard or an approved administrator workflow; keep administrator credentials out of your application integration.

Last verified 2026-09-09.