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
- Part 2: Set up Entra ID resources
- Part 3: Set up KDB.AI with OAuth
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.
- In the left sidebar, click App registrations.
- Click + New registration.
- Fill in:
- Name:
kdbai-entraid-app - Supported account types: Accounts in this organizational directory only (Single tenant)
- Redirect URI: leave blank
- Name:
- 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:
- In the left sidebar, click Manifest.
- Set
"requestedAccessTokenVersion": 2. - Click Save.
For further details, see the Microsoft app manifest documentation.
Step 2: Create a client secret
- In the left sidebar, click Certificates & secrets.
- Click + New client secret.
- Enter a description (for example,
kdbai-entraid-app-secret) and choose an expiry. - Click Add.
- 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.
- In the left sidebar, click Expose an API.
- Next to Application ID URI click Add - accept the default
api://<client-id>. - Click + Add a scope.
- Fill in:
- Scope name:
access - Who can consent:
Admins and users - Admin consent display name:
Access KDB.AI - Admin consent description:
Access KDB.AI
- Scope name:
- 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.
- In the left sidebar, click Token configuration.
- Click + Add groups claim.
- Select Security groups.
- Under each token type (Access, ID, SAML) leave the default setting (Group ID).
- 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:
- Search for Groups in the top search bar and select it.
- Click + New group.
- Fill in:
- Group type: Security
- Group name: (refer to the table below)
- Group description: optional
- Members: leave empty for now
- Click Create.
- 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:
- Open the
kdbai-entraid-appregistration. - In the left sidebar, click App roles.
- Click Create app role.
- 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
- Display name: a human-readable label (for example,
- Click Apply
- 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:
- Search for Enterprise applications in the top search bar and select it.
- Find and open the enterprise application for
kdbai-entraid-app. - In the left sidebar, click Users and groups
- Click Add user/group.
- Select the user or service principal, then select the role to assign.
- 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:
- In the left sidebar, click App registrations.
- Click + New registration, enter the name, leave all other fields as default, click Register.
- Note the Application (client) ID.
- Create a client secret: Certificates & secrets → + New client secret → copy 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:
- In the left sidebar, click Authentication.
- Under Advanced settings → Allow public client flows, set to Yes.
- 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).
- In the left sidebar, click Authentication.
- Click + Add a platform.
- Choose the platform type:
- Single-page application - for browser-based apps. Entra enforces PKCE automatically.
- Mobile and desktop applications - for native or desktop apps.
- Enter your application's redirect URI (for example,
http://localhost:3000for local development). - 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:
- Re-authenticate through the client application to obtain a new access token and refresh token.
- 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 ID → Overview → Tenant 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: Groups → kdbai-admin → Overview → Object 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 dataacl-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 theOAUTH_ISSUERSvalues. 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
- v1:
aud- the token audience; must matchOAUTH_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>
- v1:
groups(security groups) orroles(app roles) - should contain the Object ID ofkdbai-adminor the role valuekdbai-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.