# How OIDC sign-in works

> **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.

Step-by-step walkthrough of the Authorization Code Flow with PKCE that QuestDB Enterprise and the Web Console use to authenticate a user against an OIDC provider.

The [Web Console](/docs/getting-started/web-console/overview/) signs a user in
with the Authorization Code Flow with PKCE. This page follows that exchange
end to end, from the code verifier the client generates to the permission
check QuestDB runs on the user's group memberships. Read it when you are
integrating your own browser client, or debugging a sign-in that fails partway
through.

## Authentication and authorization flow

The OAuth2/OIDC standard defines different ways of obtaining access and ID
tokens from the OIDC Provider, referred to as the "_flow_".

The goal of this flow is to get the user, who is sitting in front of the Web
Console, authenticated.

Then, it allows QuestDB to determine the user's permissions based on user
information provided by the Identity Providers.

Specifically, the QuestDB [Web Console](/docs/getting-started/web-console/overview/) uses the
`Authorization Code Flow with PKCE` option.

It consists of ten steps...

### 1. Secret generation

First the [Web Console](/docs/getting-started/web-console/overview/) generates a cryptographically strong
random secret called the _code verifier_.

The secret is hashed using the _SHA256 algorithm_. The result is the _code
challenge_.

After PKCE initialization the [Web Console](/docs/getting-started/web-console/overview/) requests an
_authorization code_ from the OIDC Provider.

It calls the Authorization endpoint with a few parameters, including the:

- **Client Id**
- requested scopes (the list of scopes are configurable, default is `openid`
  only)
- code challenge
- algorithm used to generate the code challenge from the code verifier (SHA256)

When the Authorization Server receives the request, it checks if the user has
been authenticated already:

- If the user has a valid session, it can be provided with an authorization code
  straight away, so we jump to step 4.

- If the user does not have a valid session yet, it will be redirected to the
  Identity Provider for authentication.

```bash title="Authorization code request example"
https://oidc.provider:443/as/authorization.oauth2?client_id=questdb&response_type=code&scope=openid&redirect_uri=https%3A%2F%2Fquestdb.example.com%3A9000&code_challenge=IwZ-WuypAY3fMtvismbj1MQUe5CzMgrBa87nYcgFoLQ&code_challenge_method=S256
```

:::note

Some Identity Providers require the `state` parameter in the authorization
request. It is another random value generated by the client, which the OIDC
Provider returns unchanged together with the authorization code. Checking it
protects against CSRF attacks.

If your provider requires it, set
[`acl.oidc.state.required`](/docs/configuration/oidc/#acloidcstaterequired) to
`true`. The [Web Console](/docs/getting-started/web-console/overview/) then
generates the `state` parameter, sends it in the authorization request, and
validates the value returned by the provider.

:::

### 2. Prove identity

Next, the user must prove its identity.

This could be a username with:

- a password,
- an OTP
- facial recognition via a mobile app
- or anything else supported by the Identity Provider.

[Screenshot: Creating profiles](https://questdb.com/docs/images/docs/guide/oidc/oidc-setup-1.webp)

### 3. Scope consent

After successful authentication, the user provides consent for the requested
scopes.

The list of scopes is configurable with
[`acl.oidc.scope`](/docs/configuration/oidc/#acloidcscope).

By default the Web Console requests only the `openid` scope, which is mandatory
for OIDC.

No ID Token is issued without it.

A provider may require `offline_access`, explicit consent, or another policy
before issuing a refresh token. Requesting a refresh-token scope does not
guarantee that the provider returns one.

The OIDC provider can be configured to provide the consent automatically,
without presenting the user with an additional screen in the browser.

[Screenshot: Openid and profile](https://questdb.com/docs/images/docs/guide/oidc/oidc-setup-2.webp)

### 4. Redirection

Consent is granted!

The Authorization Server redirects the user back to the
[Web Console](/docs/getting-started/web-console/overview/) with the _authorization code_:

```bash title="Authorization code response example"
https://questdb.example.com:9000/?code=1L344XEY5XRka1j4ySNa8bVQSLf71as9uGLEuv_A
```

### 5. Credential request

Now, the QuestDB [Web Console](/docs/getting-started/web-console/overview/) requests the ID and access
tokens from the Token endpoint of the OIDC Provider with the authorization code.

It includes the Client ID and the PKCE code verifier together with the
authorization code in the request.

The endpoint then hashes the code verifier using the method specified previously
in step 1.

The result must match the code challenge, also provided in step 1.

The matching code challenge proves that the token is requested by the client
which requested the authorization code, and it was not stolen:

```bash title="Token request example"
curl --request POST https://oidc.provider:443/as/token.oauth2 \
  --header "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=1L344XEY5XRka1j4ySNa8bVQSLf71as9uGLEuv_A" \
  --data-urlencode "client_id=questdb" \
  --data-urlencode "redirect_uri=https://questdb.example.com:9000" \
  --data-urlencode "code_verifier=uGZh4sQffXLgRna7D-jtEAkuXzp7Lm_okZXBljzP38coAD44kEheIaz7Pdh98KxYtYLZHNiQPCczQYeF"
```

### 6. Credentials received

If the PKCE check passes, the Web Console receives the ID and access tokens. The
provider may also return a refresh token when its scopes, consent, and policy
allow one.

When present, the Web Console uses the refresh token to obtain replacement
tokens without asking the user to sign in again. Without one, the user must
authenticate again after the current tokens can no longer be used.

Token lifetimes are configurable in the OIDC Provider.

```json title="Token response example with an optional refresh token"
{
  "access_token": "gslpJtzmmi6RwaPSx0dYGD4tEkom",
  "refresh_token": "FUuAAqMp6LSTKmkUd5uZuodhiE4Kr6M7Eyv.eg83ge",
  "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6I...",
  "token_type": "Bearer",
  "expires_in": 300
}
```

`expires_in` is measured in seconds. The `refresh_token` field is absent when
the provider does not issue one.

### 7. Database access

With the tokens, the Web Console can interact with the database.

A token is in the header of every request sent to QuestDB. Which of the two goes
there depends on where QuestDB reads the group memberships from, see
[Which token to send](/docs/security/oidc/client-discovery/#which-token-to-send).

:::note

Worried about exposing the token? An access token is usually opaque and carries
no user details, though some providers issue a JWT here too. An ID token always
does: it is a JWT whose payload is base64 encoded, not encrypted, so treat it as
you would the user's directory record.

:::

To carry out permission checks, the database has to know more about the user.

For this, QuestDB has a User Info Cache.

Steps 8 and 9 describe the default flow, where QuestDB validates the access
token by asking the OIDC Provider about it. They are skipped when the cache
already holds a valid entry for the token, and they do not happen at all when
[`acl.oidc.groups.encoded.in.token`](/docs/configuration/oidc/#acloidcgroupsencodedintoken)
is `true`, in which case QuestDB validates the ID token itself and never calls
the provider. See [Which token to send](/docs/security/oidc/client-discovery/#which-token-to-send).

```http title="Query request example"
GET /api/v1/sql/execute?query=select%20current_user() HTTP/1.1
Host: questdb.example.com:9000
Authorization: Bearer gslpJtzmmi6RwaPSx0dYGD4tEkom
```

### 8. Find user information

No user information in the cache, or stale information?

QuestDB uses the access token to request user information from the OIDC
Provider's User Info endpoint.

This call also serves as token validation.

If the token is not real or has been expired, the User Info endpoint replies
with an error:

```http title="User info request example"
GET /idp/userinfo.openid HTTP/1.1
Host: oidc.provider:443
Authorization: Bearer gslpJtzmmi6RwaPSx0dYGD4tEkom
```

### 9. Receive user information

If the access token is valid, QuestDB receives the required user information
from the endpoint, then updates its cache.

The cache improves performance, as QuestDB does not have to turn to the OIDC
Provider on every single request.

Do note that cache expiry is configurable:

```json title="User info response example with Active Directory groups"
{
  "sub": "externalUser",
  "name": "External User",
  "groups": [
    "CN=TestGroup1,OU=DC Users,DC=ad,DC=quest,DC=dev",
    "CN=TestGroup2,OU=DC Users,DC=ad,DC=quest,DC=dev"
  ]
}
```

### 10. Permission check

With the help of the user information, QuestDB can carry out
[permission checks](/docs/security/oidc/group-mapping/).

If the permission check is successful, the database will process the request,
and then sends the results back:

```json title="Query response example"
{
  "query": "select current_user()",
  "columns": [
    {
      "name": "current_user",
      "type": "STRING"
    }
  ],
  "dataset": [["External User"]],
  "count": 1,
  "timestamp": -1
}
```

## See also

- [Client discovery and tokens](/docs/security/oidc/client-discovery/) - the
  settings the Web Console and other clients discover
- [Mapping OIDC groups and permissions](/docs/security/oidc/group-mapping/) -
  turn the final group claim into QuestDB permissions
- [OIDC settings reference](/docs/configuration/oidc/) - configure the browser
  flow and provider endpoints
