Skip to main content
member.dev can act as an OAuth2/OIDC relying party, letting your hub members authenticate using an identity they already have — Google, Facebook, or any OIDC/OAuth2-compatible company SSO. This is the inverse of the Login with Hub guide (where member.dev is the Identity Provider). After authentication succeeds, the member receives a standard contact JWT — the same token used for every other member API call.

Provider support matrix

Google is the only provider that can create a new member account without domain verification. For generic OIDC, hub admins can unlock auto-creation by verifying ownership of the member’s email domain — see Verified-Domain SSO below. All other providers require the member to already have an account and to have explicitly connected the provider (see Connect flow).

How it works

The one-time code in the fragment is exchanged server-side for the real token pair. The bearer token never appears in a URL, history, or referrer header.

Prerequisites

  1. The hub admin has registered an external-login provider config (see Admin: configure a provider).
  2. For Google: any member can log in (first-time creates an account using the verified email).
  3. For Facebook / Generic: the member has already connected their external account while logged in (see Connect flow).

Login flow (frontend implementation)

Step 1 — Redirect the member to the provider

The backend validates return_to, generates PKCE + state + nonce, stores a server-side flow binding (10-minute TTL), sets an HttpOnly CSRF cookie, and redirects the browser (302) to the external provider’s authorization URL. This is a browser redirect, not a fetch. Trigger it by setting window.location.href or using an <a> element.

Step 2 — Provider authenticates the member

The provider redirects back to:
The backend handles this entirely server-side:
  • Validates the CSRF cookie against the stored binding
  • Exchanges the code for an access token / id_token via a pinned, SSRF-guarded server-to-server call
  • Verifies the id_token signature (OIDC) or fetches userinfo (OAuth2)
  • Resolves or creates the member’s contact (see Account linking)
  • Stores a single-use bootstrap code (60-second TTL) bound to the member’s contact
On success, the member’s browser is redirected to:
On any error, the backend returns a generic 400 JSON response. No internal details are exposed.

Step 3 — Exchange the bootstrap code for a token pair

Your frontend extracts code from window.location.hash and POSTs it:
Response (200):
The mio_contact_refresh HttpOnly cookie is also set automatically. Use access_token as the Authorization: Bearer token for all subsequent member API calls. Errors (400):
The bootstrap code is single-use and expires in 60 seconds. Do not replay it.

Connect flow

Facebook and generic providers (OIDC / OAuth2) do not auto-create accounts. A member must explicitly connect the provider to their existing contact while they are logged in.

Initiate a connect

return_to is a query parameter (not a JSON body). The endpoint accepts no request body. Response (200):
Redirect the member’s browser to authorize_url. The backend sets the CSRF cookie as part of this response. After the member authenticates at the provider, the callback runs the same verification path as the login flow. On success, the provider identity is linked to the member’s contact and the browser is redirected to return_to (no new token is issued — the member is already logged in). After a successful connect, the member can use the normal login flow (Step 1–3 above) for all future sessions with that provider.

Account linking

The backend follows a strict account-linking decision table. The outcome is determined entirely by verified, server-side assertions — the browser never supplies identity. Why Google only for rows 2/3: Google’s token endpoint and JWKS are hardcoded constants in the backend — they cannot be overridden by admin configuration. This makes Google’s email_verified claim trustworthy. All other providers are “untrusted” because their identity endpoints are admin-supplied, and a malicious hub owner could otherwise forge email claims to take over arbitrary member accounts.

Security notes

  • CSRF: every flow is CSRF-protected by a persistent HttpOnly CSRF cookie (__Host-extlogin_csrf in production). The binding stores a hash of the cookie; the callback re-hashes and compares in constant time. Concurrent flows from the same browser share the cookie — starting provider A then B does not cancel A’s flow.
  • PKCE S256 is always used. The code_verifier lives only on the server in the flow binding.
  • No bearer token in URLs. The bootstrap code is a short-lived, single-use opaque value. Redeeming it requires an Origin header match against the code’s bound return-origin.
  • Token scope: the issued contact token covers only the hub that initiated the login. A hostile hub cannot trigger a login that produces a token covering the member’s other hubs.
  • Enumeration: all callback failures return one generic error response. The specific failure reason is written to the audit log only.
  • Rate limits: /start, /callback, /connect, and /session are rate-limited by IP.

Worked example: Google login


Worked example: Facebook connect then login


Verified-Domain SSO (v2)

By default, a generic_oidc provider can only log in members who have already connected it (row 0 → row 1 in the account-linking table). With Verified-Domain SSO, a hub admin can unlock automatic account creation for members whose email belongs to a company domain the hub has proven it controls.

How it extends v1

The connect-first rule is permanent: if a member already has a member.dev account with alice@acme.com, logging in through the OIDC provider will not silently adopt that account. Alice must sign in with her existing method and connect the provider while logged in.

Set up in three steps

  1. Register a domainPOST /api/v1/teams/{team_id}/verified-domains {hub_id, domain}. Returns the DNS TXT record to publish.
  2. Publish the TXT record — add _member-dev-challenge.<domain> = member-dev-verify=<token> to your DNS provider.
  3. Trigger verificationPOST /api/v1/teams/{team_id}/verified-domains/{id}/verify. The backend resolves the TXT; on success the domain moves to verified.
From that point on, any new @<domain> member who authenticates through a generic_oidc provider on this hub gets an account created automatically (subject to the hub’s registration_enabled setting). See the Verified-Domain SSO guide for the full walkthrough, DNS record format, lifecycle management, and security notes.

Admin: configure a provider

See the External Login Provider configuration guide for the full admin API reference, per-provider required fields, and where to obtain the callback_url to register in the provider console.