---
title: "Authentication"
description: "Public API authentication is off by default. Configure auth.authenticators"
canonical_url: "https://docs.superdurable.io/zh-Hans/reference/server/authentication/"
source: "docs/reference/server/authentication.mdx"
locale: "zh-Hans"
---

# Authentication

Public API authentication is off by default. Configure **auth.authenticators**
to require a bearer credential on every **FlowService** and **WorkerTaskService** call.
gRPC health needs no credential. Authenticators are tried in order.

```yaml
auth:
  authenticators:
    - kind: jwt
      issuer: https://issuer.example.com
      audiences: ["dex"]
      adminAudiences: []
      jwksURL: ""
      allowInsecureJWKSURL: false
      caFile: ""
    - kind: staticTokens
      file: /etc/dex-server-zd/tokens.yaml
```

**allowInsecureJWKSURL** allows a plain HTTP **jwksURL** on a trusted network. An empty **jwksURL**
discovers the key set through the issuer's OpenID configuration. **caFile** adds
a PEM bundle to the system roots. **adminAudiences** lists the audiences that
authorize [admin calls](https://docs.superdurable.io/zh-Hans/reference/server/authentication.md#admin-credentials).

The static token file lists SHA-256 hashes, never plaintext tokens:

```yaml
tokens:
  - sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
```

The file must be valid at startup. Changes take effect within a second. A
malformed update keeps the last valid content. An empty or missing file revokes
every token.

The server checks a credential once, when a call starts. An open pull Worker
stream stays open after its token expires or is revoked. The Worker needs a valid
credential when it reconnects.

**api.listen** serves plaintext gRPC, so bearer credentials cross the network in
clear text. Terminate TLS in front of the server, or restrict the network to
trusted callers.

<a id="admin-credentials"></a>

## Admin credentials

Admin calls, such as [describe and step down](https://docs.superdurable.io/zh-Hans/reference/server/high-availability.md#step-down), need their own
credential. Each **jwt** authenticator can list **adminAudiences**: the JWT
audiences that make a token of its issuer an admin credential. The list is empty
by default, so no issuer authorizes admin calls until you configure one.

```yaml
auth:
  authenticators:
    - kind: jwt
      issuer: https://issuer.example.com
      audiences: ["dex"]
      adminAudiences: ["dex-operators"]
```

When **auth.authenticators** is not empty, an admin call needs a JWT that its
issuer's **jwt** authenticator accepts. One of the token's audiences must be in
that authenticator's **adminAudiences**. Static tokens never authorize admin
calls. A missing or invalid credential fails with **UNAUTHENTICATED**. A valid
public API credential without an admin audience fails with
**PERMISSION_DENIED**.

Give **adminAudiences** only to an issuer that decides the audiences of its
tokens itself. Never give them to an issuer that lets callers choose **aud**,
such as Kubernetes projected service account tokens or a CI OIDC provider shared
with other projects. Anyone who can get a token from such an issuer could ask
for an admin audience and step the node down.

**adminAudiences** applies only to **jwt** authenticators. Its entries must not
be empty and must differ from the same authenticator's **audiences**, so a
public API credential never authorizes an admin call.

An issuer that only signs admin credentials can leave **audiences** empty when
**adminAudiences** is set. Its tokens then never authorize **FlowService** or
**WorkerTaskService** calls. If that issuer is the only authenticator, public API
authentication is still on, so every **FlowService** and Worker call is denied.

The API key exchange of **dexcli** sends only the server ID and the API key, so
it cannot ask for an admin audience. An API key works for admin commands only if
the token endpoint itself issues tokens with an admin audience for that key.

When authentication is off, as on a local development server, admin calls need
no credential. Anyone who can reach **api.listen** can then step the node down,
again and again.

server-zd validates **adminAudiences** at startup. It logs each authorized admin
call with the caller's **iss** and **sub** claims. It logs denied admin calls as
warnings, at most once every 10 seconds.
