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.
client_id, client_secret, and issuer.SSO availability depends on your plan:
| Plan | SSO providers |
|---|---|
| Free | Not available |
| Team | One provider |
| Enterprise | Unlimited (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.
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
https:// and no trailing slash.* 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.In your IdP, create a new web / confidential OpenID Connect application and configure it as follows.
| Setting | Value |
|---|---|
| Application type | Web (confidential client, has a client secret) |
| Redirect / callback URI | Your callback URL from step 2 |
| Scopes | openid, email, profile |
| Grant type | Authorization code |
Then collect three values to bring back to publishwith.ai:
| Value | What it is |
|---|---|
client_id | The application's client identifier |
client_secret | The application's client secret |
issuer | The IdP's issuer URL. Its discovery document lives at <issuer>/.well-known/openid-configuration |
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 | Issuer |
|---|---|
| Google Workspace | https://accounts.google.com |
| Microsoft Entra ID | https://login.microsoftonline.com/<tenant-id>/v2.0 |
| Okta | https://<your-org>.okta.com (or your custom authorization server URL) |
| Keycloak | https://<host>/realms/<realm> |
Scopes default to openid email profile.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.
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.
groups scope.groups scope.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
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.
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):
https://publishwith.ai/t/<your-workspace-slug>/auth/saml/acshttps://publishwith.ai/t/<your-workspace-slug>/auth/saml/metadataGroup to role mapping with group_role_map works for SAML as well.
| Symptom | Cause | Fix |
|---|---|---|
| Redirect URI mismatch at the IdP | The registered callback does not match exactly | Copy the callback URL from Admin → Auth providers and register it verbatim (Keycloak: use a suffix wildcard) |
| Issuer mismatch | The issuer in your config differs from the IdP's discovery document | Use the exact issuer the IdP publishes, usually without a trailing slash |
| Sign-in fails with "no email" | The token did not include an email claim | Add 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 unverified | Verify the user's email in the IdP, or adjust the IdP to assert verified emails |
| Token exchange failed | Wrong client secret, or the callback was not authorized at the IdP | Re-check the client secret and the registered redirect URI |
| Add provider button blocked | Plan 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 names | The 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.