> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# Troubleshoot Modular SSO

Fix common Modular SSO errors by their exact text: a missing connection selector, unregistered callback URLs, failed code exchanges, IdP-initiated login and SAML problems.
This page lists the problems developers hit most often while adding Modular SSO. Find the error you got in the table, then follow its link for the cause and the fix. **You** fix most of them in your app or your Scalekit settings; the ones marked **IT admin** are fixed by your customer's IT admin in their identity provider (IdP).

| Error | When it happens | Who fixes it |
| ----- | --------------- | ------------ |
| [`invalid_connection_selector`](#invalid_connection_selector) | Starting the login | You |
| [redirect_uri is missing in the client configuration](#redirect_uri-is-missing-in-the-client-configuration) | Starting the login | You |
| [unable to retrieve client by id](#unable-to-retrieve-client-by-id) | Starting the login | You |
| [`invalid_request`: scope missing](#invalid_request) | Starting the login | You |
| [`invalid_scope`](#invalid_scope) | Exchanging the code | You |
| [`invalid_grant`: redirect_uri does not correspond](#invalid_grant-redirect_uri-does-not-correspond) | Exchanging the code | You |
| [`invalid_grant`: invalid code](#invalid_grant-invalid-code) | Exchanging the code | You |
| [`invalid_client`](#invalid_client) | Exchanging the code | You |
| [`invalid_idp_initiated_sso`](#invalid_idp_initiated_sso) | Login started at the IdP | You |
| [`invalid_in_response_to`](#invalid_in_response_to) | At the IdP (SAML) | You |
| [`invalid_saml_metadata`](#invalid_saml_metadata) | At the IdP (SAML) | IT admin |
| [`mandatory_attribute_missing`](#mandatory_attribute_missing) | At the IdP | IT admin |
| [The user's name is empty](#the-users-name-is-empty) | After the login | IT admin |
| [The admin portal loads a blank page](#the-admin-portal-loads-a-blank-page) | Admin portal | IT admin |

For other codes, see the [SSO error reference](/sso/reference/sso-integration-errors/). To see what happened in one login, find it under **Logs** in the dashboard; see [Auth logs](/authenticate/sso/logs/).

## Where errors show up

- **On your callback URL.** When a login fails at Scalekit or at the IdP, Scalekit redirects the user to your callback URL with `error`, `error_description` and your `state`, instead of a `code`.
- **From the code exchange.** The SDK throws an error that carries Scalekit's JSON response, such as `{"error":"invalid_grant","error_description":"invalid code"}`. In Node.js, read it from `err.response.data`; in Python, it's in the exception's message. Go by the `error` value, not the HTTP status.
- **On a Scalekit error page.** When Scalekit can't trust the callback URL or the client ID, it can't redirect the user, so it shows the error itself.

## Starting the login

### `invalid_connection_selector`

```text
error=invalid_connection_selector
error_description=Need additional details to initiate SSO. Atleast one of: (organization_id, external_id, connection_id, domain).
```

**Cause:** Scalekit couldn't tell which customer's IdP to send the user to. The authorization URL has no organization ID, connection ID, domain or login hint, or the one it has isn't in this environment, such as an organization ID copied from development into production, or an email domain that isn't on any organization.

**Fix:** Pass one of them when you build the [authorization URL](/sso/guides/authorization-url/), using IDs from the same environment as your API credentials. To route by email, first add the customer's domain to their organization; see [Route users to SSO by email domain](/guides/user-auth/check-sso-domain/).

### redirect_uri is missing in the client configuration

Scalekit shows this on its own error page, with HTTP status 400:

```text
The requested redirect_uri is missing in the client configuration. If you have any questions, you may contact the administrator of the application.
```

**Cause:** The callback URL in your authorization URL isn't one of the environment's allowed callback URLs. The match is exact, so `http://localhost:3000/auth/callback` and `http://localhost:3001/auth/callback` are different URLs.

**Fix:** Add the URL under **Authentication** > **Redirect URLs** > **Allowed callback URLs**; see [Register redirect URLs](/authenticate/sso/redirect-urls/). An app served from several hosts, such as one per region or per customer subdomain, needs each callback URL registered.

### unable to retrieve client by id

**Cause:** Scalekit shows this on its own error page, with HTTP status 400, when the client ID in the authorization URL doesn't belong to this environment.

**Fix:** Take `SCALEKIT_ENVIRONMENT_URL` and `SCALEKIT_CLIENT_ID` from the same environment, under **Developers** > **Settings** > **API Credentials**.

### `invalid_request`

```text
error=invalid_request
error_description=The scope of your request is missing. Please ensure some scopes are requested. If you have any questions, you may contact the administrator of the application.
```

**Cause:** The authorization URL has no `scope`. The SDK adds `openid profile email` for you, so this happens when the URL is built by hand.

**Fix:** Build the URL with the SDK, or add `scope=openid profile email`.

## Exchanging the code

### `invalid_scope`

```json
{"error":"invalid_scope","error_description":"invalid scope offline_access not allowed"}
```

**Cause:** The authorization URL asked for `offline_access`, which requests a refresh token. The login succeeds and the code exchange fails. In Modular SSO your app runs the session, so Scalekit doesn't issue refresh tokens.

**Fix:** Remove `offline_access` from the scopes. When your app's session ends, send the user through SSO again; see [Start your session](/authenticate/sso/handle-callback/#start-your-session).

### `invalid_grant`: redirect_uri does not correspond

```json
{"error":"invalid_grant","error_description":"redirect_uri does not correspond"}
```

**Cause:** The redirect URI you passed to the code exchange isn't the one you passed to the authorization URL.

**Fix:** Pass the same URL to both. Keep it in one setting, such as the `REDIRECT_URI` environment variable the [callback guide](/authenticate/sso/handle-callback/) uses, rather than building it from the incoming request, which can differ behind a proxy.

### `invalid_grant`: invalid code

```json
{"error":"invalid_grant","error_description":"invalid code"}
```

**Cause:** A code works once. It was usually already exchanged: the user refreshed the callback page or went back to it, or your app handled the same callback twice.

**Fix:** Exchange each code once, then redirect away from the callback URL so a refresh doesn't replay it. If an exchange fails, start a new login rather than retrying the code.

### `invalid_client`

```json
{"error":"invalid_client","error_description":"invalid client_id / client_secret"}
```

**Cause:** The client secret doesn't match the client ID, often because it's from another environment or was replaced.

**Fix:** Use the client ID and secret from the same environment. After you generate a new secret, update it wherever your app is deployed.

## Logins that start at the IdP

### `invalid_idp_initiated_sso`

```text
error=invalid_idp_initiated_sso
error_description=Initiate-login URL is not configured for the environment
```

**Cause:** A user started from their IdP's dashboard, such as an Okta app tile, and your production environment has no initiate login URL. A development environment falls back to a Scalekit page instead, so this often shows up first after go-live.

**Fix:** Build an initiate login endpoint that accepts a `GET` request, and register it under **Authentication** > **Redirect URLs** > **Initiate login URL**; see [IdP-initiated SSO](/sso/guides/idp-init-sso/). Test it with the SSO simulator's **IDP Initiated SSO** option. Until it's live, ask the customer's users to start from your app's login page.

## Problems at the identity provider

### `invalid_in_response_to`

**Cause:** The SAML response doesn't match a login Scalekit started in this browser. Scalekit tracks each SAML login with a short-lived cookie, which is lost when the app opens the login in an embedded web view, when the user runs two logins at once in the same browser, or when they take several minutes at the IdP.

**Fix:** Open SSO logins in the system browser, not an embedded web view, one at a time. A user who sees this once can start the login again.

### `invalid_saml_metadata`

**Cause:** Scalekit can't use the IdP's SAML metadata or signing certificate, so logins for that connection fail. The most common cause is a signing certificate that was uploaded by hand and has expired.

**Fix:** Your customer's IT admin uploads the IdP's current signing certificate, or switches the connection to a metadata URL, which picks up new certificates when the IdP rotates them. See [Certificate expiry and rotation](/sso/guides/security-and-compliance/#certificate-expiry-and-rotation).

### `mandatory_attribute_missing`

**Cause:** The IdP didn't send an attribute Scalekit needs, such as the user's email address or, for SAML, a `NameID`. The `error_description` names it.

**Fix:** Your customer's IT admin maps the attribute in their IdP's app settings, then checks it with **Test Connection** in the [admin portal](/authenticate/sso/admin-portal/).

### The user's name is empty

**Cause:** The login succeeds, but the profile has no `givenName` or `familyName`, so your app shows a blank name or `undefined undefined`. The IdP sends only the email address.

**Fix:** Your customer's IT admin maps the first and last name attributes in their IdP, for example in Okta's attribute statements, and checks them with **Test Connection** in the admin portal. In your app, fall back to the email address when the name is empty.

## Admin portal

### The admin portal loads a blank page

**Cause:** The portal works for you but is blank for your customer, because their network blocks the files it loads.

**Fix:** Your customer's IT admin allows your Scalekit environment's domain, or your [custom domain](/authenticate/sso/custom-domain/) if you use one, and Google Fonts (`fonts.googleapis.com` and `fonts.gstatic.com`).

## Still stuck

Find the login under **Logs** in the dashboard: a failed login has the status **Failure** and the error details. When you contact support, include the login's time, the organization ID and the `error` and `error_description` values.

## Next

- [SSO error reference](/sso/reference/sso-integration-errors/): the error codes your callback can receive and how to resolve them.
- [Production readiness checklist](/sso/guides/launch-checklist/): what to check before your first customer goes live.


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
