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_KEYopenssl 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 inWeb 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 usersList 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 managementLogout

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 accessnats 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?