OAuth and identity providers¶
This page is the step-by-step for wiring KDBL Context Lake (K-Lake) sign-in to your identity provider, so that a cloud AI assistant can reach your on-prem data as the person asking — and see only what that person is allowed to see.
It is written for two readers who are often different people: the identity admin who owns the IdP tenant (steps 1 and the permission tables), and the K-Lake admin who owns the deployment (steps 2–5). Work through it in order; the whole thing is roughly 45 minutes the first time.
For the network path that makes K-Lake reachable in the first place, see Connect your AI. This page assumes you already have a public MCP URL and picks up from there.
Which sign-in mode do you need?¶
K-Lake accepts three kinds of caller. Pick the row that matches your client before you configure anything — they need very different amounts of setup.
| Mode | What the caller presents | Setup | Use it for |
|---|---|---|---|
| Personal access token (PAT) | A K-Lake token in an Authorization header |
None beyond minting the token | Desktop/CLI clients on the same network; evaluation |
| Your IdP directly | An access token your IdP minted with aud = the MCP resource URI |
An app + a way to mint resource-scoped tokens | Custom agents and scripts you build yourself |
| K-Lake's OAuth server, federated to your IdP | Nothing — the client discovers, registers and signs the user in | This page | claude.ai, ChatGPT, Copilot 365, Gemini — every cloud assistant |
The cloud assistants only support the third mode. They cannot be given a static header, and they will not accept an IdP you point them at directly — so if your users are connecting from a hosted assistant, this page is not optional.
Why the cloud clients need it
A remote MCP client expects the authorization server to live at the MCP origin: it reads the server's metadata, registers itself dynamically, and starts an OAuth flow there. K-Lake therefore runs its own OAuth 2.1 authorization server at your MCP hostname, and itself federates the actual sign-in to your IdP. Your IdP stays the source of truth for identity; K-Lake never sees a password.
How federated sign-in works¶
sequenceDiagram
participant AI as AI assistant
participant KL as K-Lake (OAuth server)
participant IdP as Your IdP
AI->>KL: 1. Discover metadata, register itself
AI->>KL: 2. Send the user to sign in
KL->>IdP: 3. Redirect to your IdP
IdP-->>KL: 4. User signs in, returning groups
KL-->>AI: 5. Short-lived K-Lake token (audience-bound)
AI->>KL: 6. Search and read, trimmed to that user
Three consequences worth internalising, because they are what people get wrong:
- You register one app, ever. The assistants register themselves with K-Lake, so there is nothing per-assistant to create in your IdP. Adding ChatGPT alongside Claude needs no IdP change at all.
- The redirect URI belongs to K-Lake, not to the assistant. You will add
https://<your-mcp-host>/oauth/callback— not aclaude.aioropenai.comURL. If a vendor's callback URL ends up in your IdP, something has gone wrong. - Signing in grants no data. Authentication and authorisation are separate gates; step 4 is where access actually happens, and skipping it is the single most common "it connected but finds nothing" report.
One app or two? (this is the confusing part)¶
K-Lake may talk to your directory in two unrelated ways, and they need different permissions. Customers routinely request the heavier permission set for the lighter job.
| Sign-in app (this page) | Directory-sync app (Directory enrichment) | |
|---|---|---|
| What it does | Signs users in and reads their identity + groups from the token | Reads your directory in the background to map IdP groups to on-prem SIDs |
| Acts as | An OAuth client on behalf of the signing-in user | A background service, no user present |
| Entra permission type | Delegated — openid, profile, email |
Application — Group.Read.All, GroupMember.Read.All, User.Read.All |
| Admin consent | Usually not needed | Always required |
| Needs a redirect URI | Yes | No |
| Needed for | Every deployment using cloud assistants | Only when file ACLs are Windows/AD groups |
You may use one app registration for both — it is legal and slightly less to manage. Two separate registrations is the better default: the sign-in app then holds no standing read access to your directory, so its secret leaking costs you far less.
If you only need users to sign in and grant access by IdP group, you need the sign-in app alone. Directory sync matters when K-Lake must reconcile cloud group membership against NTFS/NFSv4 ACLs captured from a file share.
Before you start¶
- Your public MCP URL, e.g.
https://mcp.example.com/mcp— the whole flow is keyed to it. - Permission to create an app registration in your IdP (or someone who has it on the call).
- Cluster-admin access to K-Lake (for the tenant configuration in step 3).
- A decision on which groups should reach K-Lake, and whether your IdP will emit them in the token.
Step 1 — Register K-Lake in your IdP¶
Microsoft Entra ID (validated end-to-end)¶
Create the registration
- Entra admin centre → Identity → Applications → App registrations → New registration.
- Name it something recognisable (
K-Lake MCP sign-in). Supported account types: Accounts in this organizational directory only, unless you genuinely need guests from other tenants. - Redirect URI: platform Web, value
https://<your-mcp-host>/oauth/callback. The Web platform matters — it makes this a confidential client, which is what K-Lake is. - Register, then copy the Application (client) ID and Directory (tenant) ID from the overview.
Add a client secret
- Certificates & secrets → Client secrets → New client secret. Copy the Value (not the ID) immediately — Entra never shows it again. Note the expiry date; 24 months is the maximum, and an expired secret breaks sign-in with no warning.
Permissions — what to add, and what not to
- Under API permissions, add nothing. The scopes K-Lake requests
(
openid,profile,email) are OIDC sign-in scopes Entra honours without any Microsoft Graph permission grant. TheUser.Readdelegated permission Entra adds to new registrations by default is harmless — keep or remove it. - Admin consent is only needed if your tenant has disabled user consent for applications. If it has, a tenant admin clicks Grant admin consent once and no user is ever prompted again.
| Permission | Type | Needed? | Admin consent |
|---|---|---|---|
openid, profile, email |
Delegated (OIDC) | Requested by K-Lake at sign-in | No |
User.Read |
Delegated | Optional (Entra's default) | No |
Group.Read.All, GroupMember.Read.All, User.Read.All |
Application | No — directory sync only | Yes, if used |
Emit groups in the token
- Token configuration → Add groups claim. Choose Security groups, and tick the ID token box. K-Lake reads groups from the id_token.
- If users belong to many groups, prefer Groups assigned to the application
over Security groups. Past roughly 200 groups Entra stops embedding them and
substitutes a link instead — the token arrives with no usable
groupsclaim, and group-based access silently stops working. Assigning only the groups that should reach K-Lake keeps the token small and the intent explicit.
Set the token version
- Manage → Manifest: set
"requestedAccessTokenVersion": 2. Without it Entra issues v1 tokens whose issuer does not match the v2 issuer K-Lake expects, and every sign-in fails validation.
Values for step 2
KDBL_MCP_OAUTH_FED_ISSUER = https://login.microsoftonline.com/<tenant-id>/v2.0
KDBL_MCP_OAUTH_FED_CLIENT_ID = <application (client) id>
KDBL_MCP_OAUTH_FED_SCOPES = openid profile email
KDBL_MCP_OAUTH_FED_GROUPS_CLAIM = groups
Okta¶
- Applications → Create App Integration → OIDC → Web Application.
- Sign-in redirect URI =
https://<your-mcp-host>/oauth/callback. Leave sign-out URIs empty. - Grant type Authorization Code (the default). Copy the client ID and secret.
- Assign the app to the users or groups who should be able to sign in — Okta denies anyone unassigned, which looks like a K-Lake error but is not.
- Groups: either request the built-in
groupsscope, or add a groups claim on the authorization server (Security → API → Authorization Servers → default → Claims → Add claim, include in ID token, value type Groups, filter Matches regex.*).
KDBL_MCP_OAUTH_FED_ISSUER = https://<your-okta-domain>/oauth2/default
KDBL_MCP_OAUTH_FED_SCOPES = openid profile email groups
KDBL_MCP_OAUTH_FED_GROUPS_CLAIM = groups
Using the org authorization server rather than the default one? Drop the
/oauth2/default suffix. The issuer must match the iss in the token exactly.
Auth0¶
- Applications → Create Application → Regular Web Application.
- Allowed Callback URLs =
https://<your-mcp-host>/oauth/callback. - Copy the domain, client ID and client secret.
- Auth0 emits no groups by default and requires namespaced custom claims. Add a Login Action:
exports.onExecutePostLogin = async (event, api) => {
api.idToken.setCustomClaim('https://kdbl/groups', event.authorization?.roles ?? []);
};
KDBL_MCP_OAUTH_FED_ISSUER = https://<your-tenant>.auth0.com/
KDBL_MCP_OAUTH_FED_SCOPES = openid profile email
KDBL_MCP_OAUTH_FED_GROUPS_CLAIM = https://kdbl/groups
Keep the trailing slash on the Auth0 issuer — it is part of the iss value
in the token, and dropping it fails validation.
Google Workspace¶
- Google Cloud console → APIs & Services → Credentials → Create credentials → OAuth client ID → Web application.
- Authorised redirect URI =
https://<your-mcp-host>/oauth/callback. - Configure the OAuth consent screen with user type Internal so only your Workspace users can sign in.
KDBL_MCP_OAUTH_FED_ISSUER = https://accounts.google.com
KDBL_MCP_OAUTH_FED_SCOPES = openid email profile
Google id_tokens carry no group membership. Sign-in works, but group-based access does not — grant access per user, or use Directory enrichment to pull Workspace groups separately.
Any other OIDC provider¶
K-Lake discovers everything it needs from the provider's
/.well-known/openid-configuration, so any standards-compliant provider works —
Keycloak, Ping, ADFS with OIDC enabled. It needs to be a confidential client
supporting the authorization code grant, with a redirect URI of
https://<your-mcp-host>/oauth/callback and a groups claim in the id_token if
you want group-based access.
Step 2 — Configure the K-Lake deployment¶
Two secrets and four settings. The signing key is what K-Lake mints its own tokens with; the federation secret is the IdP app secret from step 1.
# The key K-Lake signs its access tokens with. Generate once, keep it safe.
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out k1.pem
kubectl -n kdbl create secret generic kdbl-mcp-oauth-signing --from-file=k1.pem
# Your IdP app secret.
kubectl -n kdbl create secret generic kdbl-mcp-oauth-federation \
--from-literal=client_secret='<the secret Value from step 1>'
| Setting | Meaning |
|---|---|
KDBL_MCP_ENABLED |
Master switch. The MCP endpoint ships dark. |
KDBL_MCP_RESOURCE_URI |
Your public https://…/mcp URL. It is the OAuth issuer and the token audience, so it must be exactly what clients call. |
KDBL_MCP_OAUTH_FED_ISSUER |
The IdP issuer from step 1. |
KDBL_MCP_OAUTH_FED_CLIENT_ID |
The IdP application/client id. |
KDBL_MCP_OAUTH_FED_SCOPES |
Space-separated, e.g. openid profile email. |
KDBL_MCP_OAUTH_FED_GROUPS_CLAIM |
Name of the groups claim in the upstream id_token. K-Lake re-stamps it onto its own token as groups, so your tenant's groups_claim stays groups whatever your IdP calls it. |
Set the non-secret values in the kdbl-api-config ConfigMap, then roll the API:
kubectl -n kdbl rollout restart deployment/kdbl-api
kubectl -n kdbl rollout status deployment/kdbl-api
The OAuth server ships dark without its key
If kdbl-mcp-oauth-signing is absent, K-Lake runs the MCP endpoint but
publishes no authorization server — clients get a 401 with nowhere to go,
and discovery returns nothing. Create the secret before rolling the API, and
confirm /.well-known/oauth-authorization-server answers afterwards (step 5).
Step 3 — Configure the tenant¶
The tenant tells K-Lake which tokens to trust. With the federated model, both the issuer and the audience point at K-Lake's own hostname — your IdP sits upstream of it, and is not named here.
| Key | Value |
|---|---|
issuer |
https://<your-mcp-host> — origin only, no /mcp |
mcp_audience |
https://<your-mcp-host>/mcp |
groups_claim |
groups |
jit_provision |
true, so a first-time signer gets a user record |
PATCH /api/tenants/:slug replaces oidc_config wholesale — send the
complete block, including any graph or ldap sub-blocks the tenant
already has, or you will erase them.
curl -X PATCH "$KDBL_URL/api/tenants/<tenant-slug>" \
-H "Authorization: Bearer $KDBL_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"oidc_config": {
"issuer": "https://mcp.example.com",
"audience": "https://mcp.example.com/mcp",
"mcp_audience": "https://mcp.example.com/mcp",
"groups_claim": "groups"
},
"jit_provision": true
}'
At tenant-create time the whole block goes in as JSON:
kdbl-control --postgres-url "$KDBL_POSTGRES_URL" \
tenant create --slug '<tenant-slug>' --name '<Name>' \
--oidc-config-json '{"issuer":"https://mcp.example.com","mcp_audience":"https://mcp.example.com/mcp","groups_claim":"groups"}'
To read back what a tenant currently has:
The console does not edit tenant OIDC settings today — tenant creation takes a slug and a name, and the tenant page shows OIDC and JIT as read-only badges. Use the API or CLI above to set them, then use the console to confirm both badges are lit.
What the console does own is Directory enrichment configuration on the same tenant page, the access grants in step 4, and the signed-in sessions in step 5.
One MCP tenant per deployment
The issuer is derived from the deployment's resource URI, so exactly one tenant per K-Lake deployment can be the MCP tenant. Separate tenants need separate hostnames.
Step 4 — Grant access to content¶
Signing in grants nothing. A freshly federated user authenticates successfully and sees an empty result set until a grant exists. Grant to groups so membership drives access and survives people joining and leaving.
kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_ADMIN_TOKEN" \
acl grant '<source-id>' 'group:<idp-group-id>' viewer
kdbl-control --api-url "$KDBL_URL" --api-token "$KDBL_ADMIN_TOKEN" \
acl list '<source-id>'
Roles are viewer, editor, owner. Granting is an upsert, so re-running
with a different role changes it.
Open the source, go to its Security tab, and use the Access grants card: enter the principal, choose a role, and grant. The card lists current grants and revokes them individually.

The group id is whatever your IdP puts in the token — an object id in Entra, a
group name in Okta. Whatever appears in the groups claim is what to grant to.
Per-file trimming still applies on top of the grant, so a user with a viewer
role on a source still only sees the files the file system's own ACLs allow
them: see Per-file security trimming.
Step 5 — Verify¶
Run these from anywhere on the internet — they need no credentials:
HOST=https://mcp.example.com
# The authorization server exists, and advertises itself (not your IdP)
curl -s $HOST/.well-known/oauth-authorization-server | jq -r .issuer
# → https://mcp.example.com
# The resource metadata points at that issuer
curl -s $HOST/.well-known/oauth-protected-resource/mcp | jq
# Unauthenticated POST is challenged, not silently accepted
curl -si -X POST $HOST/mcp | head -2
# → 401 with a WWW-Authenticate header
Then the authenticated end-to-end check:
Finally connect an assistant (MCP clients) and watch the sign-in land in the audit trail:
A successful federated sign-in also shows up in the console under MCP → Tokens, as a login session naming the user and the assistant that registered itself. This is the fastest confirmation that federation is working end to end — and where you revoke a session later.

Revoking a session stops future refreshes; an access token already issued stays valid until it expires, which is at most an hour.
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
| Client reports it cannot connect; no sign-in prompt appears | No authorization server published — signing key secret missing | Create kdbl-mcp-oauth-signing, roll the API, re-check /.well-known/oauth-authorization-server |
| Sign-in completes, then the client 401s | Token audience does not match | mcp_audience must equal the resource URI, exactly, including scheme and path |
| Every sign-in fails validation (Entra) | v1 tokens — wrong issuer | Set "requestedAccessTokenVersion": 2 in the app manifest |
| Sign-in fails on Auth0 | Issuer trailing slash dropped | Keep https://tenant.auth0.com/ verbatim |
| Okta users get access denied at the IdP | App not assigned to them | Assign the app to the user or group in Okta |
| Connects and lists tools, but every search is empty | No access grant | Step 4 — grant the user's group a role on the source |
| Group-based access works for some users, not others | Entra group overage — token dropped the claim | Switch the groups claim to Groups assigned to the application, or use directory sync |
| Worked yesterday, fails today, nothing changed | IdP client secret expired | Rotate it in the IdP and update kdbl-mcp-oauth-federation |
Keeping it secure¶
- Rotate the IdP secret on a calendar, before expiry. Update the
kdbl-mcp-oauth-federationsecret and roll the API; sessions already issued keep working. - Protect the signing key. Anyone holding it can mint K-Lake tokens. Replacing it invalidates every token already issued — which is also the fastest way to cut all MCP access at once.
- Revoke by group. Removing someone from the IdP group removes their access at their next sign-in; revoking the grant removes it for everyone in that group immediately.
- Revoke one person now. MCP → Tokens → OAuth login sessions → Revoke ends that session's ability to refresh. Combine it with removing the group membership, or they simply sign in again.
- Watch the audit trail. Every MCP call is recorded with the principal, tool and sources touched — see MCP server for the schema and Monitoring and access control for the console view.
See also¶
- Connect your AI — the network path and the five-step overview
- MCP clients — per-client setup for Claude, ChatGPT, Copilot, Gemini
- Directory enrichment — the other app registration, and group-to-SID mapping
- Per-file security trimming — what limits results after a grant
- Enablement runbook — the whole deployment in order