Authentication

Nexus GSLB uses named user accounts with session-based authentication. Users log in with email and password, receive a session token, and carry that token (via cookie in the browser, or Authorization: Bearer header in the CLI and API clients). Sessions are stored in NATS KV and are cluster-wide — a session created on one node is valid on all nodes.

The legacy per-tenant API key system remains fully supported for programmatic and CI/CD access.


Environment variables

Two environment variables must be set before starting the daemon when auth is enabled:

Variable Purpose
GSLB_API_KEY System-level admin key. Any Bearer request carrying this exact value is granted full access bypassing all RBAC checks. Also serves as a break-glass credential if the database is unavailable. If unset, the daemon runs in dev mode — all requests are treated as system admin with no login required.
GSLB_SECRET_KEY 32-byte hex key used to encrypt TOTP secrets at rest (AES-256-GCM). Required at startup if any user has TOTP enabled. Safe to omit until you enroll the first TOTP device.

Generate values:

openssl rand -hex 32   # for GSLB_API_KEY
openssl rand -hex 32   # for GSLB_SECRET_KEY

Store them in an environment file, not in the config file:

# /etc/gslb/env  (chmod 600, owned by the gslb service user)
GSLB_API_KEY=<32-byte hex>
GSLB_SECRET_KEY=<32-byte hex>

Reference it from the systemd unit:

[Service]
EnvironmentFile=/etc/gslb/env

Configuration

Auth behaviour is controlled by the auth: section in config.yaml. All fields have sensible defaults and can be omitted for a standard deployment.

auth:
  sessionTTL: "24h"       # How long a session lives without activity
  sessionExtend: true      # Reset the TTL on every authenticated request (sliding window)
  bcryptCost: 12           # bcrypt work factor — higher = slower but more resistant to brute force (10–14 is sensible)
  setupEndpoint: true      # Allow POST /api/v1/auth/setup; set false after initial bootstrapping if desired

First-run setup

On a fresh installation with no users in the database, you have two ways to create the first admin account.

Option A — interactive CLI (on the server)

gslbd --config /etc/gslb/config.yaml --create-admin

The daemon prompts for email and password, creates a tenant_admin user in the default tenant, and exits. Start the daemon normally afterwards.

This command exits immediately if any users already exist.

Option B — setup endpoint (infrastructure-as-code, Docker, Ansible)

While the database is empty, POST /api/v1/auth/setup is available without authentication:

curl -s -X POST https://<host>/api/v1/auth/setup \
  -H 'Content-Type: application/json' \
  -d '{"email": "admin@example.com", "password": "your-password"}'

The endpoint responds with the created user object and is permanently disabled (returns 404) once the first user exists. It can also be disabled explicitly in config:

auth:
  setupEndpoint: false

Logging in

Web UI

Navigate to https://<host>/ui/login. Enter email and password. If TOTP is enrolled on the account, a second prompt appears after the password is accepted.

On successful login the browser is redirected to the dashboard. The session is stored in an HttpOnly cookie (gslb_session) — no token is visible or stored in localStorage.

API / CLI

curl -s -X POST https://<host>/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email": "admin@example.com", "password": "your-password"}'

Response:

{
  "token": "4272f488...",
  "userId": "cc543538...",
  "role": "tenant_admin",
  "expiresAt": 1778587389
}

Use the token as a Bearer header for subsequent requests:

curl -H "Authorization: Bearer 4272f488..." https://<host>/api/v1/pools

The same token also works via cookie — the login endpoint sets gslb_session in the response regardless of the client.

With TOTP

curl -s -X POST https://<host>/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email": "admin@example.com", "password": "your-password", "totpCode": "123456"}'

If the account has TOTP enabled and totpCode is omitted, the response is:

{"error": "TOTP code required", "mfaRequired": true}

System API key (programmatic / CI)

The GSLB_API_KEY value can be used directly as a Bearer token and bypasses the session system entirely. This is suitable for CI pipelines and automated tooling:

curl -H "Authorization: Bearer $GSLB_API_KEY" https://<host>/api/v1/pools

Roles and permissions

Each user has one of three built-in roles:

Role What they can do
tenant_admin Full access within their tenant: manage pools, members, services, health checks, geo rules, and other users. Cannot manage tenants or cross-tenant resources.
operator Read and write pools, members, services, health checks, and geo rules. Can read users. Cannot create, modify, or delete users.
viewer Read-only access to all resources within the tenant. No write operations.

The system API key (GSLB_API_KEY) is supra-tenant and bypasses all RBAC checks.


Managing users

List users

curl -H "Authorization: Bearer <token>" https://<host>/api/v1/users

Create a user

Requires tenant_admin role:

curl -s -X POST https://<host>/api/v1/users \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{
    "email": "operator@example.com",
    "displayName": "Jane Doe",
    "password": "secure-password",
    "role": "operator"
  }'

Update a user (role or password)

curl -s -X PUT https://<host>/api/v1/users/<userId> \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{
    "displayName": "Jane Doe",
    "role": "tenant_admin",
    "password": "new-password"
  }'

Omit password to leave it unchanged.

Delete a user

curl -s -X DELETE -H "Authorization: Bearer <token>" \
  https://<host>/api/v1/users/<userId>

Users cannot delete their own account.


Setting up TOTP (MFA)

TOTP requires GSLB_SECRET_KEY to be set (the secret is encrypted at rest).

  1. Initiate enrollment — returns a secret and a QR code PNG:
curl -s -H "Authorization: Bearer <token>" \
  -X POST https://<host>/api/v1/auth/totp/setup \
  -o /tmp/qr.png

Open /tmp/qr.png in any image viewer and scan it with an authenticator app (Google Authenticator, Authy, 1Password, etc.). The response body also contains the secret as base32 text for manual entry.

  1. Verify enrollment — submit a live code from the authenticator app to activate TOTP:
curl -s -X POST https://<host>/api/v1/auth/totp/verify \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <token>' \
  -d '{"code": "123456"}'

Once verified, all future logins for this account require a valid TOTP code.

  1. Disable TOTP — a tenant_admin can clear it by updating the user with PUT /api/v1/users/<id> (this clears the stored secret). Currently requires direct DB access if the admin has also lost their TOTP device (see Troubleshooting).

Session management

Logout

curl -s -X POST -H "Authorization: Bearer <token>" \
  https://<host>/api/v1/auth/logout

This immediately deletes the session from NATS KV — the token is invalid across all cluster nodes within milliseconds.

Session TTL

Sessions expire after auth.sessionTTL (default 24 hours). With auth.sessionExtend: true (default), each authenticated request resets the clock — the session only expires if the user is inactive for the full TTL period.

Revoking a session without the token

Delete the session directly from NATS KV. The KV key is the SHA-256 hex hash of the plaintext token:

# On any node with NATS CLI access
nats kv del NEXUS_SESSIONS <sha256-of-token>

If NATS is unavailable, sessions fall back to a per-node in-memory store and are lost on daemon restart.


Backwards compatibility

Deployments using the per-tenant API key (Authorization: Bearer <tenant-api-key>) continue to work without changes. The auth middleware checks:

  1. Cookie session
  2. Bearer — system API key
  3. Bearer — user session token
  4. Bearer — tenant API key (existing behaviour)

In that order. No config changes are required for existing programmatic clients.


SSO via OIDC

Nexus GSLB supports Single Sign-On through any OpenID Connect (OIDC) provider — Google, Okta, Keycloak, Azure AD, GitHub, and others. When enabled, a Sign in with SSO button appears on the login page. Accounts are provisioned automatically on first login (JIT provisioning).

Password-based login remains available alongside SSO; the two methods coexist.

Configuration

Add an oidc: block under auth: in config.yaml:

auth:
  oidc:
    enabled: true
    discoveryUrl: "https://accounts.google.com"      # provider OIDC discovery endpoint
    clientId: "1234567890-abc.apps.googleusercontent.com"
    clientSecret: "GOCSPX-..."
    redirectUri: "https://nexus.example.com/api/v1/auth/oidc/callback"
    scopes: ["openid", "profile", "email"]           # default; usually no need to change
    roleClaim: "groups"                               # which claim to inspect for role mapping
    roleMapping:
      "nexus-admins":    "tenant_admin"
      "nexus-operators": "operator"
    defaultRole: "viewer"                             # role assigned if no mapping matches
    allowedDomains: ["example.com"]                  # optional — reject emails from other domains
Field Type Required Description
enabled bool Set true to activate OIDC.
discoveryUrl string Yes OIDC issuer URL. The provider's /.well-known/openid-configuration is fetched from this base. Must be https://.
clientId string Yes OAuth2 client ID from the provider.
clientSecret string Yes OAuth2 client secret from the provider.
redirectUri string Yes Full callback URL registered with the provider. Must end with /api/v1/auth/oidc/callback.
scopes []string OAuth2 scopes to request. Default: [openid, profile, email].
roleClaim string Claim in the userinfo response whose value is inspected for role mapping (e.g., groups). Dot-separated paths walk nested objects — Keycloak's default layout works as realm_access.roles with no custom protocol mapper. If omitted, defaultRole is used for all users.
roleMapping map Maps claim values to Nexus role names (tenant_admin, operator, viewer). The claim may be a string or a list of strings; the first matching entry wins. The mapped role is re-applied on every login, so role changes (and revocations) in the provider take effect at the user's next sign-in.
defaultRole string Role assigned when no roleMapping entry matches. Default: viewer.
allowedDomains []string When non-empty, only emails whose domain matches one of these values are permitted. Others receive a 403.

Provider setup

Register a new OAuth2 / OIDC application with your provider and copy the client ID and secret. Set the redirect URI to exactly:

https://<your-nexus-host>/api/v1/auth/oidc/callback

Most providers call this the "Authorized redirect URI" or "Callback URL".

How it works

  1. User clicks Sign in with SSO on the login page.
  2. Browser is redirected to GET /api/v1/auth/oidc/authorize, which generates a single-use CSRF state nonce (10-minute expiry) and redirects to the provider's authorization endpoint. When NATS is configured the nonce is stored in NATS KV, so in a cluster the authorize and callback requests may be served by different nodes (multi-replica Kubernetes Services and round-robin admin hostnames work). Without NATS the nonce is process-local: both requests must reach the same node, and a daemon restart drops in-flight logins.
  3. User authenticates with the provider and approves the application.
  4. Provider redirects back to /api/v1/auth/oidc/callback?code=…&state=….
  5. The daemon validates the state nonce (CSRF protection), exchanges the code for tokens, and fetches the userinfo endpoint.
  6. The email claim is extracted and the domain allowlist is checked (if configured).
  7. The role is resolved: roleClaim value → roleMapping lookup → defaultRole fallback.
  8. The user account is looked up by email. If it doesn't exist, it is created automatically (auth_provider: oidc) with the resolved role.
  9. A session cookie (gslb_session) is set and the browser is redirected to /.

OIDC-provisioned users have no password set and cannot log in via the password form. To grant or change a role for an OIDC user, update the user record via PUT /api/v1/users/{id}.

Common provider configurations

Google Workspace

auth:
  oidc:
    enabled: true
    discoveryUrl: "https://accounts.google.com"
    clientId: "<google-client-id>"
    clientSecret: "<google-client-secret>"
    redirectUri: "https://nexus.example.com/api/v1/auth/oidc/callback"
    allowedDomains: ["example.com"]
    defaultRole: "viewer"

Okta

auth:
  oidc:
    enabled: true
    discoveryUrl: "https://<your-okta-domain>/oauth2/default"
    clientId: "<okta-client-id>"
    clientSecret: "<okta-client-secret>"
    redirectUri: "https://nexus.example.com/api/v1/auth/oidc/callback"
    roleClaim: "groups"
    roleMapping:
      "Nexus Admins":    "tenant_admin"
      "Nexus Operators": "operator"
    defaultRole: "viewer"

Keycloak

auth:
  oidc:
    enabled: true
    discoveryUrl: "https://keycloak.example.com/realms/<realm>"
    clientId: "nexus-gslb"
    clientSecret: "<keycloak-secret>"
    redirectUri: "https://nexus.example.com/api/v1/auth/oidc/callback"
    # Keycloak returns realm roles nested under realm_access.roles in the
    # userinfo response; the dotted path reads them directly — no custom
    # protocol mapper needed. Create matching realm roles and assign them
    # to users/groups in Keycloak.
    roleClaim: "realm_access.roles"
    roleMapping:
      "nexus-admin":    "tenant_admin"
      "nexus-operator": "operator"
    defaultRole: "viewer"

Authentication endpoint reference

Method Path Auth required Description
POST /api/v1/auth/setup No Create first admin user. Disabled after first user exists.
POST /api/v1/auth/login No Authenticate with email + password. Returns token + sets cookie.
POST /api/v1/auth/logout Session Revoke current session.
GET /api/v1/auth/me Session Return current user details and permissions.
POST /api/v1/auth/totp/setup Session Begin TOTP enrollment; returns QR PNG + secret.
POST /api/v1/auth/totp/verify Session Confirm TOTP code to activate MFA.
GET /api/v1/auth/config No Returns {"oidcEnabled": true/false}. Used by the login page to show/hide the SSO button.
GET /api/v1/auth/oidc/authorize No Initiates OIDC flow; redirects to provider.
GET /api/v1/auth/oidc/callback No Handles provider redirect; sets session cookie.
GET /api/v1/users operator+ List users in tenant.
POST /api/v1/users tenant_admin Create user.
GET /api/v1/users/{id} operator+ Get user details.
PUT /api/v1/users/{id} tenant_admin Update display name, role, or password.
DELETE /api/v1/users/{id} tenant_admin Delete user.

Was this article helpful?
© 2026