SSO Setup

Connect your SSO (OIDC / SAML)

Let your team sign in to publishwith.ai with your corporate identity provider. This guide is for tenant admins. Plan on about 10 minutes once you have admin access to your IdP.

ℹ️
SSO is configured per workspace by a tenant admin under Admin → Auth providers. The single value your identity provider needs from us is the callback URL. The single values we need from you are client_id, client_secret, and issuer.

1. Before you start

SSO availability depends on your plan:

PlanSSO providers
FreeNot available
TeamOne provider
EnterpriseUnlimited (multi-IdP federation)

You also need admin access to your identity provider (Google Workspace, Microsoft Entra ID, Okta, Keycloak, or any OpenID Connect provider) to create an application and read its client credentials.

2. The callback URL

This is the redirect URI your identity provider sends users back to after they sign in. It is fixed per workspace and follows this pattern:

https://publishwith.ai/t/<YOUR-WORKSPACE-SLUG>/auth/oidc/callback
You do not have to build this by hand. Open Admin → Auth providers in publishwith.ai and the exact callback URL for your workspace is shown at the top of the form, ready to copy.
⚠️
Keycloak only: Keycloak matches the * wildcard at the end of a redirect URI and treats a mid-path wildcard literally. Register the exact callback URL, or use a suffix wildcard such as https://publishwith.ai/*. A pattern like /t/*/auth/oidc/callback is rejected with Invalid parameter: redirect_uri.

3. Configure your identity provider

In your IdP, create a new web / confidential OpenID Connect application and configure it as follows.

SettingValue
Application typeWeb (confidential client, has a client secret)
Redirect / callback URIYour callback URL from step 2
Scopesopenid, email, profile
Grant typeAuthorization code

Then collect three values to bring back to publishwith.ai:

ValueWhat it is
client_idThe application's client identifier
client_secretThe application's client secret
issuerThe IdP's issuer URL. Its discovery document lives at <issuer>/.well-known/openid-configuration
⚠️
The issuer must match exactly what the IdP publishes in its discovery document. A mismatch (often just a trailing slash) fails the connection with an issuer mismatch error.

Provider notes

ProviderIssuer
Google Workspacehttps://accounts.google.com
Microsoft Entra IDhttps://login.microsoftonline.com/<tenant-id>/v2.0
Oktahttps://<your-org>.okta.com (or your custom authorization server URL)
Keycloakhttps://<host>/realms/<realm>

4. Add the provider in publishwith.ai

  1. Open Admin → Auth providers and click Add provider.
  2. Pick a Preset (Microsoft Entra ID, Okta, Google Workspace, or Generic OIDC / SAML). The preset fills in a name, default scopes, and an issuer hint — you can change any field.
  3. Fill in Client ID, Client secret, and Issuer. The Issuer field suggests common providers as you type. Scopes default to openid email profile.
  4. Optionally map groups to roles (see below), then Save.

Prefer to paste a full configuration? Expand Advanced: paste config JSON and provide it directly:

{
  "client_id": "your-client-id",
  "client_secret": "your-client-secret",
  "issuer": "https://accounts.google.com",
  "scopes": ["openid", "email", "profile"]
}

The config is encrypted at rest and the client secret is never shown again. You can Edit a provider later (leave the secret blank to keep the current one) or click Test login next to it to verify the flow without leaving the admin panel.

How your users sign in: share your branded workspace login page at https://publishwith.ai/t/<your-workspace-slug>/login, where each configured provider (OIDC or SAML) appears as a sign-in button. Users who land on the main https://publishwith.ai/login can also choose Sign in with SSO and enter your workspace slug to reach the same page.

5. Map IdP groups to roles

Optionally, map groups from your identity provider to publishwith.ai roles so access is assigned automatically at sign-in. In the provider form, use the Group to role mapping rows, or add a group_role_map object to the Config JSON:

{
  "client_id": "your-client-id",
  "client_secret": "your-client-secret",
  "issuer": "https://accounts.google.com",
  "group_role_map": {
    "Engineering": "editor",
    "Leadership": "tenant_admin"
  }
}

Valid roles are viewer, editor, group_admin, and tenant_admin. Group names must match the values your IdP sends in the token.

Directory sync (users and groups), optional

Single sign-on logs people in, but it only tells us about each user as they sign in. To get your full directory ahead of time (so you can share with people who have not logged in yet) and to deactivate accounts automatically when someone leaves, connect directory sync. Two layers, no extra setup required for the first:

To enable SCIM: open Auth providers, find your provider, and click Generate token under "Directory sync (SCIM)". Copy the token (shown once) and the endpoint:

https://publishwith.ai/scim/v2

What "Generate token" actually does

It is not a sign-in for you. It creates the password your identity provider uses to talk to us. Your IdP (Entra, Okta) needs to call our SCIM endpoint on a schedule to push your directory, and it authenticates that call with a single secret bearer token. "Generate token" mints that secret. You paste it into your IdP's provisioning settings (the "Secret Token" / "API Token" field), and from then on the IdP can send us users and groups on its own, with no person signed in.

A few things that are easy to miss:

This SCIM token is also how we get readable group names: SCIM sends each group's id together with its display name, so groups show their real names instead of an internal id.

Then in your IdP's provisioning settings paste both, and enable both Users and Groups:

The Auth providers page shows the live status in plain language (waiting, token rejected, users only, syncing) so you can tell at a glance whether your IdP is reaching us and what it is sending. If it says the token is rejected, click Regenerate and paste the new one.

6. SAML instead of OIDC

If your identity provider speaks SAML rather than OIDC, set Type to SAML and provide the IdP metadata, either by URL or inline XML:

{ "idp_metadata_url": "https://your-idp/metadata" }

or

{ "idp_metadata_xml": "<EntityDescriptor>...</EntityDescriptor>" }

On your IdP side, register these two URLs (the Auth providers form shows them when you pick a SAML provider, so you can copy them verbatim):

Group to role mapping with group_role_map works for SAML as well.

7. Troubleshooting

SymptomCauseFix
Redirect URI mismatch at the IdPThe registered callback does not match exactlyCopy the callback URL from Admin → Auth providers and register it verbatim (Keycloak: use a suffix wildcard)
Issuer mismatchThe issuer in your config differs from the IdP's discovery documentUse the exact issuer the IdP publishes, usually without a trailing slash
Sign-in fails with "no email"The token did not include an email claimAdd the email scope and make sure the IdP releases the email claim
Sign-in fails with "email not verified"The IdP marked the email as unverifiedVerify the user's email in the IdP, or adjust the IdP to assert verified emails
Token exchange failedWrong client secret, or the callback was not authorized at the IdPRe-check the client secret and the registered redirect URI
Add provider button blockedPlan limit reached (Free has no SSO, Team allows one)Upgrade the workspace, or remove an existing provider
Groups show as long ids (GUIDs) instead of namesThe directory read returns group memberships but not group names. With Microsoft Entra over Graph this means the app is missing the Group.Read.All application permission (with admin consent); Entra then returns each group with only its id and no display name.In Entra, grant the app Group.Read.All (or Directory.Read.All) as an Application permission, grant admin consent, then re-run the sync. Alternatively connect SCIM, which sends names directly. As a last resort you can rename a group by hand on the Groups page, but on a synced workspace the provider's name takes over on the next sync.
Directory sync says "token rejected"The SCIM token in your IdP no longer matches (often after a Regenerate)Click Regenerate, copy the new token, and paste it back into your IdP's provisioning settings

Tenant admins can see SSO setup diagnostics for their own workspace under Admin → Audit, including failed login reasons.