Single sign-on for a managed organization uses OpenID Connect: your members sign in to Moon AI with your organization’s credentials (Microsoft Entra ID, Google Workspace, Okta, Keycloak…). Setting it up from the admin console is covered in Single sign-on.
What your provider must offerLink to the What your provider must offer section
| Item | Expected |
|---|---|
| Discovery | A / document under the issuer, over HTTPS, whose issuer is the issuer itself. Its authorization, token and key URLs use HTTPS. |
| Flow | Authorization code with PKCE (S256). |
| Application | A client ID and, usually, a client secret. Without a secret, no client authentication is sent. |
| Redirect URL | https:// |
| Token endpoint authentication | client_secret_basic if the provider advertises it (or advertises nothing), otherwise client_secret_post. |
| ID token signature | RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384 or ES512, with a published RSA or EC key. none and HMAC algorithms (HS…) are refused. |
| Requested scopes | openid by default; openid is always added. |
How a sign-in worksLink to the How a sign-in works section
- The member chooses “Continue with my organization’s SSO” and enters their address, or opens the organization’s sign-in link:
https://.chat.realmoon.ai/ auth? org_sso= <org_id> - Moon AI redirects to your provider with a single-use
stateandnonce, valid for 10 minutes and bound to the browser, plus the PKCE challenge. The address entered is passed aslogin_hint. - Your provider sends the member back to the redirect URL. Moon AI exchanges the code, then checks the ID token: signature,
iss,aud,azp(whenaudhas several values),expandiat(60 seconds of tolerance),subandnonce. - Moon AI reads the address from the chosen claim (
emailby default) and requiresemail_verifiedto be true, unless the “Trust email addresses from this provider” option is on. For a Microsoft Entra ID issuer specific to your tenant (https://), an address withoutlogin.microsoftonline.com/ <tenant id>/ v2.0 email_verifiedis accepted. - The account is opened or created according to the organization’s rules, then the Moon AI account’s two-factor authentication is requested if it is turned on.
An address read from a claim other than email only counts as verified with the “Trust email addresses from this provider” option.
Who sign-in can let inLink to the Who sign-in can let in section
- Without a verified domain: active or suspended members of the organization (a suspended member stays suspended) and people invited as members.
- With a verified domain, or an issuer served on your domain: any address in that domain; the account is created if it does not exist. On a workspace paid by card, or with no free seat, the person is signed in without being added to the organization.
- Never the account of the organization’s owner, nor that of a platform administrator.
- Never an address from a domain verified by another organization, or from a public email provider.
When sign-in is mandatory, the organization’s owner and admins keep their other ways to sign in, so they do not lose access if the provider goes down. When it becomes mandatory, the sessions of every account in the verified domains are closed, except the owner’s.
Claims readLink to the Claims read section
| Setting | Default | Purpose |
|---|---|---|
claim_email | email | The member’s address. |
claim_name | name | The display name; otherwise given_name and family_name, then the start of the address. |
claim_groups | none | The member’s groups at the provider: a string or a list (up to 500). |
group_map | {} | Provider groups to organization groups. |
group_map maps the name or ID of a provider group to one or more organization groups: up to 200 entries, 50 groups per entry. The target groups are static groups, neither synced from the directory nor granting the admin role. At each sign-in, with claim_groups set, the member joins the groups mapped from their provider groups and leaves the ones single sign-on had given them; groups added by hand are never removed.
{
"Enseignants-Lycee": ["<id of the Enseignants group>"],
"Eleves-Seconde": ["<id of the Secondes group>", "<id of the Élèves group>"]
}Read the configuration through the APILink to the Read the configuration through the API section
GET / · security:read
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/sso \
-H "Authorization: Bearer $MOONAI_ADMIN_KEY"import os
import requests
r = requests.get(
f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}/sso",
headers={"Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}"},
timeout=30,
)
r.raise_for_status()
sso = r.json()
print(sso["sso"]["status"] if sso["sso"] else "not configured", sso["sign_in_url"])const r = await fetch(`https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}/sso`, {
headers: { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}` },
});
if (!r.ok) throw new Error(await r.text());
const { sso, sign_in_url } = await r.json();
console.log(sso ? sso.status : 'not configured', sign_in_url);{
"sso": {
"id": "8b0e2c4a-6d8f-4a1b-9c3e-5f7a9b1d3e5f",
"org_id": "8c1f2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
"issuer": "https://login.microsoftonline.com/<tenant id>/v2.0",
"client_id": "<client id>",
"has_secret": true,
"scopes": "openid email profile",
"claim_email": "email",
"claim_name": "name",
"claim_groups": "groups",
"group_map": { "Enseignants": ["<group id>"] },
"enforced": false,
"trust_email": false,
"status": "active",
"provider_metadata": { "issuer": "https://login.microsoftonline.com/<tenant id>/v2.0", "key_count": 3 },
"tested_at": 1762339200,
"last_error": null,
"created_at": 1762000000,
"updated_at": 1762339200
},
"redirect_uri": "https://chat.realmoon.ai/api/v1/orgs/sso/callback",
"verified_domains": ["ecole.fr"],
"sign_in_url": "https://chat.realmoon.ai/auth?org_sso=8c1f2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
"can_configure": false
}sso is null when nothing is configured; status is draft, active or disabled. The secret is never returned (has_secret).
Save the configurationLink to the Save the configuration section
Configuring, testing and removing single sign-on gives access to members’ accounts: only the organization’s owner and admins can do it, from the admin console › “Sign-in” or with their own session. An admin key gets 403 on these routes (can_configure: ).
| Method and path | Purpose |
|---|---|
PUT / | Save the whole configuration. |
POST / | Test an issuer, without turning anything on. |
DELETE / | Remove the sign-in and its secret. |
Body of PUT /sso: it replaces the whole configuration; a missing field goes back to its default value.
| Field | Type | Required | Description |
|---|---|---|---|
issuer | string | yes | The issuer, starting with https://, without parameters (8 to 512 characters). |
client_id | string | yes | The client ID, without spaces. |
client_secret | string | no | The secret; missing or empty, the saved secret is kept. |
scopes | string | no | Space-separated scopes, up to 20. |
claim_email, claim_name, claim_groups | string | no | The claims read. |
group_map | object | no | Missing: the mapping is cleared. |
enabled | boolean | no | false by default: a PUT without enabled turns sign-in off. With true, the provider is tested before it is turned on. |
enforced | boolean | no | Mandatory sign-in for accounts in the verified domains. Requires enabled and at least one verified domain. |
trust_email | boolean | no | Trust the provider’s addresses; missing, the saved choice is kept. |
Changing issuer or client_id puts the configuration back in draft, clears the secret (unless it is sent in the same request) and the members’ links with the old provider: a second PUT with enabled: turns it on. Each change is reported to the organization’s owner.
| Status | code | Cause |
|---|---|---|
| 400 | invalid_request | Invalid field (fields), enforced without enabled. |
| 400 | sso_test_failed | With enabled: , the provider does not respond as expected; reason gives the cause. |
| 403 | owner_only | Neither the organization’s owner nor one of its admins, or an admin key. |
| 409 | sso_enforce_needs_domain | Mandatory sign-in without a verified domain. |
| 409 | directory_group_admin | group_map targets a group that grants the admin role. |
POST / takes {"issuer": (optional: otherwise the saved issuer) and returns 200 with {"ok": , or {"ok": . It is limited to 20 attempts per organization in 10 minutes.
reason | Cause |
|---|---|
issuer_invalid | URL that does not start with https://, or that contains credentials or parameters. |
provider_unreachable | No response within 8 seconds, response too large or unreadable, URL refused. |
provider_http_error | The discovery document or the keys do not return 200. |
discovery_invalid | Incomplete document, or URLs that do not use HTTPS. |
issuer_mismatch | The document’s issuer differs from the URL tested. |
jwks_invalid | No usable RSA or EC signing key. |
Errors members seeLink to the Errors members see section
The member sees a plain-language message; the most common ones and their causes are listed in Single sign-on. Starting a sign-in is limited to 30 attempts per IP address in 10 minutes.