Single sign-on (SSO)

Blobify supports one OpenID Connect identity-provider configuration per organization. Okta, Auth0, Microsoft Entra ID, Google Workspace, Keycloak, and other OIDC-compliant providers can use the same authorization-code flow with PKCE.

An organization admin configures SSO. Blobify maps identity-provider group claims to a Blobify role and space access on every SSO login.

Sign-in flow

  1. The user enters an email on the Blobify login page.
  2. The dashboard calls POST /v1/auth/sso/lookup.
  3. Blobify returns enabled SSO organizations for existing memberships and whether password login is allowed.
  4. The browser navigates to GET /v1/auth/sso/start?orgId=...&redirectUri=....
  5. Blobify performs OIDC discovery and redirects to the provider with a PKCE S256 challenge, state, and nonce.
  6. A short-lived HTTP-only cookie stores the PKCE verifier, state, nonce, and target organization.
  7. The provider redirects the browser to the dashboard callback with an authorization code.
  8. The dashboard posts the code and state to POST /v1/auth/sso/callback.
  9. Blobify verifies the callback, exchanges the code, reads claims, resolves access, links or creates the membership, and returns a normal Blobify session.

The SSO state cookie uses SameSite=Lax, expires after 10 minutes, and is cleared after callback completion.

Register Blobify with the provider

Create an OIDC web application and register this redirect URI:

textcode
https://blobify.io/auth/sso/callback

Copy the exact value shown in Settings > SSO if you use another dashboard origin.

Collect the issuer URL, client ID, and client secret. Blobify runs discovery against the issuer's standard OpenID configuration. Ensure the ID token contains the configured email claim and, when you use group mappings, the configured groups claim.

Configure the organization

Use Settings > SSO or the admin-only IDP endpoint. Creating a configuration requires a client secret. On a later update, omit clientSecret to keep the stored value.

bashcode
curl -X PUT "https://api.blobify.io/v1/orgs/$ORG_ID/idp" \
  -H "Authorization: Bearer $ADMIN_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "oidc",
    "enabled": true,
    "enforced": false,
    "issuer": "https://acme.okta.com/oauth2/default",
    "clientId": "YOUR_CLIENT_ID",
    "clientSecret": "YOUR_CLIENT_SECRET",
    "scope": "openid profile email groups",
    "emailClaim": "email",
    "groupsClaim": "groups",
    "defaultRole": null,
    "groupRoleMap": [
      { "group": "blobify-admins", "role": "admin", "spaces": ["*"] },
      { "group": "content-team", "role": "editor", "spaces": ["main"] }
    ]
  }'
FieldBehavior
providerMust be oidc
enabledMakes this configuration available for SSO login
enforcedBlocks password login as described below
issuerBase issuer URL used for OIDC discovery
clientIdOIDC client ID
clientSecretRequired on create, optional on update, encrypted at rest, and never returned
scopeSpace-separated scopes; Blobify ensures openid is first; default is openid profile email
emailClaimDirect claim name for the user's email; default is email
groupsClaimDirect claim name for groups; default is groups; accepts a string or string array
defaultRoleFallback role when no group matches; null denies access
groupRoleMapGroup-to-role and space-scope mappings

GET /v1/orgs/{orgId}/idp never returns the secret. It returns hasClientSecret. If the stored secret cannot be decrypted, it also returns secretDecryptionFailed: true so an admin can replace it.

Resolve roles and spaces

Blobify ranks roles as viewer < editor < developer < admin.

For each login:

  1. Every map entry whose group occurs in the provider claim matches.
  2. The highest-ranked matching role wins.
  3. Spaces from all matches are unioned.
  4. A * in any matching entry grants all spaces.
  5. If nothing matches, Blobify assigns defaultRole with all spaces when a default exists.
  6. If nothing matches and defaultRole is null, sign-in returns 403 Forbidden.

Role and space access are synchronized from the claims on every SSO login.

Provision users and memberships

The provider's sub claim is the stable external identity.

  • If an organization membership already has that sub, Blobify updates its role and spaces.
  • If the email belongs to an existing member, Blobify links the sub to that membership.
  • Otherwise Blobify creates a passwordless user when needed and adds the organization membership just in time.

Subsequent logins match the membership by sub, so a later email change does not create a second membership for the same provider identity.

Enforced mode

Set enforced: true only after testing the provider and group mapping. Blobify enforces this on the API, not only in the login form.

The current password-login rule is account-wide: if any enabled SSO organization that the user belongs to has enforcement enabled, password login is blocked for that user. The login lookup returns passwordAllowed: false, and direct password authentication is also rejected by the API.

Leave enforcement off during rollout if existing password access must continue.

Security properties

  • PKCE S256 protects the authorization-code exchange.
  • state protects the browser flow, and nonce binds the ID token.
  • The state cookie is HTTP-only, short-lived, and single-use.
  • The client secret is encrypted before persistence and omitted from API responses.
  • The requested dashboard callback must match Blobify's server-side origin allowlist.
  • Organization IDP read, write, and delete endpoints require admin.

Remove SSO

bashcode
curl -X DELETE "https://api.blobify.io/v1/orgs/$ORG_ID/idp" \
  -H "Authorization: Bearer $ADMIN_JWT"

The endpoint returns 204 No Content. Removing the configuration disables SSO for the organization. Existing users with passwords can use password login again. Users created only through SSO do not have a password until they complete a password setup or recovery flow.