# Set up OIDC with Microsoft Entra ID

> **For AI agents:** the complete documentation index is at [llms.txt](https://questdb.com/docs/llms.txt). Every page is available as markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` request header.

Walkthrough for configuring single sign-on between QuestDB Enterprise and Microsoft Entra ID, covering app registration, group claims, server.conf, and permissions.

This document sets up SSO authentication for the [QuestDB Web Console](/docs/getting-started/web-console/overview/) in
[Microsoft EntraID](https://www.microsoft.com/en-gb/security/business/identity-access/microsoft-entra-id), formerly known as Azure AD.

:::tip

To enlarge the images, click or tap them.

:::

## Set up the client application in Entra ID

First thing first, let's pick a name for the client!

Then head to _Microsoft Entra Admin Center_, and register the application
under _Identity - App registrations - New registration_.

[Screenshot: EntraID image, app registration.](https://questdb.com/docs/images/guides/active-directory-entraid/1_app_registration.webp)

The QuestDB [Web Console](/docs/getting-started/web-console/overview/) is a SPA (Single Page App).

As a result, it cannot store safely a client secret.

Instead, it can use PKCE (Proof Key for Code Exchange) to secure the flow.

When registering the application, select the SPA platform.

We also have to specify the URL of the [Web Console](/docs/getting-started/web-console/overview/) as Redirect URI.

[Screenshot: EntraID image, SPA and redirection URI](https://questdb.com/docs/images/guides/active-directory-entraid/2_spa_redirect_uri.webp)

After clicking _Register_, we have created a client application with the
name _QuestDB_.

Each application is assigned a unique id (known as Client ID in the
OAuth2 - OIDC standard). The client will identify itself with this id
when sending requests to Entra ID.

[Screenshot: EntraID image, application ID](https://questdb.com/docs/images/guides/active-directory-entraid/3_application_id.webp)

We find the platform configurations under _Authentication_. This is the place where
the previously set redirect URI can be viewed and modified. We can also specify
additional redirect URIs, if necessary.

The redirect URIs of the application are automatically eligible for the
_Authorization Code Flow with PKCE_, which is a special version of the OAuth2 standard's
Authorization Code Flow. It is specifically designed for applications where a client
secret (e.g. a password) could not be kept safely. As single page applications run in
the browser, they fall into this category.

The redirect URIs are also added to the _CORS_ (Cross-Origin Resource Sharing) policy
of EntraID. CORS is a mechanism to allow a web page, such as the Web Console, to access
resources from a different domain than the one that served the page. In this context
this means that we let the Web Console to access Entra ID, while its origin is the
HTTP endpoint of QuestDB.

[Screenshot: EntraID image, PKCE and CORS](https://questdb.com/docs/images/guides/active-directory-entraid/4_cors_pkce.webp)

If we scroll down to the bottom of this page, we can also find a section where we
can enable the _Resource Owner Password Credential Flow_.

This OAuth2 flow is legacy, and should be enabled only if there is a requirement
of connecting to QuestDB using SSO (Single Sign-On) via clients not supporting
redirect based web flows.
This could mean a Postgres client without OAuth2 integration, such as _psql_, or
a standalone in-house client application, or could be just a jupyter notebook.

The main issue with this flow is that the client application has to be trusted
with the user's login details. The user's credentials are passed to the
application, in this case to QuestDB, and the client application uses these
credentials to authenticate the user by forwarding them to the identity provider,
in this case to Entra ID.

It is guaranteed that QuestDB does not store the user's credentials in any way.
They are not persisted into the database, not even in encrypted form.
The login details are treated as passthrough information. Only exception is
that server logs can contain the username, logged for audit purposes.

[Screenshot: EntraID image, enable ROPC](https://questdb.com/docs/images/guides/active-directory-entraid/5_ropc.webp)

Our next stop is the _Token configuration_, where the OAuth2/OIDC access and ID
tokens can be customized.

Note that users can be authenticated without customized tokens, but authorization
would prove to be challenging. The user's security groups are not included
in the tokens by default.

QuestDB can be configured to request the user's groups from the UserInfo
endpoint of the OAuth2 server, but Entra ID cannot be configured to provide
this information via the UserInfo endpoint.
Therefore, we choose to customize the tokens, QuestDB will decode and
validate the ID token, and take the group information from there.

QuestDB authorization relies on receiving the group memberships of the user.
Entra ID groups should be mapped to QuestDB groups, and permissions can be
granted to the QuestDB groups. Detailed information about group mappings can
be found under [User permissions](/docs/security/oidc/group-mapping/).

[Screenshot: EntraID image, token customization](https://questdb.com/docs/images/guides/active-directory-entraid/6_token_customization.webp)

The customized tokens contain user information which cannot be accessed
without permission. User information is provided by Microsoft Graph, so
the client application needs specific permissions to access
Microsoft Graph APIs.

These permissions can be configured under _API permissions_. It is important
to note that we will be setting _Delegated_ permissions here, meaning we
are not granting actual permissions to access user data. Instead, each user
logging into QuestDB will have to consent to accessing their user profile.

[Screenshot: EntraID image, API permissions](https://questdb.com/docs/images/guides/active-directory-entraid/7_API_permissions.webp)

By default, the _User.Read_ permission is added to the list, but what we
really need is:
 - openid: to be able to issue ID tokens
 - profile: to access user information
 - offline_access: to be able to issue refresh tokens

By clicking on _Microsoft Graph_ we can select and add these permissions.

[Screenshot: EntraID image, add openid permissions](https://questdb.com/docs/images/guides/active-directory-entraid/8_add_openid_permissions.webp)

The _User.Read_ permission is not needed. It can be removed by clicking
on the `...` at the end of the row, and selecting _Remove permission_ from
the popup menu.

[Screenshot: EntraID image, permissions final](https://questdb.com/docs/images/guides/active-directory-entraid/9_permissions_final.webp)

With this we have finished setting up the QuestDB client application
in Entra ID, and now we can wire QuestDB and Entra ID together by
adding OIDC configuration to QuestDB.

## QuestDB configuration

The below should be set in QuestDB's `server.conf`:

```ini title="server.conf"
# enable OIDC
acl.oidc.enabled=true

# the claim contains the username or user id
acl.oidc.sub.claim=name

# the claim contains the user's group memberships
acl.oidc.groups.claim=groups

# groups are encoded in the token
acl.oidc.groups.encoded.in.token=true

# OIDC configuration endpoint of Entra ID
acl.oidc.configuration.url=https://login.microsoftonline.com/12345678-1234-1234-1234-123456789abc/v2.0/.well-known/openid-configuration

# application ID taken from Entra ID
acl.oidc.client.id=8de84b90-1ea5-4e41-9e84-dba860aa01a6

# redirect URI, QuestDB's HTTP endpoint
acl.oidc.redirect.uri=http://localhost:9000

# OAuth scopes the user has to consent to
acl.oidc.scope=openid profile offline_access

# enable ROPC flow
# optional, required only if ROPC is enabled in Entra ID
acl.oidc.ropc.flow.enabled=true
```

The application ID and the OIDC configuration endpoint's URL can be found
in the Overview of the application in Entra ID.

The application ID is displayed right under the application's name, the
OIDC configuration endpoint is displayed on the panel which opens up when
the _Endpoints_ button is clicked.

[Screenshot: EntraID image, overview](https://questdb.com/docs/images/guides/active-directory-entraid/10_overview.webp)

:::caution

This setup relies on
[`acl.oidc.groups.encoded.in.token`](/docs/configuration/oidc/#acloidcgroupsencodedintoken),
because Entra ID cannot serve the group memberships from its User Info
endpoint. QuestDB then validates the ID token itself and never asks Entra ID
for current user information. Revoking a token, disabling a user, or changing
their Entra ID groups therefore does not immediately change access in QuestDB.
The client must obtain a new ID token for updated claims.

On releases that validate `exp`, a token can still authenticate for the
60-second clock-skew allowance plus up to `acl.oidc.cache.ttl` while an earlier
validation remains cached. Existing PGWire and WebSocket connections are not
closed when the token expires. QuestDB Enterprise 4.0.1 and earlier do not
validate `exp` in this mode, so upgrade before relying on token expiry. Read the
configuration option before going to production, and note that clients must
send the ID token here, not the access token.

:::

## Map groups and grant permissions

Now we can start QuestDB, and login with the built-in admin to create
group mappings.

As mentioned earlier, authorization works by mapping Entra ID groups
to QuestDB groups. When the user logs in, QuestDB decodes Entra ID
group memberships from the token, then finds the QuestDB groups
mapped to them, and the user gets the permissions based on the
mapped groups.

```questdb-sql title="Create a group which is mapped to an Entra ID group"
CREATE GROUP extUsers WITH EXTERNAL ALIAS '87654321-1234-1234-1234-123456789abc';
```
The above command maps the Entra ID group identified by object
id `87654321-1234-1234-1234-123456789abc` to a QuestDB group called `extUsers`.

We should grant the necessary QuestDB endpoint permissions first
to make sure users can access the Web Console, Postgres and ILP
interfaces as required. [Read more about endpoint permissions](/docs/security/rbac/authentication/#endpoint-permissions).

```questdb-sql title="Grant endpoint permissions"
GRANT HTTP, PGWIRE TO extUsers;
```

Now we can grant the rest of the permissions as required. We can
grant access to tables, for example.

```questdb-sql title="Grant database permissions"
GRANT SELECT ON trades, fx_trades TO extUsers;
```

## Confirm group mappings and login

To test, head to the Web Console and login.

If all has been wired up well, then login will succeed, and the user
will have the access granted to them.

<br />

## See also

- [Mapping OIDC groups and permissions](/docs/security/oidc/group-mapping/) -
  map Entra ID group IDs to QuestDB groups
- [OIDC settings reference](/docs/configuration/oidc/) - all `acl.oidc.*`
  settings and token-validation behavior
- [How sign-in works](/docs/security/oidc/how-sign-in-works/) - the browser
  authorization flow used by the Web Console
