Skip to content

KDB.AI OAuth 2.0 with Microsoft Entra ID: End-to-End Setup Guide

This guide walks through setting up KDB.AI OAuth 2.0 authentication with Microsoft Entra ID step by step. It covers how groups and permissions map together, how to create the required Entra ID resources manually, and how to configure KDB.AI to enforce access control using those groups.


Contents


Part 1: Groups and permission structure

This section explains how KDB.AI uses Entra ID groups to enforce access control, and defines the example group, user, and service principal structure used throughout this guide. No resources are created yet.

How KDB.AI uses groups

KDB.AI uses OAuth 2.0 to authenticate identities through Entra ID. When an identity authenticates with Entra, it receives a signed JWT access token. That token contains a groups claim listing the group memberships of the identity. KDB.AI reads this claim on every request to determine what the caller is allowed to do.

Access is controlled through ACL grants - explicit rules that bind a group to a KDB.AI resource (a database or table) and an action level. A group must have a grant before it can access anything; membership alone is not enough.

Permission tiers

KDB.AI supports four permission levels, each of which includes the ones below it:

Level What it allows
system_admin Full access including managing grants
delete Drop tables, databases, and delete data (includes read)
write Insert data, create tables, update data and indexes (includes read)
read Query, search, list tables and indexes

Example structure

The following structure is used throughout this guide. All users, groups and Service Principals live in a single Entra ID directory (one tenant). Part 2 will walk through creating each of these resources in the Azure portal.

Groups (all within a single Entra ID directory):

Note

These are example groups for this guide. In practice, create one group per team + access level, that maps to KDB.AI resources - you can denifne any number of groups.

Group Purpose
kdbai-quants-admin Quants team read+write+delete
kdbai-quants-writer Quants team read+write
kdbai-quants-reader Quants team read-only
kdbai-risk-admin Risk team read+write+delete
kdbai-risk-reader Risk team read-only
kdbai-admin Manages KDB.AI ACL grants

The kdbai-<team>-<access_level> naming convention makes groups self-describing and scoped to KDB.AI. Entra ID has no sub-tenant concept, so a consistent prefix prevents name collisions as the directory grows. Adjust this convention to match your organisation's standards.

Users:

Note

These are example users for this guide. You can add any number of users to KDB.AI groups - each user's access is determined entirely by their group memberships.

User Groups
alice kdbai-quants-writer, kdbai-quants-reader
bob kdbai-quants-reader
charlie kdbai-risk-reader

Service principals (recommended for programmatic access):

Note

These are example service principals for this guide. Service principals allow for programmatic access. You can create and map any number of service principals to their corresponding groups - one per access tier is a common pattern.

Service principal Groups Role
kdbai-sp-admin kdbai-admin Manages KDB.AI ACL grants
kdbai-sp-quants-reader kdbai-quants-reader Programmatic read access to quants data
kdbai-sp-quants-writer kdbai-quants-writer Programmatic read+write access to quants data
kdbai-sp-risk-reader kdbai-risk-reader Programmatic read access to risk data

System admin access is reserved for members of the kdbai-admin group. Only these identities can create or modify ACL grants. How this group is wired up in KDB.AI is covered in Part 3.

How group membership maps to access

The table below is a high-level example of how group membership maps to KDB.AI access. With the example ACL grants set up in Part 3, access resolves as follows:

Group Database Table Permission
kdbai-quants-writer quants prices read+write
kdbai-quants-reader quants prices read
kdbai-risk-reader risk positions read

The effective access for each user is:

User Groups quants.prices quants.orders risk.positions
alice kdbai-quants-writer, kdbai-risk-reader read+write read+write read
bob kdbai-quants-reader read read
charlie kdbai-risk-reader read
kdbai-sp-quants-reader kdbai-quants-reader read read
kdbai-sp-admin kdbai-admin all (system admin) all (system admin) all (system admin)

Part 2: Set up Entra ID resources

This section walks through creating the required Entra ID resources manually through the Azure portal. It covers the app registration that KDB.AI uses to validate tokens, the groups that represent access tiers, and the service principals that will authenticate on behalf of each team.

Assume users already exist in the directory - only group assignment is needed for them. By the end of this section, you will have all the IDs and credentials required for Part 3.

Prerequisites

Access to a Microsoft Entra ID tenant with a Global Administrator or Application Administrator role.

Step 1: Create the app registration

The app registration defines the contract for tokens KDB.AI will accept. Every user or service principal that needs to call KDB.AI will request a token scoped to this app.

  1. In the left sidebar, click App registrations.
  2. Click + New registration.
  3. Fill in:
    • Name: kdbai-entraid-app
    • Supported account types: Accounts in this organizational directory only (Single tenant)
    • Redirect URI: leave blank
  4. Click Register.

When you register an app, Entra creates two objects automatically:

  • App registration - the definition: client ID, redirect URIs, permissions, and token configuration
  • Service principal (under Enterprise applications) - the runtime instance Entra uses for token issuance and sign-in logs

From the app's Overview page, copy and save the Application (client) ID - you will need this throughout the rest of the guide.

Token API version

Entra ID supports two token API versions that affect the iss (issuer) and aud (audience) claims in the access token. These claims must match the KDB.AI environment variables configured in Part 3. Microsoft recommends v2 for all new integrations - it uses the login.microsoftonline.com endpoint for key discovery.

v1 (default) v2 (recommended)
iss https://sts.windows.net/<tenant-id>/ https://login.microsoftonline.com/<tenant-id>/v2.0
aud api://<client-id> <client-id>

To configure v2, update the app manifest:

  1. In the left sidebar, click Manifest.
  2. Set "requestedAccessTokenVersion": 2.
  3. Click Save.

For further details, see the Microsoft app manifest documentation.

Step 2: Create a client secret

  1. In the left sidebar, click Certificates & secrets.
  2. Click + New client secret.
  3. Enter a description (for example, kdbai-entraid-app-secret) and choose an expiry.
  4. Click Add.
  5. Copy the secret Value immediately - it is only shown once and you cannot retrieve after leaving the page.

Step 3: Expose an API scope

Adding a scope registers the app as a named resource in Entra by establishing its Application ID URI (api://<client-id>). This URI is used as the basis for the aud (audience) claim in tokens issued for this app - the exact value depends on the token API version (see Token API version).

KDB.AI validates aud on every request against OAUTH_CLIENT_ID and rejects tokens where it does not match. Without this step, tokens default to aud=https://graph.microsoft.com and all requests will be rejected.

Entra requires you to define at least one scope to complete the registration. We will use the scope name access for this purpose. The scope name must match what clients include in their token requests (for example, scope=api://<client-id>/access). KDB.AI does not validate the scope itself - only the audience.

  1. In the left sidebar, click Expose an API.
  2. Next to Application ID URI click Add - accept the default api://<client-id>.
  3. Click + Add a scope.
  4. Fill in:
    • Scope name: access
    • Who can consent: Admins and users
    • Admin consent display name: Access KDB.AI
    • Admin consent description: Access KDB.AI
  5. Click Add scope.

Tokens requested with scope=api://<client-id>/access will now have aud=api://<client-id> (v1) or aud=<client-id> (v2), depending on the token API version.

Step 4: Configure the groups claim

Skip this step if you are using app roles instead of security groups. See Alternative: Use app roles.

By default, access tokens do not include group memberships. This step enables the groups claim so KDB.AI can read which groups a user belongs to.

  1. In the left sidebar, click Token configuration.
  2. Click + Add groups claim.
  3. Select Security groups.
  4. Under each token type (Access, ID, SAML) leave the default setting (Group ID).
  5. Click Add.

Step 5: Create groups

Entra ID supports two group types: Security groups and Microsoft 365 groups. KDB.AI requires Security groups as they are the only group type that Entra includes in access‑token claims and allows for resource authorization. Microsoft 365 groups are designed for collaboration tools (Teams, SharePoint) and are not suitable here.

Create one Security group per team and access level combination. The kdbai-<team>-<access_level> naming convention used in this guide keeps groups self-describing and clearly scoped to KDB.AI - adjust to match your organisation's standards.

For each group listed below:

  1. Search for Groups in the top search bar and select it.
  2. Click + New group.
  3. Fill in:
    • Group type: Security
    • Group name: (refer to the table below)
    • Group description: optional
    • Members: leave empty for now
  4. Click Create.
  5. After creation, open the group and copy its Object ID - you will need this when configuring KDB.AI grants.

Groups to create:

Group name Purpose
kdbai-quants-admin Quants team read+write+delete
kdbai-quants-writer Quants team read+write
kdbai-quants-reader Quants team read-only
kdbai-risk-admin Risk team read+write+delete
kdbai-risk-reader Risk team read-only
kdbai-admin System administrators (KDB.AI system admin)

Alternative: Use app roles instead of security groups

App roles are an alternative to security groups for controlling access in KDB.AI. You define them directly on the app registration instead of as directory-level objects. Entra ID automatically includes them in the roles claim of every access token, so you do not need to configure token claims separately.

A key advantage over security groups is that ACL grants reference the role value (for example, kdbai-quants-reader) by name, rather than the group Object ID.

To use app roles, set OAUTH_GROUPS_CLAIM=roles in the KDB.AI configuration instead of groups.

Create app roles

Create app roles on the app registration:

  1. Open the kdbai-entraid-app registration.
  2. In the left sidebar, click App roles.
  3. Click Create app role.
  4. Fill in:
    • Display name: a human-readable label (for example, Quants Reader)
    • Allowed member types: Users/Groups
    • Value: the name used in ACL grants (for example, kdbai-quants-reader). KDB.AI reads this value from the token.
    • Description: optional
  5. Click Apply
  6. Repeat these steps for each role.

Roles to create (using the same naming convention as the groups above):

Display name Value
Admin kdbai-admin
Quants Admin kdbai-quants-admin
Quants Writer kdbai-quants-writer
Quants Reader kdbai-quants-reader
Risk Admin kdbai-risk-admin
Risk Reader kdbai-risk-reader

Assign app roles

Assign app roles through Enterprise applications, not the app registration:

  1. Search for Enterprise applications in the top search bar and select it.
  2. Find and open the enterprise application for kdbai-entraid-app.
  3. In the left sidebar, click Users and groups
  4. Click Add user/group.
  5. Select the user or service principal, then select the role to assign.
  6. Click Assign.

An identity can have multiple roles. When KDB.AI evaluates a token, it reads all values in the roles claim, the same way it reads group Object IDs from the groups claim.

Step 6: Create service principals and add to groups

Skip this step if you are using user accounts instead. Refer to Step 7.

A service principal is the recommended identity for programmatic and automated access to KDB.AI. It authenticates using a client ID and secret - no user interaction required.

Info

These service principals are examples. You can create any number of them and assign each one to the appropriate group. A common pattern is to create one service principal per integration or access tier.

For each service principal in the table below:

  1. In the left sidebar, click App registrations.
  2. Click + New registration, enter the name, leave all other fields as default, click Register.
  3. Note the Application (client) ID.
  4. Create a client secret: Certificates & secrets+ New client secretcopy the Value immediately - it is only shown once.

Then add each service principal to its corresponding group. To add to a group: search for Groups → open the group → Members+ Add members:

Service principal Group
kdbai-sp-admin kdbai-admin
kdbai-sp-quants-reader kdbai-quants-reader
kdbai-sp-quants-writer kdbai-quants-writer
kdbai-sp-risk-reader kdbai-risk-reader

Step 7: Assign users to groups

Assuming users already exist in your directory, this step adds them to the appropriate KDB.AI groups. Their access is determined entirely by their group memberships. For programmatic access, use a service principal from Step 6 rather than a user account.

For each user, search for Groups → open the group → Members+ Add members.

The table below lists example users and the groups assigned to them:

User Groups
alice kdbai-quants-writer, kdbai-quants-reader
bob kdbai-quants-reader
charlie kdbai-risk-reader

Step 8: Configure authentication flows for user accounts

Skip this step if you are using service principals only. The client credentials flow used by service principals requires no authentication flow configuration. The Part 3 examples use service principals for simplicity.

Configure whichever user-facing flow applies to your setup.

Password grant:

On the kdbai-entraid-app registration, enable public client flows:

  1. In the left sidebar, click Authentication.
  2. Under Advanced settingsAllow public client flows, set to Yes.
  3. Click Save.

Without this, Entra will reject password grant requests with AADSTS7000218.

PKCE (authorization code + PKCE) and device authorization flow:

Info

These flows require user interaction and are not suitable for programmatic or automated access. A user must go through a login step each time their refresh token expires - typically every 8-24 hours. Token acquisition is the responsibility of the client tooling. For unattended access, use a service principal with the client credentials flow instead.

PKCE is the recommended flow for interactive login from a browser (SPA) or native/desktop app. It does not require Allow public client flows - the platform configuration handles public client behaviour through the redirect URI. Register a redirect URI on the kdbai-entraid-app registration.

The device authorization flow is an alternative for devices without a browser. Unlike PKCE, it does not use a redirect URI and therefore requires Allow public client flows to be enabled (same setting as the password grant above).

  1. In the left sidebar, click Authentication.
  2. Click + Add a platform.
  3. Choose the platform type:
    • Single-page application - for browser-based apps. Entra enforces PKCE automatically.
    • Mobile and desktop applications - for native or desktop apps.
  4. Enter your application's redirect URI (for example, http://localhost:3000 for local development).
  5. Click Configure.

Once a token is obtained by the client application, pass it to KDB.AI as Authorization: Bearer <token>.

Important - token lifecycle for interactive flows

Access tokens issued by Entra ID are short-lived (typically 1 hour). The KDB.AI Python client can automatically refresh the access token using the refresh token supplied at configuration time - no user action is needed during that window. However, refresh tokens themselves expire (typically every 24 hours for interactive flows, or when the user is inactive). Once the refresh token expires, the user must:

  1. Re-authenticate through the client application to obtain a new access token and refresh token.
  2. Update the KDB.AI Python client configuration with the new token.

This is a manual step that must be repeated daily for interactive user flows (PKCE, device authorization). If unattended or automated access is required, use a service principal with the client credentials flow - service principal tokens are non-interactive and do not rely on refresh tokens.

Verify the setup

Before proceeding, confirm in the portal:

  • Entra ID → Groups - all groups exist and have the correct Object IDs.
  • Open a group → Members - the expected users and service principals are listed.
  • Click a user → Groups - group memberships are correct.

Replace the example users and groups with your own.


Part 3: Set up KDB.AI with OAuth

This section configures KDB.AI to use OAuth 2.0 through EntraID, creates the example databases and tables, applies ACL grants, and verifies that access is enforced correctly. All token acquisition uses the service principals created in Part 2 - this is the recommended approach for programmatic access.

User accounts are also supported - Entra ID offers several flows including password grant, PKCE, and device authorization - but these require additional portal configuration (Part 2, Step 8) and are not covered in the verification scenarios below.

Prerequisites

This section uses shell commands that require curl and jq. All examples use Bash syntax and target Linux, macOS, and WSL/Git Bash on Windows. They do not cover native Windows Command Prompt or PowerShell. Adjust the commands as needed for your environment.

Gather required variables from Part 2

The steps in Part 3 - KDB.AI configuration, token requests, and ACL grants - all depend on IDs created in Part 2. Set these shell variables once at the start so you can reuse them without repeatedly looking them up in the portal.

# From Part 2, Step 1 - kdbai-entraid-app → Overview → Directory (tenant) ID
export OAUTH_TENANT_ID=<your-tenant-id>
# From Part 2, Step 1 - kdbai-entraid-app → Overview → Application (client) ID
export OAUTH_CLIENT_ID=<kdbai-entraid-app-client-id>

# From Part 2, Step 5 - Groups → kdbai-admin → Overview → Object ID
export ADMIN_GROUP_ID=<kdbai-admin-object-id>
# From Part 2, Step 6 - kdbai-sp-admin → Overview → Application (client) ID
export SP_ADMIN_CLIENT_ID=<kdbai-sp-admin-client-id>
# From Part 2, Step 6 - kdbai-sp-admin → Certificates & secrets → Client secrets → Value
# Note: the secret value is only shown once at creation time - create a new secret if needed
export SP_ADMIN_SECRET=<kdbai-sp-admin-client-secret>

# From Part 2, Step 5 - Groups → kdbai-quants-reader → Overview → Object ID
# If using app roles (OAUTH_GROUPS_CLAIM=roles), use the role value instead: export QUANTS_READER_GROUP_ID=kdbai-quants-reader
export QUANTS_READER_GROUP_ID=<kdbai-quants-reader-object-id>
# From Part 2, Step 5 - Groups → kdbai-quants-writer → Overview → Object ID
# If using app roles (OAUTH_GROUPS_CLAIM=roles), use the role value instead: export QUANTS_WRITER_GROUP_ID=kdbai-quants-writer
export QUANTS_WRITER_GROUP_ID=<kdbai-quants-writer-object-id>
# From Part 2, Step 5 - Groups → kdbai-risk-reader → Overview → Object ID
# If using app roles (OAUTH_GROUPS_CLAIM=roles), use the role value instead: export RISK_READER_GROUP_ID=kdbai-risk-reader
export RISK_READER_GROUP_ID=<kdbai-risk-reader-object-id>

KDB.AI OAuth configuration

KDB.AI is configured through environment variables. The following settings connect it to the Entra ID app registration created above.

Variable Value Notes
AUTH_TYPE oauth Fixed value
OAUTH_ISSUERS v1: https://sts.windows.net/<directory-tenant-id>/
v2: https://login.microsoftonline.com/<directory-tenant-id>/v2.0
Must exactly match the iss claim in your token - depends on token API version, see Step 1
OAUTH_CLIENT_ID v1: api://<client-id>
v2: <client-id>
Depends on token API version - see Step 1
OAUTH_TENANT_CLAIM tid Entra always includes the tenant/directory ID in the tid JWT claim
OAUTH_GROUPS_CLAIM groups/roles Set to groups if using security groups, or roles if using app roles
ACL_SYSTEM_ADMIN_TENANT <directory-tenant-id> The identifier of the tenant the system admin group belongs to. Entra IDOverviewTenant ID
ACL_SYSTEM_ADMIN_GROUP <kdbai-admin-object-id> The identifier of the group/role, whose members will have system admin privileges in KDB.AI. Security groups: Groupskdbai-adminOverviewObject ID. App roles: use the role value (for example, kdbai-admin)

Start KDB.AI

With the configuration values gathered we are not in a position to start the KDB.AI container.

Copy the Docker compose config from the official KDB.AI OAuth 2.0 configuration docs to docker-compose.yml.

Update the environment variable placeholders with actual values based on the configuration table above.

Create the following directories that will be mounted into the container:

  • kdbai-data - persists the KDB.AI database and table data
  • acl-data - persists ACL grants so they survive container restarts
mkdir -p acl-data kdbai-data

Set the required permissions to allow the container to write to them.

chmod 777 acl-data kdbai-data

Then start the kdbai-db container:

docker compose up -d

Acquire a token for the admin identity

Creating databases and configuring ACL grants, both require an authenticated request from an identity in the ACL_SYSTEM_ADMIN_GROUP group. From Part 2 we have aleady created an admin group (kdbai-admin) and added the admin service principal (kdbai-sp-admin) to that group.

The ACL_SYSTEM_ADMIN_GROUP environment variable references that group ID. When using security groups, KDB.AI reads group Object IDs from the groups claim. When using app roles, it reads role values from the roles claim - set via OAUTH_GROUPS_CLAIM. Without a valid admin token, grant API calls will return an access error.

Client‑credentials tokens require the .default scope

scope=api://${OAUTH_CLIENT_ID}/.default rquires the .default suffix for the client credentials flow. Because there is no user involved, Entra ID cannot prompt for consent to a named scope; instead .default requests a token covering all application permissions pre-granted to the app registration.

export ADMIN_TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/${OAUTH_TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${SP_ADMIN_CLIENT_ID}" \
  -d "client_secret=${SP_ADMIN_SECRET}" \
  -d "scope=api://${OAUTH_CLIENT_ID}/.default" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

Decode the token to verify the key claims match what is expected:

# Security groups
echo $ADMIN_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '{iss, aud, groups}'
# App roles
echo $ADMIN_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '{iss, aud, roles}'
  • iss - the token issuer; must match one of the OAUTH_ISSUERS values. The value depends on the token API version configured in Step 1
    • v1: https://sts.windows.net/<tenant-id>/
    • v2: https://login.microsoftonline.com/<tenant-id>/v2.0
  • aud - the token audience; must match OAUTH_CLIENT_ID. The value depends on the token API version configured in [Step 1] (#step-1-create-the-app-registration)
    • v1: api://<client-id>
    • v2: <client-id>
  • groups (security groups) or roles (app roles) - should contain the Object ID of kdbai-admin or the role value kdbai-admin, respectively

Create databases and tables

Using the admin token, create two databases - quants with a prices table, and risk with a positions table. These mirror the structure from Part 1. Access to each will be controlled by the ACL grants set up in the next section.

Create the quants database

curl -s -X POST http://localhost:8081/api/v2/databases \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"database": "quants"}'

Create the prices table in quants

curl -s -X POST http://localhost:8081/api/v2/databases/quants/tables \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "table": "prices",
    "schema": [
      {"name": "id", "type": "char"},
      {"name": "vectors", "type": "floats"}
    ],
    "indexes": [
      {
        "name": "prices_index",
        "type": "flat",
        "column": "vectors",
        "params": {"dims": 8, "metric": "L2"}
      }
    ]
  }'

Create the risk database

curl -s -X POST http://localhost:8081/api/v2/databases \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{"database": "risk"}'

Create the positions table in risk

curl -s -X POST http://localhost:8081/api/v2/databases/risk/tables \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d '{
    "table": "positions",
    "schema": [
      {"name": "id", "type": "char"},
      {"name": "vectors", "type": "floats"}
    ],
    "indexes": [
      {
        "name": "positions_index",
        "type": "flat",
        "column": "vectors",
        "params": {"dims": 8, "metric": "L2"}
      }
    ]
  }'

Configure ACL grants

ACL grants control access to KDB.AI resources. A grant must explicitly bind a group to a database (or table) and an action level before access is permitted.

Security groups vs. app roles

Throughout this section, group refers to either a security group (referenced by Object ID) or an app role (referenced by role value). If you are using app roles (OAUTH_GROUPS_CLAIM=roles), replace the Object ID placeholders with the corresponding role value - for example, use kdbai-quants-reader instead of $QUANTS_READER_GROUP_ID.

The following grants implement the access model from Part 1. See the KDB.AI REST API reference for the full grant schema.

Note

These commands assume $ADMIN_TOKEN, $QUANTS_READER_GROUP_ID, $QUANTS_WRITER_GROUP_ID, and $RISK_READER_GROUP_ID have been set - refer to the gather required variables section.

Grant kdbai-quants-reader read access to the quants database

curl -s -X POST http://localhost:8081/api/v2/admin/grants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d "[{\"resource\":\"database\",\"tenant\":\"${OAUTH_TENANT_ID}\",\"groups\":[\"${QUANTS_READER_GROUP_ID}\"],\"databaseName\":\"quants\",\"actions\":[\"read\"]}]"

Grant kdbai-quants-writer read+write access to the quants database

curl -s -X POST http://localhost:8081/api/v2/admin/grants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d "[{\"resource\":\"database\",\"tenant\":\"${OAUTH_TENANT_ID}\",\"groups\":[\"${QUANTS_WRITER_GROUP_ID}\"],\"databaseName\":\"quants\",\"actions\":[\"read\",\"write\"]}]"

Grant kdbai-risk-reader read access to the risk database

curl -s -X POST http://localhost:8081/api/v2/admin/grants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -d "[{\"resource\":\"database\",\"tenant\":\"${OAUTH_TENANT_ID}\",\"groups\":[\"${RISK_READER_GROUP_ID}\"],\"databaseName\":\"risk\",\"actions\":[\"read\"]}]"

Note

A database level grant applies to all current and future tables in that database. Use a table-level grant instead if you need to restrict access to specific tables within a database.

Verify access controls

With grants in place, confirm that KDB.AI enforces access correctly. Each scenario acquires a token for a specific service principal and makes requests that should either succeed or fail based on the grants configured above. This validates the full chain: Entra ID group membership or app role assignment → JWT groups/roles claim → KDB.AI ACL grant → allow or deny.

Scenario 1 - kdbai-sp-quants-reader: read access to quants, write denied

kdbai-sp-quants-reader is a member of kdbai-quants-reader, which has a read grant on the quants database. It should be able to query quants.prices but be blocked from inserting, confirming that KDB.AI enforces read-only access even when the identity has a valid token.

# From Part 2, Step 6 - kdbai-sp-quants-reader → Overview → Application (client) ID
export SP_QUANTS_READER_CLIENT_ID=<kdbai-sp-quants-reader-client-id>
# From Part 2, Step 6 - kdbai-sp-quants-reader → Certificates & secrets → Client secrets → Value
export SP_QUANTS_READER_SECRET=<kdbai-sp-quants-reader-client-secret>

Acquire a token for kdbai-sp-quants-reader:

export READER_TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/${OAUTH_TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${SP_QUANTS_READER_CLIENT_ID}" \
  -d "client_secret=${SP_QUANTS_READER_SECRET}" \
  -d "scope=api://${OAUTH_CLIENT_ID}/.default" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

Verify the token contains the kdbai-quants-reader group or role:

# Security groups
echo $READER_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.groups'
# App roles
echo $READER_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.roles'

Read from prices - should succeed and return the table schema:

curl -s http://localhost:8081/api/v2/databases/quants/tables/prices \
  -H "Authorization: Bearer $READER_TOKEN"

Attempt to insert into prices - should return {"error":"access not allowed"}:

curl -s -X POST http://localhost:8081/api/v2/databases/quants/tables/prices/insert \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $READER_TOKEN" \
  -d '{"payload": [{"id": "a", "vectors": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]}]}'

Scenario 2 - kdbai-sp-quants-writer: read+write access to quants

kdbai-sp-quants-writer is a member of kdbai-quants-writer, which has a write grant on the quants database. It should be able to insert data into quants.prices, confirming that KDB.AI grants write access to the correct group.

# From Part 2, Step 6 - kdbai-sp-quants-writer → Overview → Application (client) ID
export SP_QUANTS_WRITER_CLIENT_ID=<kdbai-sp-quants-writer-client-id>
# From Part 2, Step 6 - kdbai-sp-quants-writer → Certificates & secrets → Client secrets → Value
export SP_QUANTS_WRITER_SECRET=<kdbai-sp-quants-writer-client-secret>

Acquire a token for kdbai-sp-quants-writer:

export WRITER_TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/${OAUTH_TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${SP_QUANTS_WRITER_CLIENT_ID}" \
  -d "client_secret=${SP_QUANTS_WRITER_SECRET}" \
  -d "scope=api://${OAUTH_CLIENT_ID}/.default" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

Verify the token contains the kdbai-quants-writer group or role:

# Security groups
echo $WRITER_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.groups'
# App roles
echo $WRITER_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.roles'

Read from prices - should succeed and return the table schema:

curl -s http://localhost:8081/api/v2/databases/quants/tables/prices \
  -H "Authorization: Bearer $WRITER_TOKEN"

Insert into prices - should succeed and return number of rows inserted:

curl -s -X POST http://localhost:8081/api/v2/databases/quants/tables/prices/insert \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WRITER_TOKEN" \
  -d '{"payload": [{"id": "a", "vectors": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]}]}'

Scenario 3 - kdbai-sp-risk-reader: read access to risk, blocked from quants

kdbai-sp-risk-reader is a member of kdbai-risk-reader, which has a read grant on the risk database only. It should be able to query risk.positions but be blocked from quants.prices entirely, confirming that database-level grants enforce strict isolation between teams.

# From Part 2, Step 6 - kdbai-sp-risk-reader → Overview → Application (client) ID
export SP_RISK_READER_CLIENT_ID=<kdbai-sp-risk-reader-client-id>
# From Part 2, Step 6 - kdbai-sp-risk-reader → Certificates & secrets → Client secrets → Value
export SP_RISK_READER_SECRET=<kdbai-sp-risk-reader-client-secret>

Acquire a token for kdbai-sp-risk-reader:

export RISK_TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/${OAUTH_TENANT_ID}/oauth2/v2.0/token" \
  -d "client_id=${SP_RISK_READER_CLIENT_ID}" \
  -d "client_secret=${SP_RISK_READER_SECRET}" \
  -d "scope=api://${OAUTH_CLIENT_ID}/.default" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

Verify the token contains the kdbai-risk-reader group or role:

# Security groups
echo $RISK_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.groups'
# App roles
echo $RISK_TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq '.roles'

Read from risk.positions - should succeed and return the table schema:

curl -s http://localhost:8081/api/v2/databases/risk/tables/positions \
  -H "Authorization: Bearer $RISK_TOKEN"

Read from quants.prices - should return {"error":"access not allowed"}:

curl -s http://localhost:8081/api/v2/databases/quants/tables/prices \
  -H "Authorization: Bearer $RISK_TOKEN"

All three scenarios confirm that access is enforced entirely via group membership (or app role assignment) and ACL grants.


Troubleshooting

Token retrieval

If you experience issues with token requests, you can validate client_credentials and password grants directly with curl. Use the examples below as reference and substitute your own values for the placeholders. For other flows, consult the Microsoft Entra ID documentation.

Client credentials:

curl -X POST \
  "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
  -d "client_id=<service-principal-client-id>" \
  -d "client_secret=<service-principal-client-secret>" \
  -d "scope=api://<kdbai-app-registration-client-id>/.default" \
  -d "grant_type=client_credentials"

Note

client_id is the service principal making the request, while the scope references the KDB.AI app registration client ID.

Password grant:

curl -X POST \
  "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token" \
  -d "client_id=<kdbai-app-registration-client-id>" \
  -d "username=<user@domain.com>" \
  -d "password=<user-password>" \
  -d "scope=api://<kdbai-app-registration-client-id>/access offline_access" \
  -d "grant_type=password"
Issue Fix
AADSTS50034 - user account does not exist in the directory $USERNAME must be the full User Principal Name (UPN), not just the display name. Find the exact value in Entra ID → Users → (user) → Overview → User principal name
AADSTS7000218 - must contain 'client_assertion' or 'client_secret' client_secret is missing or empty - confirm the variable is set: echo $SP_ADMIN_SECRET
AADSTS7000218 - public client not allowed Password grant requires Allow public client flows enabled: App registrations → kdbai-entraid-app → Authentication → Allow public client flows → Yes

For a full reference of AADSTS error codes, read the Microsoft Entra ID error codes documentation.

JWKS endpoint unreachable

KDB.AI fetches signing keys from the issuer URL configured in OAUTH_ISSUERS. That URL must be reachable from the kdbai-db container at runtime. If it is blocked by a firewall or network policy, token validation will fail. The specific URLs depend on the token API version configured in your app registration (see Token API version in Step 1).

v2 (recommended):

Endpoint Purpose
https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration OpenID Connect discovery - KDB.AI fetches this to locate the jwks_uri
https://login.microsoftonline.com/<tenant-id>/discovery/v2.0/keys JWKS public key retrieval - URL returned in the discovery document

v1:

Endpoint Purpose
https://login.microsoftonline.com/<tenant-id>/.well-known/openid-configuration OpenID Connect discovery
https://login.windows.net/common/discovery/keys JWKS public key retrieval - URL returned in the discovery document

KDB.AI access errors

For KDB.AI log message errors, refer to the OAuth 2.0 troubleshooting guide.