Docs/Integration deep dive

Social SSO + Stripe + SDK Deep Dive

End-to-end integration reference for Doorman-managed auth and billing in a third-party app.

Overview

This guide covers the full path from sign-in to paid entitlement: provider-first social login, optional enterprise domain discovery, Stripe recurring billing setup, and runtime SDK integration.

  • Third-party app handles login/register UI.
  • Doorman handles OAuth/OIDC and identity session orchestration.
  • Stripe handles payment collection and provider-native billing state.
  • Doorman remains the source of truth for entitlement gating.

Architecture

Third-party app (sample-spa) -> Hydra authorize -> Doorman /oauth/login
Doorman /oauth/login -> control-api session check
Doorman /oauth/login -> control-api external SSO broker (org provider config)
External provider (Google/Microsoft/GitHub) -> control-api callback -> Hydra login accept
Third-party app -> control-api app-key billing APIs -> Stripe
control-api + workers -> subscription sync + usage export -> Stripe

Runtime endpoints: Control API: https://doorman.f1cs-dev.it, Hydra issuer: https://doorman.f1cs-dev.it/oauth2.

Provider-first SSO (Google/Microsoft/GitHub)

This is the consumer-style flow: user clicks a provider button and goes through Doorman's app-scoped provider broker. It does not require domain mapping.

  1. In Doorman dashboard, create org identity provider records for the app org (issuer URL, client ID, client secret).
  2. Configure provider apps with callback URL:
    • All providers (Google/Microsoft/GitHub): https://doorman.f1cs-dev.it/api/oauth/sso/callback
  3. From app login, Doorman resolves available org providers for that app and either:
    • auto-starts when a provider hint resolves, or
    • auto-starts when exactly one provider exists, or
    • shows provider picker at `/oauth/login`.
  4. Direct API start endpoint:
    curl -i -sS "https://doorman.f1cs-dev.it/api/oauth/sso/start?login_challenge=<challenge>&provider_id=<provider_id>"

This flow is app/org-scoped and does not depend on global Kratos social provider configuration.

Google setup for sample-spa (local)

  1. In Google Cloud Console, create OAuth client credentials with app type Web application.
  2. Set Google authorized redirect URI to: https://doorman.f1cs-dev.it/api/oauth/sso/callback.
  3. In Doorman dashboard, for the same org that owns the sample-spa app, create identity provider:
    • Type: OIDC
    • Name: google (or Google)
    • Issuer URL: https://accounts.google.com
    • Client ID/secret: from Google OAuth client credentials
  4. Set control-api public URL in env and restart control-api:
    # services/control-api/.env
    CONTROL_API_PUBLIC_URL=https://doorman.f1cs-dev.it
  5. In sample-spa, click Login with Google. Doorman should broker provider login and return to /callback in the app.

If you use 127.0.0.1 instead of localhost anywhere, register that exact callback host in Google and keep it consistent across control-api URL, browser URL, and provider config.

Domain-discovery SSO (enterprise routing)

This flow maps email domain to org identity provider. Use it for enterprise environments where `@company.com` should route deterministically.

  1. Create org identity provider in Doorman.
  2. Map domain(s) on provider (for example `acme.com`).
  3. Resolve provider from email:
    curl -sS "https://doorman.f1cs-dev.it/api/sso/resolve-provider?email=user@acme.com"
  4. App starts OIDC redirect with resolver context (`doorman_provider_id`, `doorman_org_id`, `login_hint`).

`provider_not_found` is expected for unmapped domains and should fall back to password/registration path.

Stripe connection

Configure Stripe at control-api level first. Without this, checkout and status operations cannot call Stripe.

# services/control-api/.env
STRIPE_DRY_RUN=0
STRIPE_API_BASE=https://api.stripe.com
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SIGNING_SECRET=whsec_...

Webhook endpoint: POST /api/billing/stripe/webhook with stripe-signature provider signature verification.

Plan, customer, and meter mapping

Stripe recurring requires three mapping layers. Missing one causes explicit errors.

  1. Plan component mapping: plan_id + component kind -> Stripe price_... in /api/apps/:appId/billing/provider-plan-components.
  2. Customer mapping: tenant_id -> cus_... in /api/apps/:appId/billing/ensure-customeror bootstrap path.
  3. Meter mapping for usage export workers: /api/apps/:appId/billing/provider-meters.

`external_price_id` must be a Stripe price ID (`price_...`), not a numeric amount.

Third-party SDK integration pattern

Use `@doorman/react-sdk` to centralize auth, SSO, billing, and generic authorization mechanics.

For fastest onboarding, embed DoormanAuthPanel in your app login page. It includes register/login + direct provider SSO buttons + domain-discovery SSO.

import {
  DoormanAuthPanel,
  DoormanProvider,
  RequirePolicy,
  buildDoormanOidcClientSettings,
  createDoormanSsoClient,
  createDoormanBillingClient,
  createDoormanServerContext,
  requireServerPolicy,
  handleDoormanOidcCallback
} from "@doorman/react-sdk";
<DoormanAuthPanel
  userManager={userManager}
  kratosPublicUrl="http://localhost:4433"
  controlApiBase="https://doorman.f1cs-dev.it"
/>
  1. Authenticate user (provider-first, discovery, or password).
  2. Handle callback idempotently with SDK helper.
  3. Resolve tenant for identity (`sub`).
  4. Bootstrap tenant only when missing; ensure customer for existing tenant.
  5. Create checkout session and redirect to Stripe.
  6. Poll subscription status and gate paid features by `is_active`.
  7. Parse Doorman claims and enforce tenant permissions with SDK policy helpers.
  8. Ingest usage events.
<DoormanProvider user={user} appId="APP_ID">
  <RequirePolicy
    rule={{ type: "permission", permission: "resources:read" }}
    fallback={<NoAccess />}
  >
    <Workspace />
  </RequirePolicy>
</DoormanProvider>

React gates are for UX only. Protected mutations still need server-side checks with requireServerPolicy or equivalent app-side logic.

SDK/API contract map

Use this mapping when handing the SDK to app teams. It keeps auth context explicit and shows where each SDK call lands in control-api/Swagger.

For strict implementation requirements and pass/fail integration criteria, use the dedicated contract page:/docs/api-contract.

  • Swagger UI: https://doorman.f1cs-dev.it/docs/api/
  • OpenAPI JSON: https://doorman.f1cs-dev.it/docs/api/json
  • Checked contract: services/control-api/contracts/control-api.openapi.json
  • createDoormanBillingClient methods use x-doorman-api-key app-key endpoints.
  • createDoormanSsoClient methods use browser session cookies (owner/org-admin context).
  • parseDoormanClaims, can, RequirePolicy, and requireServerPolicy are local SDK helpers; they do not call control-api.
@doorman/react-sdk (billing) -> control-api
listPlans -> GET /api/apps/:appId/billing/plans
resolveTenantForIdentity -> POST /api/apps/:appId/billing/resolve-tenant
bootstrapTenant -> POST /api/apps/:appId/billing/bootstrap-tenant
ensureBillingCustomer -> POST /api/apps/:appId/billing/ensure-customer
createCheckoutSession -> POST /api/apps/:appId/billing/checkout-sessions
getSubscriptionStatus -> GET /api/apps/:appId/billing/subscriptions/:subscriptionId/status
getCurrentSubscriptionStatus -> GET /api/apps/:appId/billing/subscriptions/current?tenant_id=...
cancelSubscription/reactivateSubscription/changeSubscriptionPlan/createBillingPortalSession ->
  POST /api/apps/:appId/billing/subscriptions/:subscriptionId/{cancel|reactivate|change-plan|portal-session}
ingestUsageEvent -> POST /api/apps/:appId/events/ingest
getUsageSummary/getAgentUsageSummary -> GET /api/apps/:appId/billing/usage-summary{,/agents}
getCostPreview -> GET /api/apps/:appId/billing/cost-preview
getCapacityUtilization -> GET /api/apps/:appId/billing/utilization
listAgents/createAgent/rotate/revoke/deactivate/delete ->
  /api/apps/:appId/service-accounts...
@doorman/react-sdk (authorization) -> local app/runtime
getDoormanClaimsFromUser -> parse OIDC user access_token/id_token/profile
createDoormanPolicyContext -> choose active tenant
can / requirePolicy -> evaluate roles, permissions, entitlements, actor type
DoormanProvider / RequirePolicy -> client UX gates
createDoormanServerContext / requireServerPolicy -> server action/API enforcement

Service-account lifecycle endpoints accept both app-key runtime callers and Doorman dashboard session callers.

Agent and service-account management

For app backends and agent-like entities, Doorman provides app-scoped service account management. These entities are not sample-spa-only records: they are persisted in Doorman and can be used for usage attribution and plan-cap enforcement.

  • Service-account lifecycle API: /api/apps/:appId/service-accounts plus rotate/revoke/deactivate/delete actions for app-key and owner/admin session contexts.
  • Usage attribution by entity: /api/apps/:appId/events/ingest with optional agent_id and readback via /api/apps/:appId/billing/usage-summary/agents.
  • Cap enforcement: service_accounts and member_seats meter limits block over-cap create/assignment paths deterministically.

Expected data state

After first successful bootstrap for a new app user:

  • Identity exists and has Doorman session.
  • Identity has a default tenant link.
  • Tenant membership exists.
  • Tenant app membership exists (role auto-selection/fallback).
  • Stripe customer mapping exists for org + tenant.

Subscription is created when checkout session is created, not at bootstrap.

Troubleshooting matrix

  • Google Access blocked: This app's request is invalid: verify OAuth client type is Web application and redirect URI matches exactly https://doorman.f1cs-dev.it/api/oauth/sso/callback.
  • Google callback mismatch by host: do not mix localhost and127.0.0.1 unless both are configured everywhere.
  • provider_not_found: domain-discovery mapping missing.
  • Provider unavailable for app login: org/provider mapping missing for the OAuth client app, or provider hint did not match configured provider.
  • missing_provider_plan_base_component_mapping: map a base plan component to Stripe price.
  • missing_provider_customer_mapping: ensure customer mapping for tenant.
  • Checkout `line_items[0][price]` error: wrong value, must be price_... ID.
  • Subscription stuck pending: verify webhook delivery/secret/signature and status polling against same subscription ID.
  • Usage not exported: verify provider meter mapping and run aggregate + sync-usage workers.

Execution checklist

  1. Create org identity provider entries in Doorman dashboard.
  2. Set provider callback URI to https://doorman.f1cs-dev.it/api/oauth/sso/callback.
  3. Configure Stripe credentials and webhook on control-api.
  4. Map Doorman plans to Stripe prices.
  5. Map/create tenant customers.
  6. Run provider-first Google login end to end.
  7. Run domain-discovery mapped and unmapped paths.
  8. Complete checkout and wait for active entitlement.
  9. Send usage event and verify ingestion/export pipeline.