Skip to content

Authentication ​

OpenViking Server supports multiple built-in authentication modes with role-based access control. The mode is auto-detected if not explicitly configured. In addition, custom authentication plugins can be registered to support arbitrary identity sources.

API Key mode is recommended for most deployments; it is auto-selected when no mode is specified and a nonempty root key is configured. Use API Key mode when OpenViking should manage credentials, OIDC or LDAP for an existing identity service, and Trusted mode behind a gateway that validates callers.

Authentication Modes Overview ​

Modeserver.auth_modeIdentity SourceTypical Use
API Key Modeapi_keyAPI key. Data ownership is resolved from the user/admin key.Standard multi-tenant deployment
OIDC ModeoidcJWT tokens from external identity providersEnterprise SSO integration with Okta/Auth0/Keycloak
LDAP ModeldapEnterprise LDAP/AD directory servicesIntegrate with existing enterprise user directory
Trusted ModetrustedX-OpenViking-Account / X-OpenViking-User, plus root_api_key on every deployment. Role is looked up from APIKeyManager if the user exists.Behind a trusted gateway or internal network boundary
Dev ModedevNo authentication, always ROOTLocal development only

If auth_mode is not explicitly configured:

  • If root_api_key is set (non-empty): auto-selects api_key mode
  • If root_api_key is not set: auto-selects dev mode

Note: Setting root_api_key to an empty string "" is invalid. Either set a non-empty value or remove the setting entirely.

API Key mode uses a root key for account administration and user/admin keys for tenant data access.

Configuration ​

Configure the authentication mode in the server section of ov.conf:

json
{
  "server": {
    "auth_mode": "api_key",
    "root_api_key": "your-secret-root-key"
  }
}

Start the Server ​

bash
openviking-server

Two-Layer API Key System ​

Key TypeCreated ByRolePurpose
Root KeyServer config (root_api_key)ROOTAccount management + selected system/monitoring operations
User KeyAdmin APIADMIN or USERPer-account data access; ADMIN can also manage users in its account

User keys are generated by the Admin API. By default they use random secret material; Admin API callers may also provide a seed to derive a predictable key secret from sha256(user_id + "\0" + seed). The server still verifies the resulting key against the stored key or key hash.

Managing Accounts and Users ​

Use the root key to create accounts (workspaces) and users via the Admin API:

bash
# Create account with first admin
curl -X POST http://localhost:1933/api/v1/admin/accounts \
  -H "X-API-Key: your-secret-root-key" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "acme", "admin_user_id": "alice"}'
# Returns: {"result": {"account_id": "acme", "admin_user_id": "alice", "user_key": "..."}}

# Register a regular user (as ROOT or ADMIN)
curl -X POST http://localhost:1933/api/v1/admin/accounts/acme/users \
  -H "X-API-Key: your-secret-root-key" \
  -H "Content-Type: application/json" \
  -d '{"user_id": "bob", "role": "user"}'
# Returns: {"result": {"account_id": "acme", "user_id": "bob", "user_key": "..."}}

ACL groups are the exception: groups and memberships are maintained through the Admin API, and members must be registered users in the current account. Clients cannot assert groups through headers or token claims. After authentication, the server resolves the group registry into RequestContext.group_ids for that request.

For gateway deployments, see Trusted Mode for identity headers and Admin API access.

Client Usage ​

OpenViking accepts API keys via two headers:

X-API-Key header

bash
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
  -H "X-API-Key: <user-key>"

Authorization: Bearer header

bash
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
  -H "Authorization: Bearer <user-key>"

Python SDK (HTTP)

python
import openviking_sdk as ov

client = ov.SyncHTTPClient(
    url="http://localhost:1933",
    api_key="<user-key>",
)

CLI (via ovcli.conf)

json
{
  "url": "http://localhost:1933",
  "api_key": "<user-key>"
}

When you use a user key or admin key, the server derives account and user from the key. Do not send X-OpenViking-Account / X-OpenViking-User in api_key mode; those identity headers are accepted only in trusted mode.

CLI request

bash
ov ls viking://

Using --sudo with Root API Key ​

The CLI supports configuring both api_key (for regular user operations) and root_api_key (for admin operations) in ovcli.conf:

json
{
  "url": "http://localhost:1933",
  "api_key": "<user-key>",
  "root_api_key": "<root-key>"
}

When you need to perform admin commands (admin, system, reindex), use the --sudo flag to elevate privileges:

bash
# List all accounts (requires root privileges)
ov --sudo admin list-accounts

# Reindex content
ov --sudo reindex viking://

# System commands
ov --sudo system status

The --sudo flag:

  • Only works with management/system commands: admin, system, reindex
  • Will error if used with non-admin commands
  • Will error if root_api_key is not configured in ovcli.conf
  • Uses root_api_key instead of api_key for the request

Tenant Data Access ​

Tenant-scoped data APIs (for example ls, find, resources, and sessions) must use a key that is bound to an account/user in api_key mode. That can be a USER key or an ADMIN key; an ADMIN key accesses data as its own user and cannot switch identity with X-OpenViking-Account / X-OpenViking-User.

ACL checks also use account groups resolved by the server. Membership changes take effect on the next request without issuing a new API key or modifying resource ACLs.

A ROOT key is not bound to a tenant user, so it cannot access tenant-scoped data APIs in api_key mode. If a deployment needs an upstream gateway to assert account / user, use trusted mode instead of passing identity headers with a root key.

OIDC Authentication ​

OIDC (OpenID Connect) authentication allows using JWT tokens from external identity providers (Okta, Auth0, Keycloak, Azure AD, etc.) for authentication.

Install Dependencies ​

OIDC and LDAP authentication require optional dependencies. You can install them as:

bash
# Install auth features only
uv pip install "openviking[auth]"

# Or install with all features (including bot)
uv pip install "openviking[bot]"

Configure OIDC ​

Configure OIDC in ov.conf:

json
{
  "server": {
    "auth_mode": "oidc",
    "oidc": {
      "issuer": "https://your-oidc-provider.com/",
      "client_id": "your-client-id",
      "audience": "openviking",
      "jwks_uri": "https://your-oidc-provider.com/.well-known/jwks.json",
      "identity": {
        "account_id": {
          "mode": "organization",
          "source": "claim",
          "claim": "tenant_id",
          "prefix": "org-",
          "fallback": "default-org"
        },
        "user_id": {
          "source": "claim",
          "claim": "sub",
          "prefix": "user-",
          "normalize": "lowercase"
        },
        "role": {
          "source": "claim",
          "claim": "openviking_role",
          "mapping": {
            "admin": "admin",
            "developer": "user"
          },
          "default": "user"
        }
      }
    }
  }
}

OIDC Configuration Options ​

OptionDescription
issuerOIDC provider issuer URL (required); must exactly match the token's iss, including any trailing /
client_idOIDC client ID (optional)
client_secretOIDC client secret (optional)
audienceAudience to validate (optional)
jwks_uriJWKS URL, auto-discovered from issuer by default
token_locationToken location: header (default) or query
token_header_nameHeader name, default Authorization
token_header_prefixHeader prefix, default Bearer

Identity Mapping Configuration ​

Account ID Mapping ​

Two modes are supported:

  1. Organization mode (mode: "organization") - entire organization shares one account
json
{
  "account_id": {
    "mode": "organization",
    "source": "claim",
    "claim": "tenant_id"
  }
}
  1. Team mode (mode: "team") - isolation by team
json
{
  "account_id": {
    "mode": "team",
    "source": "claim",
    "claim": "department",
    "prefix": "team-"
  }
}

User ID Mapping ​

json
{
  "user_id": {
    "source": "claim",
    "claim": "sub",
    "prefix": "user-",
    "normalize": "lowercase"
  }
}

Role Mapping ​

json
{
  "role": {
    "source": "claim",
    "claim": "role",
    "mapping": {
      "administrator": "admin",
      "developer": "user",
      "viewer": "user"
    },
    "default": "user"
  }
}

Advanced Mapping Features ​

Composite Mapping ​

json
{
  "account_id": {
    "source": "composite",
    "parts": [
      {"source": "claim", "claim": "tenant_id"},
      {"literal": "-"},
      {"source": "claim", "claim": "department"}
    ]
  }
}

Regex Extraction ​

json
{
  "user_id": {
    "source": "claim",
    "claim": "email",
    "regex": "^([^@]+)@",
    "regex_group": 1
  }
}

Multiple Field Fallback ​

json
{
  "account_id": {
    "source": "claim",
    "claims": ["department", "team", "organization"],
    "fallback": "default"
  }
}

Using OIDC Authentication ​

Clients carry the JWT token in requests:

bash
curl https://openviking.example.com/api/v1/fs/ls?uri=viking:// \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

LDAP Authentication ​

LDAP authentication allows using enterprise LDAP servers (Active Directory, OpenLDAP, etc.) for authentication.

Configure LDAP ​

json
{
  "server": {
    "auth_mode": "ldap",
    "ldap": {
      "host": "ldap.example.com",
      "port": 636,
      "use_ssl": true,
      "use_starttls": false,
      "bind_dn": "cn=openviking,dc=example,dc=com",
      "bind_password": "${LDAP_BIND_PASSWORD}",
      "base_dn": "dc=example,dc=com",
      "user_search_filter": "(uid=%s)",
      "username_attribute": "uid",
      "email_attribute": "mail",
      "name_attribute": "cn",
      "identity": {
        "account_id": {
          "mode": "team",
          "source": "dn_attribute",
          "attribute": "ou",
          "prefix": "team-",
          "fallback": "default"
        },
        "user_id": {
          "source": "attribute",
          "attribute": "uid",
          "normalize": "lowercase"
        },
        "role": {
          "source": "group_membership",
          "group_mapping": {
            "cn=openviking-admins,ou=groups,dc=example,dc=com": "admin",
            "cn=openviking-users,ou=groups,dc=example,dc=com": "user"
          },
          "default": "user"
        }
      }
    }
  }
}

Using Direct Bind ​

json
{
  "server": {
    "auth_mode": "ldap",
    "ldap": {
      "host": "ldap.example.com",
      "port": 389,
      "use_ssl": false,
      "use_starttls": true,
      "base_dn": "dc=example,dc=com",
      "user_dn_pattern": "uid=%s,ou=users,dc=example,dc=com",
      "identity": {
        "account_id": {
          "mode": "organization",
          "source": "fixed",
          "value": "my-company"
        },
        "user_id": {
          "source": "attribute",
          "attribute": "uid"
        },
        "role": {
          "source": "fixed",
          "value": "user"
        }
      }
    }
  }
}

LDAP Configuration Options ​

OptionDescription
hostLDAP server address (required)
portLDAP server port, default 389
use_sslWhether to use LDAPS (SSL), default false
use_starttlsWhether to use StartTLS, default false
bind_dnBind DN for user searches (recommended)
bind_passwordBind password, supports environment variables
base_dnBase DN for searches (required)
user_search_filterUser search filter, default (uid=%s)
user_search_baseUser search base DN, defaults to base_dn
username_attributeUsername attribute, default uid
email_attributeEmail attribute, default mail
name_attributeName attribute, default cn
user_dn_patternUser DN pattern for direct bind

LDAP Identity Mapping ​

Extract Organizational Unit from DN ​

json
{
  "account_id": {
    "mode": "team",
    "source": "dn_attribute",
    "attribute": "ou",
    "prefix": "team-"
  }
}

Map from Attribute ​

json
{
  "account_id": {
    "mode": "team",
    "source": "attribute",
    "attribute": "department",
    "mapping": {
      "Engineering": "eng-team",
      "Sales": "sales-team"
    }
  }
}

Role Mapping based on Group Membership ​

json
{
  "role": {
    "source": "group_membership",
    "group_mapping": {
      "cn=admins,ou=groups,dc=example,dc=com": "admin",
      "cn=users,ou=groups,dc=example,dc=com": "user"
    },
    "default": "user"
  }
}

Using LDAP Authentication ​

Clients submit credentials using Basic Auth:

bash
curl https://openviking.example.com/api/v1/fs/ls?uri=viking:// \
  -u alice

Trusted Mode ​

Trusted mode manages user identities and roles without personal credentials. Creating accounts (including the first administrator) or users generates no personal key, hash, or key index. Resetting a personal key returns HTTP 412 (FAILED_PRECONDITION). Studio can select users without personal keys; Bot authenticates with the deployment root key and sends user identity separately. Existing personal keys are not automatically deleted and are not used for trusted authentication.

Trusted mode skips user-key lookup and instead trusts explicit identity headers on each request:

json
{
  "server": {
    "auth_mode": "trusted",
    "root_api_key": "your-secret-root-key",
    "host": "127.0.0.1"
  }
}

A missing, empty, or whitespace-only server.root_api_key prevents startup, including on localhost.

Rules in Trusted Mode ​

  • Normal data access does not require user registration or user-key provisioning first.
  • X-OpenViking-Account and X-OpenViking-User are required on tenant-scoped requests.
  • A trusted upstream may also send X-OpenViking-Role: user or X-OpenViking-Role: admin to assert the request role. This header is accepted only when root_api_key is configured and the request presents the matching API key. root is not an allowed asserted value; ROOT is reserved for validated Admin API fallback.
  • /api/v1/admin/* is special: when a configured root_api_key is presented, trusted mode treats the request as ROOT. Explicit account/user headers are allowed only when they are complete and match the target URL.
  • For ordinary trusted data APIs, role is determined by X-OpenViking-Role when present and authorized; otherwise by looking up the account/user in APIKeyManager. If the user exists, their configured role is used; otherwise it defaults to USER.
  • Trusted identity comes from the headers, not from a user key. The required root_api_key authenticates the trusted upstream.
  • Trusted data-plane identities are asynchronously registered in batches (five minutes by default) so they eventually appear in the existing account/user management APIs. Registration creates no user API key and never changes groups or an existing user's role. Set server.trusted_identity_flush_interval_seconds to 0 to disable registration entirely; /api/v1/admin/* requests are never registered.
  • Every authenticated request must provide the matching root API key. Anonymous /health probes remain available for liveness checks.
  • Only expose this mode behind a trusted network boundary or an identity-injecting gateway.

Trusted Deployment Admin API ​

Trusted deployments can also call Admin API through a trusted gateway. There are two supported patterns:

  1. Present the trusted deployment's root_api_key. For /api/v1/admin/*, the server treats the request as ROOT after validating that key.
  2. Optionally also present X-OpenViking-Account + X-OpenViking-User when the admin route targets a specific account/user. Those headers must match the target URL and are kept as the request identity, but authorization still comes from the trusted root_api_key.

A trusted deployment with a configured root key can create an account directly; switching to API key mode or registering a gateway user first is unnecessary.

bash
curl -X POST http://localhost:1933/api/v1/admin/accounts \
  -H "X-API-Key: your-secret-root-key" \
  -H "Content-Type: application/json" \
  -d '{"account_id": "acme", "admin_user_id": "alice"}'

Trusted Mode Client Configuration ​

ovcli.conf

json
{
  "url": "http://localhost:1933",
  "auth_mode": "trusted",
  "api_key": "your-trusted-server-key",
  "account": "acme",
  "user": "alice"
}

Provide the configured root key as X-API-Key or SDK api_key, including when connecting to localhost.

curl Example

bash
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
  -H "X-API-Key: your-secret-root-key" \
  -H "X-OpenViking-Account: acme" \
  -H "X-OpenViking-User: alice"

Python SDK

python
import openviking_sdk as ov

client = ov.SyncHTTPClient(
    url="http://localhost:1933",
    api_key="your-secret-root-key",
    account="acme",
    user="alice",
)

Dev Mode ​

When auth_mode = "dev" (or auto-detected when no root_api_key is configured), authentication is disabled. Requests are accepted as ROOT; account/user default to default when identity headers are absent. This is only allowed when the server binds to localhost (127.0.0.1, localhost, or ::1). If host is set to a non-loopback address (e.g. 0.0.0.0) in dev mode, the server will refuse to start.

json
{
  "server": {
    "host": "127.0.0.1",
    "port": 1933
  }
}

Or explicitly:

json
{
  "server": {
    "auth_mode": "dev",
    "host": "127.0.0.1",
    "port": 1933
  }
}

The default host is 127.0.0.1. Before binding to a network address, choose and configure API Key, OIDC, LDAP, or a protected Trusted deployment. Dev mode refuses non-loopback addresses.

Roles and Permissions ​

RoleScopeCapabilities
ROOTDeployment administrationAdmin API and selected system operations; a root API key cannot access tenant data in API key mode
ADMINOwn accountRegular operations + manage users in own account
USEROwn accountRegular operations (ls, read, find, sessions, etc.)

Regular resource operations in this table are still subject to resource ACLs. Account roles determine identity administration and implicit management of public resources; Resource Access Control (ACL) determines read/write/manage permissions on individual directories and files.

In trusted mode, ordinary tenant requests default to USER unless the account/user is registered with a higher role or the gateway asserts X-OpenViking-Role: admin with the configured root API key. X-OpenViking-Role: root is rejected. Admin routes grant ROOT only after validating the root key, with or without explicit identity headers.

Unauthenticated Endpoints ​

Anonymous /health requests require no credentials, allowing load balancers and monitoring tools to check server health. Supplying credentials or complete trusted identity headers triggers authentication.

bash
curl http://localhost:1933/health

Admin API Reference ​

MethodEndpointRoleDescription
POST/api/v1/admin/accountsROOTCreate account with first admin
GET/api/v1/admin/accountsROOTList all accounts
DELETE/api/v1/admin/accounts/{id}ROOTDelete account
POST/api/v1/admin/accounts/{id}/usersROOT, ADMINRegister user
GET/api/v1/admin/accounts/{id}/usersROOT, ADMINList users
DELETE/api/v1/admin/accounts/{id}/users/{uid}ROOT, ADMINRemove user
PUT/api/v1/admin/accounts/{id}/users/{uid}/roleROOT, ADMINPromote a user to ADMIN; ADMIN is limited to its own account
POST/api/v1/admin/accounts/{id}/users/{uid}/keyROOT, ADMINRegenerate user key

Custom Authentication Plugins ​

The server uses a plugin-based auth architecture. Each auth_mode maps to an AuthPlugin implementation. Built-in plugins (dev, api_key, trusted, oidc, ldap) are auto-registered; third-party plugins can be added by subclassing AuthPlugin and registering it before startup.

Plugin Interface (openviking.server.auth.plugin.AuthPlugin) ​

MethodPurpose
resolve_identity(request, api_key, x_openviking_account, x_openviking_user)Resolve credentials to a ResolvedIdentity.
validate_config(config)Validate ServerConfig at startup; should sys.exit(1) on fatal misconfiguration.
initialize(app, service, config)Initialize runtime state (e.g., APIKeyManager) on app.state.
get_request_context_checks(path, identity)Optional post-auth path/identity checks.
can_skip_api_key_for_bot_proxy()Whether the bot proxy may skip API key validation (e.g., dev mode).

Register a Custom Plugin ​

python
from openviking.server.auth.plugin import AuthPlugin
from openviking.server.auth.registry import register_auth_plugin
from openviking.server.identity import ResolvedIdentity, Role

@register_auth_plugin
class CustomAuthPlugin(AuthPlugin):
    auth_mode = "custom"

    async def resolve_identity(self, request, *, api_key=None, x_openviking_account=None, x_openviking_user=None):
        # ... Custom identity resolution logic ...
        return ResolvedIdentity(role=Role.USER, account_id="...", user_id="...")

    def validate_config(self, config):
        pass

    async def initialize(self, app, service, config):
        pass

Then set server.auth_mode = "custom" in ov.conf.

Custom Roles ​

The built-in Role class supports dynamic registration of custom roles with privilege ranks:

python
from openviking.server.identity import Role

Role.register("operator", rank=1)  # Same rank as ADMIN for downgrade checks

The rank is used for downgrade checks; it does not inherit ADMIN permissions. require_role() and require_auth_role() check explicit role names, so a route must list the custom role to accept it.


CLI LDAP Authentication Configuration ​

The OpenViking CLI (ov) supports LDAP authentication. Once configured, all CLI commands automatically use LDAP credentials.

Configuration Methods ​

Edit ~/.openviking/ovcli.conf to add LDAP authentication settings:

json
{
    "url": "http://localhost:1933",
    "auth_mode": "ldap",
    "ldap_username": "alice",
    "ldap_password": "your-ldap-password"
}

Configuration Fields:

FieldRequiredDescription
urlYesOpenViking server URL
auth_modeYesAuthentication mode, set to "ldap" to enable LDAP
ldap_usernameYesLDAP username (UID)
ldap_passwordYesLDAP password; without it the CLI does not send Basic Auth

2. Mixed Configuration ​

Use OPENVIKING_CLI_CONFIG_FILE to select another complete configuration file. The ov CLI does not use OPENVIKING_URL or OPENVIKING_ACCOUNT as connection overrides (except ov chat, which derives the bot endpoint from OPENVIKING_URL); those variables configure Agent plugins.

Using the CLI ​

Once configured, all CLI commands automatically use LDAP authentication:

bash
# List resources
ov ls viking://

# Read a resource
ov read viking://resources/example.md

# Write a resource
ov write viking://resources/test.md --content "Hello LDAP!"

Building the Rust CLI (if updated) ​

If you modify the Rust CLI source code, rebuild it:

bash
make build-cli

The built binary is located at openviking/bin/ov.

Switching Authentication Modes ​

You can switch between LDAP and API Key authentication by editing ~/.openviking/ovcli.conf and changing auth_mode to "api_key" or removing the field.

Security Recommendations ​

  1. Restrict config-file access: The current CLI reads the LDAP password from its config file. Limit read access to the current user and keep the file out of repositories and shared logs
  2. Use HTTPS: Always use HTTPS in production environments
  3. Least privilege: Use regular user accounts for daily operations, admin accounts only for management tasks
  4. Rotate passwords regularly: Follow your organization's password security policy

Troubleshooting ​

"Missing LDAP credentials" error:

  • Check that auth_mode is set to "ldap"
  • Verify ldap_username and ldap_password are configured correctly

"LDAP authentication failed" error:

  • Verify LDAP username and password are correct
  • Check that the LDAP server is reachable
  • Review server logs for detailed error messages

"Permission denied" error:

  • Confirm the user's LDAP groups map to the correct OpenViking role
  • Check if the operation requires admin privileges
  • Contact your system administrator to verify permissions

Debug mode:

bash
# Enable verbose logging
RUST_LOG=debug ov ls viking://

# Check configuration
ov config validate

Open source under the AGPL-3.0 License. Font licenses