Enterprise authentication for the NORG connector

Administrator setup and security reference

One HTTPS endpoint, OAuth 2.1 with mandatory PKCE, and a single scope. Nothing is installed on your infrastructure, no credential is issued to hold, and no user needs setting up before they connect. This page sets out exactly what an approval admits, and how to verify each claim against the live endpoint.

Terms this page uses
Connector
The NORG server an MCP client connects to, at the path /v2/mcp/express. One outbound HTTPS endpoint. Nothing is installed on your network and nothing listens there.
OAuth 2.1 / PKCE
The only way in. Authorization-code flow with PKCE, and only the S256 challenge method is advertised — plain is not offered.
Scope
The permission a user grants at consent. This connector has exactly one, mcp:onboarding, and no mechanism to grant a second.
DCR
Dynamic Client Registration (RFC 7591). A client registers itself at /v2/mcp/register with no pre-shared credential — but only if its callback is already on NORG's exact allowlist.
CIMD
Client ID Metadata Document, the mechanism added in the 2025-11-25 MCP authorization specification. The client's client_id is an https URL, and the JSON document served there declares its own redirect URIs. Offered per environment.
Relay
NORG owns the client-facing authorize, callback and token endpoints itself and passes the request through to its identity provider behind them, so the issuer a client discovers is the issuer it is answered by.
Audience mapper
The identity-provider configuration that puts the connector's client id into an access token's aud claim. NORG-side, but worth knowing: without it every authenticated call fails.

What you are approving

One endpoint, one scope, one user at a time. This page is the technical detail behind that sentence, written so it can be checked rather than taken on trust.

The shape of it
One outbound HTTPS endpoint
The connector is a URL your users’ MCP client calls. Nothing is installed on your network, nothing listens there, and NORG opens no connection towards you.
OAuth 2.1, authorization code with PKCE
Every user authenticates individually. The only code-challenge method advertised or accepted is S256; a request without it is refused.
One API scope
Consent grants mcp:onboarding and nothing else. It is the only scope the discovery documents advertise, the only one the endpoint accepts, and the only one dynamic registration issues.
No identity token
openid is neither advertised nor granted, so the connector never receives an OpenID identity token. It is filtered at two independent layers.
Accounts are created on first use
There are no seats to buy, assign or pre-provision. A user who has never connected before is set up automatically when they first authenticate.
Its own workspace, always
The workspace the connector creates carries a provenance stamp, and the connector only ever operates on a workspace bearing it — never on a workspace created any other way.
Before you start

A client that speaks OAuth 2.1 with PKCE. Claude's connector support does. Any MCP client that cannot do authorization-code + PKCE (S256) cannot use this endpoint at all.

Outbound HTTPS to the NORG API origin. If egress is filtered, allow that one origin. Everything the flow needs — discovery, authorize, token, JWKS and the connector itself — is on it.

Nothing else. No directory integration, no per-user setup, no certificate exchange, no allowlisting on your side of anything NORG runs.

The connector URL

The path is fixed. The host is the NORG production API origin, which is the one shown on the connector’s directory listing or supplied by NORG — confirm it there rather than assuming it, then verify it with the probes below.

What to enter

https://<norg-host>/v2/mcp/express

Both /v2/mcp/express and /v2/mcp/express/ answer directly, so a trailing slash is not a mistake. The version sits in the path so the contract can change without breaking connectors already installed.

# 1. The endpoint refuses an unauthenticated call, and says where to look.
curl -isS https://<norg-host>/v2/mcp/express | head -n 20

# 2. That pointer resolves to the RFC 9728 document describing the endpoint.
curl -sS https://<norg-host>/.well-known/oauth-protected-resource/v2/mcp/express

# 3. Which names the authorization server, described in the next section.
curl -sS https://<norg-host>/.well-known/oauth-authorization-server
What step 1 returns

HTTP/1.1 401 Unauthorized, with a WWW-Authenticate: Bearer header carrying error="invalid_token" and a resource_metadata parameter pointing at the document in step 2. The body is the same error as JSON.

A 403 with insufficient_scope instead means the token was valid but did not carry mcp:onboarding. A 503 means that environment has no authorization server configured — the endpoint fails closed rather than answering unauthenticated calls.

Verifying you have the right host

The document in step 2 contains a resource field. It must equal, character for character, the URL you entered. If it does not, you are not looking at the connector you think you are. The same document lists scopes_supported, which must be exactly ["mcp:onboarding"], and resource_name, which reads NORG Express Onboarding.

How sign-in works

OAuth 2.1 authorization code with PKCE, discovered rather than configured. Two public, unauthenticated documents describe the whole arrangement, and both are cached for an hour.

FieldValueWhy it matters
issuer<origin>NORG’s own API origin — the same origin as the connector URL.
authorization_endpoint<origin>/v2/mcp/authorizeWhere the user’s browser is sent to sign in and consent.
token_endpoint<origin>/v2/mcp/tokenCode-for-token exchange, and refresh.
jwks_uri<origin>/v2/mcp/jwksThe signing keys access tokens are verified against.
registration_endpoint<origin>/v2/mcp/registerDynamic Client Registration. Constrained — see the next section.
scopes_supported["mcp:onboarding"]The whole of it. There is no second scope to ask for.
response_types_supported["code"]Authorization code only. No implicit flow.
grant_types_supported["authorization_code", "refresh_token"]No client-credentials grant: there is no way to call it as a service.
code_challenge_methods_supported["S256"]PKCE is mandatory and "plain" is not offered.
authorization_response_iss_parameter_supportedtrueEvery response carries iss, so a client can detect a mix-up attack.
token_endpoint_auth_methods_supported["none", "client_secret_post", "client_secret_basic"]The secret-bearing methods exist for a pre-registered client. Anything registered dynamically is issued as "none".
client_id_metadata_document_supportedtrue or false, per environmentWhether CIMD is offered here. Read it; do not assume it.
Why every endpoint is on one origin

NORG runs its own authorize, callback and token endpoints in front of its identity provider. That is not decoration. RFC 9207 requires a client to record the issuer it discovered and reject any authorization response whose iss does not match it by exact string comparison. Relaying the client-facing leg is what makes the advertised issuer the one the client is actually answered by.

The practical consequence for you: there is one origin to review, one origin to allow through egress filtering, and no second party for your users' browsers to be handed off to. Point tooling at the discovery documents above — never at the identity provider behind them.

PKCE passes through untouched. The client's code_challenge goes upstream and its code_verifier comes back through the token endpoint, so the relay never holds the verifier and cannot weaken the binding. An authorization request with no challenge, or with a method other than S256, is sent back to the client as error=invalid_request.

What consent actually grants

One API scope, mcp:onboarding. It is the only scope advertised, the only one the endpoint will accept, and the only one dynamic registration will issue — the registration code discards whatever a client asks for and returns that constant.

Alongside it the identity provider attaches its own standard identity scopes so the connector can read the subject, email and name claims it needs to know who is connecting. offline_access is available on request, which is what makes refresh tokens work. openid is filtered out at two independent layers, so no OpenID identity token is ever issued: such a token would carry the identity provider's own iss, which can never equal the issuer NORG advertises, and a conformant client is required to reject it.

One thing that is worth knowing about tokens

Access tokens must carry the connector's client id in their aud claim, and audience verification is on. That claim comes from an audience protocol mapper attached to the mcp:onboarding client scope — it is not an identity provider default. If that mapper is absent, sign-in and consent both succeed and then every call fails authentication, with no other symptom the client can see.

This is NORG-side configuration, not something you set. It is here because it is the one failure that looks like a client bug and is not — see the troubleshooting section.

How a client is registered

There are two ways a client can obtain an identity here, and both end at the same place: a public client using PKCE, with no secret to store or leak.

Route 1 — Dynamic Client Registration (RFC 7591)

A client POSTs to /v2/mcp/register with no pre-shared credential. That is deliberate: it is what lets a connector be added from the URL alone. It is not open registration.

The callback must already be allowlisted. Every entry in the request's redirect_uris must match a configured allowlist by exact string. Not a prefix, not a pattern, not a host — the whole URI. Anything else is refused with 400 invalid_redirect_uri, and the refusal records only the callback's hostname, never the URI itself.

An unconfigured allowlist refuses everyone. Where no allowlist is set the endpoint answers 503 registration_unavailable. It fails closed; there is no "allow all" state.

Registrations are rate-capped. A fixed window, keyed on the source address the server derives itself rather than anything the caller can claim. Only successful registrations consume budget; past the cap the answer is 429 rate_limited. The shipped default is 20 per hour per source, set per environment, and it is a per-instance count rather than a cluster-wide one.

The result is always a public client. The response says token_endpoint_auth_method: "none", grants authorization_code and refresh_token, scopes to mcp:onboarding, and carries no client_secret. A request asking for a confidential client does not get one — those fields are not read at all — and the response is marked no-store.

Route 2 — Client ID Metadata Documents

The mechanism introduced in the 2025-11-25 MCP authorization specification. The client's client_id is an https URL, and the JSON document served at that URL declares its own redirect URIs. Nothing is registered in advance and NORG issues no credential at all.

It is offered per environment. Read client_id_metadata_document_supported in the authorization-server metadata for the environment you are approving. It is off unless explicitly enabled, and where it is false, clients use dynamic registration instead. Do not hard-code an assumption either way.

The URL never reaches the identity provider as a URL: it is mapped to a derived identifier, one per document, so the same document always resolves to the same client. Documents are cached for ten minutes, refusals for one, and the number of distinct new documents accepted is capped both per source address and across all callers.

Fetching the document
https only, and a real path
The client_id must be an https URL with a host and a genuine path — no bare origin, no fragment, no embedded credentials, no "." or ".." segments, and no longer than 2048 characters. Anything else is not treated as a CIMD identifier at all.
The host must be publicly routable
The name is resolved before the connection is made and every resolved address must be public. Private, loopback, link-local, reserved and carrier-grade-NAT ranges are refused, as are numeric and alternate-notation forms of them. The connection is then pinned to the address already checked, so the name cannot be re-pointed in between.
Redirects are not followed
A 3xx response is a refusal, not a hop. The document has to be served directly from the URL that is the client_id.
One three-second deadline
The whole fetch and read runs under a single wall-clock deadline, so a server that drip-feeds bytes cannot hold a request open.
JSON, and bounded
The Content-Type must be exactly application/json, and the body is capped at 65,536 bytes — refused on a declared length that exceeds it, and aborted mid-stream if the bytes do.
Validating the document
It must name itself
The document’s own client_id field has to equal the URL it was served from, exactly. A document that claims to be some other client is refused.
It must be a public client
token_endpoint_auth_method must be "none". If grant_types is present it must include authorization_code. There is no path here that produces a confidential client.
It must list its own redirect URIs
At least one, and the redirect presented at authorize time must match one of them. Matching is exact string equality, with a single exception: two loopback URIs may differ in port, as RFC 8252 requires.
The redirect must be https or loopback
An https URL, or a plain-http loopback URL for a client running on the user’s own machine. Nothing else is accepted, whatever the document says.
The user is told who is asking
The consent screen shows the document’s host and the hosts it redirects to — or, for a loopback-only client, that it is a local application on the user’s own device.
Which one applies to you

Rolling out Claude, or another client whose callback NORG already knows

Dynamic registration. The callback is on the allowlist, the client registers itself once and reuses the client_id it is given. Nothing for you to configure.

Deploying an in-house or third-party MCP client

If the environment advertises CIMD, publish a metadata document at an https URL you control and use that URL as the client_id — no allowlisting request, and no credential to hold. Otherwise, ask NORG to add your callback to the allowlist.

Wanting a confidential client with a secret

Neither route produces one. Both issue public clients using PKCE. The secret-bearing authentication methods in the discovery document exist for a client registered directly with NORG, not for anything self-registered.

Accounts and workspaces

There is no seat management, and nothing to prepare before a user connects. The first authenticated request provisions everything that user needs.

What the first request creates
  • A user record, from the token’s subject and email claims, with the display name taken from the name claim or the email’s local part.
  • A workspace owned by that user, stamped with the connector’s own provenance marker.
  • An owner membership linking the two.
  • A starting token balance, so the first piece of work the user asks for is not refused for lack of quota.
On every later request

The connector holds no session, so the same check runs on every authenticated call. It is idempotent: after the first request it finds what already exists and creates nothing. Concurrent first requests from the same user are serialised, so a burst cannot produce duplicates.

If a NORG account already exists for that email address, the connector adopts it rather than creating a second one — and refuses to do so when the identity provider reports the address as unverified, so an unverified sign-up cannot claim someone else's account.

The workspace it creates carries a provenance marker, and the lookup that finds an existing one keys on that marker. That is what confines the connector to workspaces it created itself: it has no route to a workspace made any other way.

What you do not have to do
  • Buy, assign or reclaim seats — there are none.
  • Synchronise users, groups or roles from your directory. The connector reads no group or role information, and authorisation is the single scope, nothing more.
  • Create anything in advance. A user who has never connected is set up on the request that authenticates them.

What this connector never does

Each of these is a property of how the endpoint is built, and each is checkable — from the discovery documents, from a registration attempt, or by asking NORG for the relevant source.

Seven absences
No client secret is issued to a self-registered client
Both registration routes produce public clients using PKCE. The dynamic-registration response states an authentication method of "none" and carries no secret field, and a metadata document declaring anything other than "none" is refused.
No OpenID identity token is issued
openid is absent from the advertised scopes, stripped from whatever a client requests, and never granted at registration. The connector learns who the user is from the access token’s own claims, not from an identity token.
No credential is written to a log
A refused registration records the callback’s hostname and the client name, never the URI. Token verification logs the outcome and the resolved identifiers, never the token. The relay hands a client secret straight to the identity provider without inspecting or storing it.
No redirect is followed when fetching a client metadata document
A 3xx response is treated as a refusal. Combined with resolving the host and pinning the connection to an address already checked as publicly routable, there is no hop an attacker can insert to reach an internal address.
No caller-supplied address is trusted for rate limiting
The registration cap keys on the source address the server derives itself. A caller cannot rotate a header to mint fresh budget, and an address that cannot be derived is capped rather than exempted.
No unauthenticated request is answered with data
The connector answers 401 and nothing else. The only public documents are the two discovery documents, which exist so a client can find out how to authenticate, and both are excluded from search-engine crawling along with the endpoint itself.
No connection is made towards your network
The connector is an endpoint your users’ client calls. There is no agent, no appliance, no callback into your infrastructure, and no firewall rule to open inbound.

Troubleshooting

The connector tells a client as little as possible about why authentication failed, which is right for security and unhelpful for diagnosis. These are the symptoms that reach an administrator, and what each one actually means.

Symptoms and causes

Sign-in and consent succeed, then every call returns 401 with no detail.

The 401 is deliberately uninformative — the endpoint never explains an authentication failure, so it cannot be used to probe. Almost always this is the token audience: access tokens must carry the connector’s client id in aud, which comes from an audience mapper on the mcp:onboarding client scope and is not an identity-provider default. It is NORG-side configuration. Report the time of the attempt to support, who can confirm it from the server-side authentication failure log.

A call returns 403 with insufficient_scope rather than 401.

The token was valid but did not carry mcp:onboarding. That means the client requested a different scope, or dropped it on a refresh. Check what the client asks for at the authorize step — the required scope is named in both discovery documents.

Registration is refused with 400 invalid_redirect_uri.

The client’s callback is not on the allowlist. Matching is exact string equality, so a different port, a trailing slash, or an extra query parameter is a different URI. Send NORG the exact callback URL to have it added, or — where the environment advertises it — use a Client ID Metadata Document instead, which needs no allowlisting.

Registration is refused with 503 registration_unavailable.

That environment has no redirect-URI allowlist configured at all, so dynamic registration fails closed rather than accepting everyone. Nothing to change on your side; raise it with NORG.

Registration is refused with 429 rate_limited.

The fixed-window cap for that source address has been reached — the shipped default is 20 successful registrations per hour. Wait for the window to roll over. Repeated registration usually means the client is not persisting the client_id it was issued; it should register once and reuse it.

A Client ID Metadata Document client_id is rejected.

Work through the checks in the registration section in order: served via a redirect rather than directly; a Content-Type that is not exactly application/json; a body over 65,536 bytes; a document whose own client_id does not equal its URL; token_endpoint_auth_method not "none"; a host that does not resolve to a publicly routable address; or a presented redirect_uri the document does not list. The refusal names the failing check.

The metadata says client_id_metadata_document_supported is false.

Client ID Metadata Documents are not enabled in that environment, and the authorize endpoint will refuse a URL client_id with a message saying so. Use dynamic registration there. Read the field per environment rather than assuming it — it is published precisely so clients can adapt.

A client complains the issuer does not match, or discovery looks stale.

Both discovery documents are served with a one-hour cache lifetime, so a change reaches clients within the hour. A client holding an older copy should re-fetch. The issuer and every endpoint it advertises are on one origin by construction, so a genuine mismatch means the client is not talking to the origin it discovered.

Talk to us

For a security review, a callback to allowlist, or source to read before you approve this, write to solutions@norg.ai and include:

  • the connector URL you were given, and the client you are rolling out
  • the exact callback URL, if you need it added to the registration allowlist
  • the symptom and the time it happened, for anything in the troubleshooting list

For what the product does once a user has connected, see the AI Retrievability by Norg guide. For the per-CDN technical detail behind how NORG serves content, see the edge routing white paper.