Skip to content

The v1 docs follow nauthera-server's main branch and its 0.x releases on the way to 1.0, so fields and APIs can still change.

v1

Quick Start: Run Nauthera on kind

Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated

This guide installs the published Helm chart with the server and its operator, then creates an account, registers a client with an OIDCClient manifest and opens the admin dashboard. It needs no source checkout and no registry credentials: the chart and both images are public on GHCR. Run it on a local kind cluster with a throwaway PostgreSQL, or on a cluster you already run.

Prerequisites

  • kubectl and Helm 3 or 4, curl and jq
  • On a laptop: Docker and kind
  • On your own cluster: what that section lists

1. Start the stack

On a laptop, follow On kind. If you already run a cluster, skip to On a cluster you already run.

On kind

Create a cluster that publishes the server's NodePort on https://localhost:8443:

# kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: nauthera
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30443
        hostPort: 8443
        listenAddress: "127.0.0.1"
kind create cluster --config kind.yaml
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager -n cert-manager \
  --create-namespace --set crds.enabled=true --wait
kubectl create namespace nauthera

Then a PostgreSQL whose data lives in an emptyDir, and the Secret the chart reads its connection from (nauthera-pg-owner, key uri):

# postgres.yaml — throwaway: the data is gone with the pod
apiVersion: v1
kind: Secret
metadata:
  name: nauthera-pg-owner
  namespace: nauthera
stringData:
  password: local-only
  uri: postgres://nauthera:local-only@postgres.nauthera.svc:5432/nauthera?sslmode=disable
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
  namespace: nauthera
spec:
  selector:
    matchLabels: { app: postgres }
  template:
    metadata:
      labels: { app: postgres }
    spec:
      containers:
        - name: postgres
          image: postgres:17
          env:
            - { name: POSTGRES_USER, value: nauthera }
            - { name: POSTGRES_DB, value: nauthera }
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef: { name: nauthera-pg-owner, key: password }
          ports:
            - containerPort: 5432
          volumeMounts:
            - { name: data, mountPath: /var/lib/postgresql/data }
      volumes:
        - name: data
          emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: nauthera
spec:
  selector: { app: postgres }
  ports:
    - port: 5432
kubectl apply -f postgres.yaml
kubectl -n nauthera rollout status deploy/postgres

Install the chart with its operator.yaml profile: one replica that migrates the database on start, no Redis, TLS from a CA the chart creates, self-registration on, and the operator talking to the server over mutual TLS. The profile is written for images loaded into kind (pullPolicy: Never), so let the kubelet pull the released ones, which the chart pins by digest:

VERSION=<version>   # the newest is at the top of https://www.nauthera.io/downloads
helm pull oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" --untar
helm install nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera -f nauthera-server/profiles/operator.yaml \
  --set image.pullPolicy=IfNotPresent --set operator.image.pullPolicy=IfNotPresent
 
kubectl -n nauthera rollout status deploy/nauthera-nauthera-server
ServiceURLNotes
nauthera-serverhttps://localhost:8443Public OAuth 2.0 / OIDC surface and sign-in UI
admin dashboardhttps://localhost:8443/admin/For an account that holds an admin role; step 5 grants one

The certificate comes from the chart's own CA, which nothing trusts yet. Save it, so curl and your applications can trust it, and add it to your browser or click through the warning:

kubectl -n nauthera get secret nauthera-nauthera-server-ca \
  -o jsonpath='{.data.tls\.crt}' | base64 -d > nauthera-local-ca.crt

The chart's install notes (helm get notes nauthera -n nauthera) show how to add it to the macOS and Debian trust stores, and how to remove it again.

On a cluster you already run

Nauthera needs a few things beside it that this guide does not install, because most clusters already have their own way of running them:

DependencyWhat Nauthera needsWe suggest
cert-managerIssues the token signing keys, the control plane's mutual TLS and, with an issuer of yours, the public certificatecert-manager with a ClusterIssuer, such as Let's Encrypt
PostgreSQLThe only database; a Secret with its connection URICloudNativePG: its Cluster publishes the <cluster>-app Secret with a uri key
RedisRequired for any issuer that is not localhost; must not evict keys (maxmemory-policy noeviction)The OT-Container-Kit Redis operator, with a password Secret
Ingress or GatewayRoutes the public hostname to the serverThe ingress controller or Gateway API implementation you already run

Setting these up is out of scope here; Installation and Operational requirements say what each must provide in production. With them in place, create the namespace and the two encryption keys the server needs on an issuer that is not localhost:

kubectl create namespace nauthera
kubectl -n nauthera create secret generic nauthera-keys \
  --from-literal=mfa-encryption-key="$(openssl rand -hex 32)" \
  --from-literal=keys-encryption-key="$(openssl rand -hex 32)"

Back up nauthera-keys: without it, the MFA secrets and every organization's sealed keys cannot be read again. Then write the values that name your hostname and the Secrets. They go on top of the chart's production profile:

# my-values.yaml
ingress:
  host: auth.example.com            # becomes the issuer: https://auth.example.com
  className: nginx                  # your ingress class
certManager:
  issuerRef:
    name: letsencrypt               # your ClusterIssuer, for the public certificate
    kind: ClusterIssuer
  grpcTLS:
    enabled: true                   # the operator calls the control plane over mutual TLS
operator:
  enabled: true
  mainOrganizationNamespaces: [nauthera]
config:
  cache:
    address: nauthera-redis:6379    # your Redis Service
    tls: false                      # true when your Redis serves TLS
  server:
    grpc:
      enabled: true
  auth:
    registration:
      enabled: true                 # for this guide's step 3; turn it off afterwards
secrets:
  databaseDsn:
    existingSecret: nauthera-pg-app # CloudNativePG's <cluster>-app Secret
    existingSecretKey: uri
  cachePassword:
    existingSecret: nauthera-redis  # the Secret holding your Redis password
    existingSecretKey: password
  mfaEncryptionKey:
    existingSecret: nauthera-keys
  keysEncryptionKey:
    existingSecret: nauthera-keys

Behind a Gateway instead of an ingress, replace the ingress block:

ingress:
  enabled: false
httpRoute:
  enabled: true
  parentRefs:
    - name: public                  # your Gateway
      namespace: gateway
  hostnames: [auth.example.com]
config:
  issuer:
    url: https://auth.example.com

The production profile runs three replicas, trusts proxies in the private address ranges, and turns on a NetworkPolicy, a ServiceMonitor and alerting rules; the ServiceMonitor and rules need the Prometheus Operator's CRDs, so set metrics.serviceMonitor.enabled and metrics.prometheusRule.enabled to false without them. Install:

VERSION=<version>   # the newest is at the top of https://www.nauthera.io/downloads
helm pull oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" --untar
helm install nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera -f nauthera-server/profiles/production.yaml -f my-values.yaml
 
kubectl -n nauthera rollout status deploy/nauthera-nauthera-server

In the steps below, use your issuer, https://auth.example.com, wherever they say https://localhost:8443, and leave out --cacert nauthera-local-ca.crt: your ClusterIssuer serves a certificate your machine already trusts. For step 5, add -f my-values.yaml to the helm upgrade, or keep --reuse-values.

2. Check the issuer

curl -s --cacert nauthera-local-ca.crt https://localhost:8443/.well-known/openid-configuration \
  | jq '{issuer, grant_types_supported}'

The issuer is https://localhost:8443. The advertised grant types are authorization_code, refresh_token, client_credentials and urn:ietf:params:oauth:grant-type:device_code.

3. Create an account

Open https://localhost:8443/ui/register and register. Registration asks for the built-in proof-of-work captcha, which the browser solves on its own. You then land on the hosted sign-in pages, signed in to the main organization.

4. Register your own client

The operator registers each OIDCClient with the server and writes the generated secret into a Secret beside it. The profile lets manifests in the nauthera namespace configure the main organization:

# my-app.yaml
apiVersion: nauthera.io/v1alpha1
kind: OIDCClient
metadata:
  name: my-app
  namespace: nauthera
spec:
  clientId: my-app
  displayName: My App
  redirectUris:
    - http://localhost:5173/callback
  grantTypes:
    - authorization_code
    - refresh_token
  responseTypes:
    - code
  scopes:
    - openid
    - profile
    - email
    - offline_access
  tokenEndpointAuthMethod: client_secret_basic
  pkcePolicy: required_s256
  allowRefresh: true
  secret:
    generate: true
kubectl apply -f my-app.yaml
kubectl -n nauthera get oidcclients            # wait for Ready
kubectl -n nauthera get secret my-app -o jsonpath='{.data.client-secret}' | base64 -d

The Secret my-app holds client-id, client-secret and issuer; mount it into the application, or read it as above. Re-applying the manifest never rotates the secret. For a public client, set tokenEndpointAuthMethod: none and leave out secret. Then point any OpenID Connect library at the issuer https://localhost:8443, trusting nauthera-local-ca.crt. OIDCClient has every field.

5. Open the admin dashboard

The dashboard admits an account that holds an admin role, and the server grants one to an account that already exists and is named in auth.admin.subjects. Name the account you registered in step 3:

helm upgrade nauthera oci://ghcr.io/nauthera/charts/nauthera-server --version "$VERSION" \
  -n nauthera --reuse-values --set 'config.auth.admin.subjects={you@example.com}'
kubectl -n nauthera rollout status deploy/nauthera-nauthera-server

The new pod grants that account platform-owner, and https://localhost:8443/admin/ opens for it (The first administrator).

6. Clean up

On kind, delete the cluster:

kind delete cluster --name nauthera

On your own cluster, turn self-registration off again (config.auth.registration.enabled: false) or remove the release with helm uninstall nauthera -n nauthera. The resource definitions stay, so the OIDCClients you declared are kept.

Next steps

  • Grafana sign-in: an application signing in through Nauthera, with its OIDCClient and the Secret the operator writes.
  • Installation: the production profile, with Redis, replicas, an ingress and cert-manager issuing from your own CA.
  • Clients: every way to register an application.
  • Organizations: add tenants with their own issuers.