Skip to main content
  1. Articles/

Custom OIDC claims with Argo CD and Audiobookshelf

·
Vegard S. Hagen
Author
Vegard S. Hagen
Pondering post-physicists
Table of Contents

Custom OIDC claims allow more granular control as we can give each client application its own scope with custom claims. In this article we’ll use Authelia backed by LLDAP as an Identity Provider (IdP) to implement custom claims for Argo CD and Audiobookshelf .

Motivation
#

Although not part of the list of standard OIDC claims , the groups claim is often used for user permissions in a client — e.g. if a given user has the groups: [ 'admin' ] claim, it will have admin-privileges for the given client, assuming that’s how it’s configured.

This is all well and good if you only have one application, or if every user is supposed to have the same privileges for every client, though that is rarely the case.

An alternative is to prefix each attribute with the corresponding client — e.g. argocd:admin, audiobookshelf:admin, usw. , though then every client will be able to see the user’s privileges in every other client as well, which is not ideal.

Building on the notion that the groups claim is already non-standard, we can instead introduce our own non-standard claim — so-called custom claims , for each application. Separate scopes for each application enable us to isolate each claim for the intended client only, this way we can request an argocd or audiobookshelf scope instead of the groups scope.

Prerequisites
#

This article assumes a functioning implementation of Authelia using LLDAP as an authentication backend — a somewhat esoteric choice perhaps, though the concepts should hopefully be readily applicable to other OIDC providers and authentication backends. If you’re interested in following along to the letter, I’ve written both an article for getting started with LLDAP , as well as an article for configuring Authelia as an OIDC provider .

Implicitly, this article also assumes a working Kubernetes cluster, though LLDAP, Authelia, and Audiobookshelf can all be run without Kubernetes.

Overview
#

We’ll first configure custom attributes in LLDAP. Following Infrastructure as Code (IaC) principles, we’ll use the community-maintained bootstrapping script to provision users and custom attributes, though you could also ClickOps the configuration.

After bootstrapping the LLDAP instance, we’ll look at how we can configure Authelia to pick up the custom attributes from LLDAP and expose them as claims for clients to consume.

Lastly, we’ll configure Argo CD and Audiobookshelf to make use of our OIDC expansion.

LLDAP
#

To get started with LLDAP in your Kubernetes cluster, you can peruse the following article

LLDAP — Declarative Selfhosted Lightweight Authentication
·
Self-hosting multiple applications often means having to deal with disparate user accounts unless you can integrate them with a common third party. A tried and tested framework for this is the ubiquitous LDAP, or Lightweight Directory Access Protocol , server.

LLDAP v0.6 — released in November 2024, or newer is required for custom attributes. The bootstrapping script got updated in April 2025 — in pull request #1155 , which at the time of writing is not part of a stable release. We therefore have to use a development version to be able to bootstrap values for the custom attributes.

We can carefully craft either a ConfigMap or Secret to create the required JSON files for the bootstrapping process. I prefer to use Secrets for this and encrypt it using sealed-secrets — or something similar, to keep any Personal Identifiable Information (PII) private.

The following Secret will expand into three JSON files:

  1. groups.json (line 8) which will create a regular group we can assign to a user
  2. user-schemas.json (line 12) which is a custom attribute we will later turn into a claim
  3. users.json (line 29) which creates two users and assigns attributes to them
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# lldap/lldap-config.yaml
apiVersion: v1
kind: Secret
metadata:
  name: lldap-config
  namespace: lldap
stringData:
  groups.json: |
    {
      "name": "k8s:cluster_admin"
    }
  user-schemas.json: |
    [
      {
        "name": "argocd",
        "attributeType": "STRING",
        "isEditable": true,
        "isList": true,
        "isVisible": true
      },
      {
        "name": "audiobookshelf",
        "attributeType": "STRING",
        "isEditable": true,
        "isList": true,
        "isVisible": true
      }
    ]
  users.json: |
    {
      "id": "admin",
      "email": "[email protected]",
      "firstName": "Admin",
      "lastName": "Adminsdottir",
      "displayName": "admin",
      "groups": [ "lldap_admin", "k8s:cluster_admin" ],
      "argocd": [ "admin" ]
      "audiobookshelf": [ "admin" ],
    }
    {
      "id": "user",
      "email": "[email protected]",
      "firstName": "User",
      "lastName": "Userson",
      "displayName": "user",
      "argocd": [ "reader" ]
      "audiobookshelf": [ "user" ]
    }

We’ve applied the custom attributes to both the admin user (line 36–38) and the user user (line 46–47). The lldap_admin role is a built-in role in LLDAP. Note also that the group and user configurations can either — curiously enough , be a single JSON file with nested JSON top-level values or several JSON files, not a JSON list as with the user- and group-schema configuration.

The bootstrapping script requires admin-credentials, which — if you followed the LLDAP configuration in my previous article, can be found in the admin-credentials Secret

1
2
3
4
5
6
7
8
9
# lldap/admin-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
  name: admin-credentials
  namespace: lldap
stringData:
  LLDAP_LDAP_USER_DN: "admin"
  LLDAP_LDAP_USER_PASS: "<RANDOM_PASSWORD>"

We also need to tell the bootstrapping script where to find LLDAP, which we can do by providing it with a LLDAP_URL env variable, again assuming you followed by LLDAP guide this can be set to http://lldap:80 as in the Kustomize configMapGenerator below on lines 5–11

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# lldap/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

configMapGenerator:
  - name: bootstrap-env
    namespace: lldap
    literals:
      - TZ="Europe/Oslo"
      - LLDAP_URL="http://lldap:80"
      - DO_CLEANUP="true"

resources:
  - lldap-config.yaml
  - admin-credentials.yaml
  - bootstrap.yaml

For a declarative configuration we’ve also told the script to clean up any discrepancies between the actual and desired configuration by setting DO_CLEANUP=true on line 11.

Next, we stitch everything together using a Kubernetes Job . Here we’ve chosen a fairly new image (line 16) which includes the updated bootstrap.sh script (line 17) which lets us bootstrap custom attributes. Also note the annotations on lines 8 and 9 which allows Argo CD to relaunch the job on every app sync action. For basic config we’re importing the Kustomize generated ConfigMap on line 19, though we have to transform the admin-credentials from env-variables understood by LLDAP to env-variables picked up by bootstrap.sh on line 23 and 26.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# lldap/bootstrap.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: lldap-bootstrap
  namespace: lldap
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: lldap-bootstrap
          image: ghcr.io/lldap/lldap:2025-07-10-alpine-rootless
          command: [ /app/bootstrap.sh ]
          envFrom:
            - configMapRef: { name: bootstrap-env }
          env:
            - name: LLDAP_ADMIN_USERNAME
              valueFrom:
                secretKeyRef: { name: admin-credentials, key: LLDAP_LDAP_USER_DN }
            - name: LLDAP_ADMIN_PASSWORD
              valueFrom:
                secretKeyRef: { name: admin-credentials, key: LLDAP_LDAP_USER_PASS }
          volumeMounts:
            - { name: tmp, mountPath: /tmp }
            - { name: groups, mountPath: /bootstrap/group-configs, readOnly: true }
            - { name: user-schemas, mountPath: /bootstrap/user-schemas, readOnly: true }
            - { name: users, mountPath: /bootstrap/user-configs, readOnly: true }
      volumes:
        - { name: tmp, emptyDir: { } }
        - name: groups
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: groups.json, path: groups.json } ]
        - name: user-schemas
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: user-schemas.json, path: user-schemas.json } ]
        - name: users
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: users.json, path: users.json } ]

Next we volume-mount the different JSON configuration files from the lldap-config Secret on lines 29–31 and 39, 45, and 51. Applying the above manifests and launching the Job should configure your LLDAP deployment with our custom attributes. For more details regarding the LLDAP bootstrapping process, see the documentation .

Authelia
#

Authelia v4.39 released in March 2025 did a major overhaul on the use of ID Tokens and Claims Policies, as well as adding support for Custom Scopes and Claims. I recommend reading James Elliott’s OpenID Connect 1.0 Nuances article for more details on the changes.

I’ve previously written a comprehensive guide to getting started with Authelia, so I’ll assume you’ve either read it or have a similar setup as described there and take some shortcuts.

OpenID Connect with Authelia on Kubernetes
·
Authelia is an open-source authentication and authorisation solution, fulfilling an identity and access management (IAM) role, providing multi-factor authentication (MFA) and single sign-on (SSO) for applications via a web portal.

We first need to tell Authelia to pick up the custom attributes from LLDAP by explicitly requesting them as extra attributes using the following configuration

17
18
19
20
21
22
  authentication_backend:
    ldap:
      attributes:
        extra:
          argocd: { multi_valued: true, value_type: string }
          audiobookshelf: { multi_valued: true, value_type: string }

Next we need to configure claims_policies to expose the extra attributes as claims

24
25
26
27
28
29
30
31
  identity_providers:
    oidc:
      claims_policies:
        argocd_policy:
          custom_claims: { argocd_claim: { attribute: argocd } }
          id_token: [ email, name, preferred_username, argocd_claim ]
        audiobookshelf:
          custom_claims: { audiobookshelf: { attribute: audiobookshelf } }

Notice that we also configure the ID Token to contain the requested claims (line 29) for Argo CD as Argo CD only fetches the groups claim from the UserInfo Endpoint . I’ve opened an issue with the Argo CD project asking them for general support of the UserInfo Endpoint in Issue #23768 .

After exposing the attributes as claims, we need to create custom scopes to allow requesting said claims

32
33
34
35
36
      scopes:
        argocd:
          claims: [ argocd_claim ]
        audiobookshelf:
          claims: [ audiobookshelf_claim ]

Finally, we need to configure the clients claim_policy to one that includes the custom claims (line 42 and 55, argocd_policy and audiobookshelf respectively), as well as allowing the custom scopes that include the claims (line 47 and 61, argocd_scope and audiobookshelf respectively).

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
      clients:
        - client_id: argocd
          client_secret: { path: /secrets/client-argocd/client_secret.txt }
          client_name: Argo CD
          public: false
          claims_policy: argocd_policy
          require_pkce: false
          redirect_uris:
            - https://argocd.example.com/auth/callback
            - https://argocd.example.com/applications
          scopes: [ openid, email, profile, offline_access, argocd_scope ]
          grant_types: [ authorization_code, refresh_token ]
          userinfo_signed_response_alg: none

        - client_id: audiobookshelf
          client_secret: { path: /secrets/client-audiobookshelf/client_secret.txt }
          client_name: Audiobookshelf
          public: false
          claims_policy: audiobookshelf
          require_pkce: true
          redirect_uris:
            - https://abs.example.com/audiobookshelf/auth/openid/callback
            - https://abs.example.com/audiobookshelf/auth/openid/mobile-redirect
            - audiobookshelf://oauth
          scopes: [ openid, email, profile, offline_access, audiobookshelf ]
          grant_types: [ authorization_code, refresh_token ]

Custom description (optional)
#

It’s possible to add a description to our custom Claims and Scopes by overriding server assets . To do this, we can create a ConfigMap containing our overrides by again using Kustomize’s configMapGenerator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# authelia/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: authelia

configMapGenerator:
  - name: consent
    namespace: authelia
    files:
      - ./locales/en/consent.json

which references our own version of the consent.json locale file based on the original , but with our custom Claims

47
48
    "argocd_claim": "Argo CD Membership",
    "audiobookshelf": "Audiobookshelf Membership"

and Scopes added

59
60
    "argocd_scope": "Access Argo CD memberships",
    "audiobookshelf": "Access Audiobookshelf memberships"

Before configuring Authelia to accept our asset override (line 15) and mounting the custom file (lines 4–6)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# authelia/values.yaml
pod:
  extraVolumeMounts:
    - name: consent
      mountPath: /config/assets/locales/en/consent.json
      subPath: consent.json
  extraVolumes:
    - name: consent
      configMap:
        defaultMode: 0644
        name: consent

configMap:
  server:
    asset_path: /config/assets/

Note that we’re only overriding the English locale here as I’m assuming it’s used as a fallback.

Clients
#

With the IdP bit configured, we can now configure clients to make use of the custom claims. Due to what I can only assume is a complicated specification, both the Identity Provider and Client side implementation of the OAuth/OIDC spec vary between providers and clients. I’ve therefore picked Argo CD and Audiobookshelf to display some of the different client implementations.

Argo CD
#

Argo CD has pretty decent OAuth/OIDC support, although it appears to only honour the groups claim from the UserInfo Endpoint, which is why we configured Authelia to allow more claims in the ID Token. Ideally, Argo CD should fetch user claims from the UserInfo Endpoint using an Access Token instead of relying on the claims being present in the ID Token. I’ve opened Issue #23768 for the Argo CD developer to hopefully look into this.

Argo CD also appears to not support the Authorization Code Flow with Proof of Key for Code Exchange (PKCE), throwing the following error if we try

Error occurred: Error getting token Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The request was determined to be using ’token_endpoint_auth_method’ method ’none’, however the OAuth 2.0 client registration does not allow this method.

This suggests that Argo CD only supports PKCE as a so-called public client. We therefore rely on only the client_secret for authentication, skipping the extra security PKCE offers. I’ll be paying attention to Issue #23773 for if/when they decide to implement Authorization Code Flow with both a client secret and PKCE.

What Argo CD gets right, however, is the option to request optional, or non-essential claims like name and email.

The following Argo CD Helm chart values configure Authelia OIDC integration. Note the essential useless —since we’re not using the groups claim, parameters on line 16–18 to fetch the groups claim from Authelia’s UserInfo Endpoint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# argocd/values.yaml
configs:
  cm:
    oidc.config: |
      name: 'Authelia'
      issuer: 'https://authelia.example.com'
      clientID: 'argocd'
      clientSecret: $oidc:authelia.clientSecret
      cliClientID: 'argocd-cli'
      requestedScopes: [ 'openid', 'offline_access' ]
      requestedIDTokenClaims: 
        argocd_claim: { essential: true }
        email: { essential: false }
        name: { essential: false }
        preferred_username: { essential: false }
      enableUserInfoGroups: true
      userInfoPath: /api/oidc/userinfo
      userInfoCacheExpiration: '5m'

  rbac:
    scopes: '[ argocd_claim ]'
    policy.csv: |
      g, admin, role:admin
      g, read, role:readonly

Also note the discrepancy on line 21 where what Argo CD calls a scope is actually a claim, Scopes being a group of Claims . I’ve opened Issue #23772 to further investigate this.

Configuring Argo CD with the above configuration and trying to log in, you should be met with the following consent screen

Deselecting all the checkboxes and inspecting the ID Token, you should see the following claims missing

{
  "email": "[email protected]",
  "name": "Admin",
  "preferred_username": "admin"
}

For more details configuring OIDC in Argo CD, consult the operator manual on RBAC .

Audiobookshelf
#

Audiobookshelf honours the use of the UserInfo Endpoint to request user claims, even custom ones, though we have to configure it using traditional ClickOps following their OIDC guide .

Since I can’t provide you with a piece of declarative configuration, I’ll have to rely on a screenshot on how I’ve configured Authelia as an OIDC provider in Audiobookshelf.

Screenshot of Audiobookshelf OIDC authentication configuration
Screenshot of the Audiobookshelf OIDC configuration (full size)

I’ve opened Issue #4479 with Audiobookshelf asking for the feasibility of adding declarative configuration.

Having configured OIDC, you should now be met with a login screen similar to the one below

Inspecting the openid_id_token cookie after logging in we see that it only contains the openid scope claims, indicating that Audiobookshelf is using the UserInfo Endpoint for additional properties.

Audiobookshelf also supports a custom claim for advanced permissions for non-admin users which we will not look into here. Instead of a list as with the group claim, the advanced permission claim expects a JSON object with the following structure:

{
  "canDownload": false,
  "canUpload": false,
  "canDelete": false,
  "canUpdate": false,
  "canAccessExplicitContent": false,
  "canAccessAllLibraries": false,
  "canAccessAllTags": false,
  "canCreateEReader": false,
  "tagsAreDenylist": false,
  "allowedLibraries": [
    "5406ba8a-16e1-451d-96d7-4931b0a0d966",
    "918fd848-7c1d-4a02-818a-847435a879ca"
  ],
  "allowedTags": [
    "ExampleTag",
    "AnotherTag",
    "ThirdTag"
  ]
}

If an option is missing, it will be treated as false.

Implementing the advanced permission claim is left as an exercise to the reader. Submissions in the comments.

While playing around, I also noticed an issue where Audiobookshelf appears to cache the public JWK for JWT validation where I had to restart Audiobookshelf before it accepted new keys. This could be a user error, though I opened Issue #4478 asking about it.

Summary
#

All resources shown in this article can be found in the resources folder of the article . Where applicable, they are written for use with Argo CD with Kustomize + Helm , but should be easily adaptable for other approaches using e.g. Flux CD .

The resources in this article are not intended to be applied directly, but rather incorporated in an existing configuration. I invite you to explore my homelab repository to view a functioning in situ configuration at the time of writing this article (commit f5841b4 ), especially the k8s/infra/auth directory .

LLDAP
#

# lldap/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

configMapGenerator:
  - name: bootstrap-env
    namespace: lldap
    literals:
      - TZ="Europe/Oslo"
      - LLDAP_URL="http://lldap:80"
      - DO_CLEANUP="true"

resources:
  - lldap-config.yaml
  - admin-credentials.yaml
  - bootstrap.yaml
# lldap/admin-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
  name: admin-credentials
  namespace: lldap
stringData:
  LLDAP_LDAP_USER_DN: "admin"
  LLDAP_LDAP_USER_PASS: "<RANDOM_PASSWORD>"
# lldap/lldap-config.yaml
apiVersion: v1
kind: Secret
metadata:
  name: lldap-config
  namespace: lldap
stringData:
  groups.json: |
    {
      "name": "k8s:cluster_admin"
    }
  user-schemas.json: |
    [
      {
        "name": "argocd",
        "attributeType": "STRING",
        "isEditable": true,
        "isList": true,
        "isVisible": true
      },
      {
        "name": "audiobookshelf",
        "attributeType": "STRING",
        "isEditable": true,
        "isList": true,
        "isVisible": true
      }
    ]
  users.json: |
    {
      "id": "admin",
      "email": "[email protected]",
      "firstName": "Admin",
      "lastName": "Adminsdottir",
      "displayName": "admin",
      "groups": [ "lldap_admin", "k8s:cluster_admin" ],
      "argocd": [ "admin" ]
      "audiobookshelf": [ "admin" ],
    }
    {
      "id": "user",
      "email": "[email protected]",
      "firstName": "User",
      "lastName": "Userson",
      "displayName": "user",
      "argocd": [ "reader" ]
      "audiobookshelf": [ "user" ]
    }
# lldap/bootstrap.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: lldap-bootstrap
  namespace: lldap
  annotations:
    argocd.argoproj.io/hook: PostSync
    argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
spec:
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: lldap-bootstrap
          image: ghcr.io/lldap/lldap:2025-07-10-alpine-rootless
          command: [ /app/bootstrap.sh ]
          envFrom:
            - configMapRef: { name: bootstrap-env }
          env:
            - name: LLDAP_ADMIN_USERNAME
              valueFrom:
                secretKeyRef: { name: admin-credentials, key: LLDAP_LDAP_USER_DN }
            - name: LLDAP_ADMIN_PASSWORD
              valueFrom:
                secretKeyRef: { name: admin-credentials, key: LLDAP_LDAP_USER_PASS }
          volumeMounts:
            - { name: tmp, mountPath: /tmp }
            - { name: groups, mountPath: /bootstrap/group-configs, readOnly: true }
            - { name: user-schemas, mountPath: /bootstrap/user-schemas, readOnly: true }
            - { name: users, mountPath: /bootstrap/user-configs, readOnly: true }
      volumes:
        - { name: tmp, emptyDir: { } }
        - name: groups
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: groups.json, path: groups.json } ]
        - name: user-schemas
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: user-schemas.json, path: user-schemas.json } ]
        - name: users
          projected:
            sources:
              - secret:
                  name: lldap-config
                  items: [ { key: users.json, path: users.json } ]

Authelia
#

# authelia/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: authelia

configMapGenerator:
  - name: consent
    namespace: authelia
    files:
      - ./locales/en/consent.json

resources:
  - client-secret-argocd.yaml
  - client-secret-audiobookshelf.yaml

helmCharts:
  - name: authelia
    repo: https://charts.authelia.com
    releaseName: authelia
    namespace: authelia
    version: 0.10.34
    valuesFile: values.yaml
# authelia/client-secret-argocd.yaml
apiVersion: v1
kind: Secret
metadata:
  name: client-argocd
  namespace: authelia
stringData:
  clientSecret: '<HASHED_ARGO_CD_CLIENT_SECRET>'
# authelia/client-secret-audiobookshelf.yaml
apiVersion: v1
kind: Secret
metadata:
  name: client-audiobookshelf
  namespace: authelia
stringData:
  clientSecret: '<HASHED_AUDIOBOOKSHELF_CLIENT_SECRET>'
# authelia/values.yaml
pod:
  extraVolumeMounts:
    - name: consent
      mountPath: /config/assets/locales/en/consent.json
      subPath: consent.json
  extraVolumes:
    - name: consent
      configMap:
        defaultMode: 0644
        name: consent

configMap:
  server:
    asset_path: /config/assets/

  authentication_backend:
    ldap:
      attributes:
        extra:
          argocd: { multi_valued: true, value_type: string }
          audiobookshelf: { multi_valued: true, value_type: string }

  identity_providers:
    oidc:
      claims_policies:
        argocd_policy:
          custom_claims: { argocd_claim: { attribute: argocd } }
          id_token: [ email, name, preferred_username, argocd_claim ]
        audiobookshelf:
          custom_claims: { audiobookshelf: { attribute: audiobookshelf } }
      scopes:
        argocd:
          claims: [ argocd_claim ]
        audiobookshelf:
          claims: [ audiobookshelf_claim ]
      clients:
        - client_id: argocd
          client_secret: { path: /secrets/client-argocd/client_secret.txt }
          client_name: Argo CD
          public: false
          claims_policy: argocd_policy
          require_pkce: false
          redirect_uris:
            - https://argocd.example.com/auth/callback
            - https://argocd.example.com/applications
          scopes: [ openid, email, profile, offline_access, argocd_scope ]
          grant_types: [ authorization_code, refresh_token ]
          userinfo_signed_response_alg: none

        - client_id: audiobookshelf
          client_secret: { path: /secrets/client-audiobookshelf/client_secret.txt }
          client_name: Audiobookshelf
          public: false
          claims_policy: audiobookshelf
          require_pkce: true
          redirect_uris:
            - https://abs.example.com/audiobookshelf/auth/openid/callback
            - https://abs.example.com/audiobookshelf/auth/openid/mobile-redirect
            - audiobookshelf://oauth
          scopes: [ openid, email, profile, offline_access, audiobookshelf ]
          grant_types: [ authorization_code, refresh_token ]

secret:
  additionalSecrets:
    authelia-postgres-app:
    client-argocd:
      items: [ { key: clientSecret, path: client_secret.txt } ]
    client-audiobookshelf:
      items: [ { key: clientSecret, path: client_secret.txt } ]

Copied from Authelia GitHub repository — which is subject to change without notice, and added description for custom Claims and Scopes.

{
  "Accept this consent request": "Accept this consent request",
  "An error occurred processing the request": "An error occurred processing the request",
  "Claim": "Claim {{name}}",
  "Client ID": "Client ID: {{client_id}}",
  "Code": "Code",
  "Confirm the Code": "Confirm the Code",
  "Consent has been accepted and processed": "Consent has been accepted and processed",
  "Consent has been rejected and processed": "Consent has been rejected and processed",
  "Consent Request": "Consent Request",
  "Debug Information": "Debug Information",
  "Deny this consent request": "Deny this consent request",
  "Description": "Description",
  "Documentation": "Documentation",
  "Error": "Error",
  "Error Code": "Error Code",
  "Failed to submit the user code": "Failed to submit the user code",
  "Hint": "Hint",
  "Remember Consent": "Remember Consent",
  "Scope": "Scope {{name}}",
  "The above application is requesting the following permissions": "The above application is requesting the following permissions",
  "This saves this consent as a pre-configured consent for future use": "This saves this consent as a pre-configured consent for future use",
  "You may close this tab or return home by clicking the home button": "You may close this tab or return home by clicking the home button",
  "You must reauthenticate to be able to give consent": "You must reauthenticate to be able to give consent",
  "claims": {
    "address": "Postal Address",
    "birthdate": "Birthdate",
    "email": "E-mail Address",
    "email_verified": "E-mail Address (Verified)",
    "family_name": "Family Name",
    "gender": "Gender",
    "given_name": "Given Name",
    "groups": "Group Membership",
    "locale": "Locale / Language",
    "middle_name": "Middle Name",
    "name": "Display Name",
    "nickname": "Nickname",
    "phone_number": "Phone Number",
    "phone_number_verified": "Phone Number (Verified)",
    "picture": "Picture URL",
    "preferred_username": "Preferred Username",
    "profile": "Profile URL",
    "sub": "Unique Identifier",
    "updated_at": "Profile Update Time",
    "website": "Website URL",
    "zoneinfo": "Timezone",
    "argocd_claim": "Argo CD Membership",
    "audiobookshelf": "Audiobookshelf Membership"
  },
  "scopes": {
    "address": "Access your address",
    "authelia.bearer.authz": "Access protected resources logged in as you",
    "email": "Access your email addresses",
    "groups": "Access your group memberships",
    "offline_access": "Automatically refresh these permissions without user interaction",
    "openid": "Use OpenID to verify your identity",
    "phone": "Access your phone number",
    "profile": "Access your profile information",
    "argocd_scope": "Access Argo CD memberships",
    "audiobookshelf": "Access Audiobookshelf memberships"
  }
}

Argo CD
#

# argocd/oidc-client-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: oidc
  namespace: argocd
  labels:
    app.kubernetes.io/part-of: argocd
stringData:
  authelia.clientSecret: '<ARGO_CD_CLIENT_SECRET>'
# argocd/values.yaml
configs:
  cm:
    oidc.config: |
      name: 'Authelia'
      issuer: 'https://authelia.example.com'
      clientID: 'argocd'
      clientSecret: $oidc:authelia.clientSecret
      cliClientID: 'argocd-cli'
      requestedScopes: [ 'openid', 'offline_access' ]
      requestedIDTokenClaims: 
        argocd_claim: { essential: true }
        email: { essential: false }
        name: { essential: false }
        preferred_username: { essential: false }
      enableUserInfoGroups: true
      userInfoPath: /api/oidc/userinfo
      userInfoCacheExpiration: '5m'

  rbac:
    scopes: '[ argocd_claim ]'
    policy.csv: |
      g, admin, role:admin
      g, read, role:readonly