> ## Documentation Index
> Fetch the complete documentation index at: https://docs.member.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure an external-login provider (Admin)

> Register Google, Facebook, or a generic OIDC/OAuth2 provider so your hub members can use it to log in.

This guide covers the admin API for registering and managing external-login providers. Hub members use these providers to authenticate — see [Log in with an external provider](/guides/external-login) for the member-facing flow.

**Requires:** a real user (JWT) session with `team_owner` role. API keys cannot manage provider configs.

***

## The callback URL

Every provider config exposes a `callback_url` in its read response. This is the URL you must register in the external provider's developer console as an allowed redirect URI.

The callback URL has this form:

```
https://api.member.dev/api/v1/external-login/callback
```

Register this exact URL in the provider console (Google Cloud Console, Facebook App Dashboard, your company IdP, etc.) before testing the login flow.

***

## Register a Google provider

Google is the only provider that can auto-create member accounts on first login without additional configuration (verified email required). A `generic_oidc` provider can also auto-create accounts for new members whose email belongs to a company domain the hub has verified — see the [Verified-Domain SSO guide](/guides/external-login-verified-domains). Facebook and `generic_oauth2` never auto-create accounts regardless of domain configuration.

**Prerequisites:**

* A Google Cloud project with the OAuth consent screen configured.
* OAuth 2.0 credentials (client ID + secret) for a "Web application".
* The `callback_url` from Step 1 registered as an "Authorised redirect URI" in Google Console.

```bash theme={null}
curl -X POST "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "attributes": {
        "provider_kind": "google",
        "display_name": "Google",
        "client_id": "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com",
        "client_secret": "YOUR_GOOGLE_CLIENT_SECRET"
      }
    }
  }' | jq
```

**Response (201):**

```json theme={null}
{
  "data": {
    "type": "external_login_providers",
    "id": "cfg_abc123",
    "attributes": {
      "provider_kind": "google",
      "slug": "google",
      "display_name": "Google",
      "client_id": "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com",
      "client_secret_set": true,
      "enabled": true,
      "callback_url": "https://api.member.dev/api/v1/external-login/callback",
      "created_at": "2026-06-25T10:00:00Z",
      "updated_at": "2026-06-25T10:00:00Z"
    }
  }
}
```

`client_secret` is **write-only** — it is AES-256-GCM encrypted at rest and never returned. The `client_secret_set: true` flag confirms a secret is stored.

Google's authorization, token, and JWKS endpoints are hardcoded platform constants — you do not supply them.

***

## Register a Facebook provider

Facebook members cannot auto-create accounts. They must [connect their Facebook identity](/guides/external-login#connect-flow) while already logged in, then use Facebook to log in on subsequent visits.

**Prerequisites:**

* A Facebook App (facebook.com/developers) with "Facebook Login" product added.
* The `callback_url` added to "Valid OAuth Redirect URIs" in the Facebook App Dashboard.

```bash theme={null}
curl -X POST "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "attributes": {
        "provider_kind": "facebook",
        "display_name": "Facebook",
        "client_id": "YOUR_FACEBOOK_APP_ID",
        "client_secret": "YOUR_FACEBOOK_APP_SECRET"
      }
    }
  }' | jq
```

Facebook's Graph API endpoints are hardcoded platform constants.

***

## Register a generic OIDC provider

Use this for company SSO (Okta, Auth0, Azure AD, Keycloak, etc.) or any standards-compliant OIDC Identity Provider.

By default, generic OIDC members cannot auto-create accounts — they must connect while logged in first. Exception: if the hub has a [Verified Domain configured](/guides/external-login-verified-domains), new members whose email matches a verified domain can have accounts created automatically on first login (connect-first: the platform never merges into an existing account).

**Prerequisites:**

* The `callback_url` registered as a redirect URI in your IdP.
* Your IdP's discovery URL (typically `https://your-idp.example.com/.well-known/openid-configuration`).

**Security constraints enforced at write time:**

* `issuer` must **not** be `https://accounts.google.com` or any other platform-trusted issuer. Generic configs claiming a trusted issuer are rejected (prevents token-forging attacks).
* All URL fields (`discovery_url`, `authorize_url`, `token_url`, `userinfo_url`, `jwks_uri`) are SSRF-validated: HTTPS-only, no loopback, no private IPs, no cloud metadata endpoints.

```bash theme={null}
curl -X POST "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "attributes": {
        "provider_kind": "generic_oidc",
        "display_name": "Acme Corp SSO",
        "slug": "acme-sso",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "issuer": "https://auth.acme.example.com",
        "discovery_url": "https://auth.acme.example.com/.well-known/openid-configuration",
        "scopes": ["openid", "email", "profile"]
      }
    }
  }' | jq
```

You can supply individual endpoint URLs (`authorize_url`, `token_url`, `jwks_uri`) instead of `discovery_url` if your IdP does not expose a discovery document. If `discovery_url` is supplied, it takes precedence and the individual fields are populated from it at runtime — supply only the ones your IdP does not include in its discovery document.

***

## Register a generic OAuth2 provider

Use for providers that do not support OIDC (no `id_token`) but expose a userinfo-style endpoint.

```bash theme={null}
curl -X POST "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "attributes": {
        "provider_kind": "generic_oauth2",
        "display_name": "My OAuth2 Provider",
        "client_id": "YOUR_CLIENT_ID",
        "client_secret": "YOUR_CLIENT_SECRET",
        "authorize_url": "https://provider.example.com/oauth/authorize",
        "token_url": "https://provider.example.com/oauth/token",
        "userinfo_url": "https://provider.example.com/oauth/userinfo",
        "scopes": ["email", "profile"],
        "claim_map": {
          "sub": "id",
          "email": "email_address",
          "name": "full_name"
        }
      }
    }
  }' | jq
```

`claim_map` is optional. Use it when the provider's userinfo response uses non-standard claim names. Keys are the standard member.dev field names (`sub`, `email`, `email_verified`, `name`); values are the corresponding claim names in the provider's userinfo response. The `sub` field is required.

***

## Provider config attributes reference

| Attribute           | Type             | Writable                 | Notes                                                                                                                                                                                |
| ------------------- | ---------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `provider_kind`     | string           | Create                   | `google`, `facebook`, `generic_oidc`, `generic_oauth2`                                                                                                                               |
| `slug`              | string           | Yes                      | URL-path identifier. Defaults to `provider_kind`. Must be unique per hub.                                                                                                            |
| `display_name`      | string           | Yes                      | Button label shown to members.                                                                                                                                                       |
| `client_id`         | string           | Yes                      | Your app ID / client ID from the provider console.                                                                                                                                   |
| `client_secret`     | string           | Yes (write-only)         | Your app secret. AES-256-GCM encrypted at rest. Never returned on read.                                                                                                              |
| `client_secret_set` | boolean          | Read-only                | `true` if a secret is stored.                                                                                                                                                        |
| `enabled`           | boolean          | Yes                      | Set to `false` to disable the provider without deleting the config.                                                                                                                  |
| `callback_url`      | string           | Read-only                | Register this in the provider console.                                                                                                                                               |
| `issuer`            | string           | Yes (generic only)       | OIDC issuer URI. Must match the `iss` claim in tokens.                                                                                                                               |
| `discovery_url`     | string           | Yes (generic only)       | OIDC discovery document URL. SSRF-validated.                                                                                                                                         |
| `authorize_url`     | string           | Yes (generic only)       | Authorization endpoint. SSRF-validated.                                                                                                                                              |
| `token_url`         | string           | Yes (generic only)       | Token endpoint. SSRF-validated.                                                                                                                                                      |
| `userinfo_url`      | string           | Yes (generic only)       | Userinfo / profile endpoint. SSRF-validated.                                                                                                                                         |
| `jwks_uri`          | string           | Yes (generic\_oidc only) | JWKS endpoint for id\_token verification. SSRF-validated.                                                                                                                            |
| `scopes`            | array of strings | Yes (generic only)       | OAuth2 scopes to request.                                                                                                                                                            |
| `claim_map`         | object           | Yes (generic only)       | Maps standard field names → provider claim names. Key = member.dev standard name (`sub`, `email`, `email_verified`, `name`); value = provider's claim name in the userinfo response. |

`issuer`, `discovery_url`, `authorize_url`, `token_url`, `userinfo_url`, `jwks_uri`, `scopes`, and `claim_map` are ignored for pre-canned providers (`google`, `facebook`) — their endpoints are hardcoded platform constants.

***

## List providers

```bash theme={null}
curl "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" | jq '.data[] | {id: .id, kind: .attributes.provider_kind, enabled: .attributes.enabled}'
```

Returns all providers including disabled ones.

***

## Retrieve a provider

```bash theme={null}
curl "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers/$PROVIDER_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" | jq
```

***

## Update a provider (PATCH)

PATCH is partial — only supply the fields you want to change.

```bash theme={null}
# Disable a provider
curl -X PATCH "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers/$PROVIDER_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "id": "'"$PROVIDER_ID"'",
      "attributes": {
        "enabled": false
      }
    }
  }'
```

```bash theme={null}
# Rotate the client secret
curl -X PATCH "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers/$PROVIDER_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "external_login_providers",
      "id": "'"$PROVIDER_ID"'",
      "attributes": {
        "client_secret": "NEW_SECRET"
      }
    }
  }'
```

***

## Delete a provider

Soft-deletes the config. Existing member logins linked to this provider will fail (no existing link to resolve against). Returns `204 No Content`.

```bash theme={null}
curl -X DELETE "https://api.member.dev/api/v1/teams/$TEAM_ID/external-login-providers/$PROVIDER_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN"
```

***

## Common errors

| HTTP  | Code                                | Meaning                                                                                               |
| ----- | ----------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `422` | `ssrf_rejected`                     | A generic provider URL failed SSRF validation (must be HTTPS, no private IPs).                        |
| `422` | `trusted_issuer_reserved`           | `issuer` for a generic config matches a platform-trusted issuer (e.g. `https://accounts.google.com`). |
| `422` | `no_active_hub`                     | The team has no active hub. Create a hub first.                                                       |
| `404` | `external_login_provider_not_found` | The provider ID does not exist or belongs to a different team.                                        |
