docs/en/guides/04-authentication.md
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.
Recommended: API Key mode is the most common and secure default choice, suitable for most deployment scenarios.
| Mode | server.auth_mode | Identity Source | Typical Use | Recommendation |
|---|---|---|---|---|
| API Key Mode ⭐ | api_key | API key. Data ownership is resolved from the user/admin key. | Standard multi-tenant deployment | ⭐⭐⭐⭐⭐ |
| OIDC Mode | oidc | JWT tokens from external identity providers | Enterprise SSO integration with Okta/Auth0/Keycloak | ⭐⭐⭐⭐ |
| LDAP Mode | ldap | Enterprise LDAP/AD directory services | Integrate with existing enterprise user directory | ⭐⭐⭐⭐ |
| Trusted Mode | trusted | X-OpenViking-Account / X-OpenViking-User, plus root_api_key on non-localhost deployments. Role is looked up from APIKeyManager if the user exists. | Behind a trusted gateway or internal network boundary | ⭐⭐⭐ |
| Dev Mode | dev | No authentication, always ROOT | Local development only | ⭐⭐ |
If auth_mode is not explicitly configured:
root_api_key is set (non-empty): auto-selects api_key moderoot_api_key is not set: auto-selects dev modeNote: Setting
root_api_keyto an empty string""is invalid. Either set a non-empty value or remove the setting entirely.
API Key mode is the most secure and easiest default choice.
Configure the authentication mode in the server section of ov.conf:
{
"server": {
"auth_mode": "api_key",
"root_api_key": "your-secret-root-key"
}
}
openviking-server
| Key Type | Created By | Role | Purpose |
|---|---|---|---|
| Root Key | Server config (root_api_key) | ROOT | Account management + selected system/monitoring operations |
| User Key | Admin API | ADMIN or USER | Per-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.
Use the root key to create accounts (workspaces) and users via the Admin API:
# 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": "..."}}
OpenViking accepts API keys via two headers:
X-API-Key header
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
-H "X-API-Key: <user-key>"
**Authorization: Bearer ***
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
-H "Authorization: Bearer <user-key>"
Python SDK (HTTP)
import openviking as ov
client = ov.SyncHTTPClient(
url="http://localhost:1933",
api_key="<user-key>",
)
CLI (via ovcli.conf)
{
"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 override flags
openviking ls viking://
The CLI supports configuring both api_key (for regular user operations) and root_api_key (for admin operations) in ovcli.conf:
{
"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:
# 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:
admin, system, reindexroot_api_key is not configured in ovcli.confroot_api_key instead of api_key for the requestTenant-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.
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 (OpenID Connect) authentication allows using JWT tokens from external identity providers (Okta, Auth0, Keycloak, Azure AD, etc.) for authentication.
OIDC and LDAP authentication require optional dependencies. You can install them as:
# Install auth features only
uv pip install openviking[auth]
# Or install with all features (including bot)
uv pip install openviking[bot]
Configure OIDC in ov.conf:
{
"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"
}
}
}
}
}
| Option | Description |
|---|---|
issuer | OIDC provider issuer URL (required) |
client_id | OIDC client ID (optional) |
client_secret | OIDC client secret (optional) |
audience | Audience to validate (optional) |
jwks_uri | JWKS URL, auto-discovered from issuer by default |
token_location | Token location: header (default) or query |
token_header_name | Header name, default Authorization |
token_header_prefix | Header prefix, default Bearer |
Two modes are supported:
mode: "organization") - entire organization shares one account{
"account_id": {
"mode": "organization",
"source": "claim",
"claim": "tenant_id"
}
}
mode: "team") - isolation by team{
"account_id": {
"mode": "team",
"source": "claim",
"claim": "department",
"prefix": "team-"
}
}
{
"user_id": {
"source": "claim",
"claim": "sub",
"prefix": "user-",
"normalize": "lowercase"
}
}
{
"role": {
"source": "claim",
"claim": "role",
"mapping": {
"administrator": "admin",
"developer": "user",
"viewer": "user"
},
"default": "user"
}
}
{
"account_id": {
"source": "composite",
"parts": [
{"source": "claim", "claim": "tenant_id"},
{"literal": "-"},
{"source": "claim", "claim": "department"}
]
}
}
{
"user_id": {
"source": "claim",
"claim": "email",
"regex": "^([^@]+)@",
"regex_group": 1
}
}
{
"account_id": {
"source": "claim",
"claim": ["department", "team", "organization"],
"fallback": "default"
}
}
Clients carry the JWT token in requests:
curl https://openviking.example.com/api/v1/fs/ls?uri=viking:// \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."
LDAP authentication allows using enterprise LDAP servers (Active Directory, OpenLDAP, etc.) for authentication.
{
"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"
}
}
}
}
}
{
"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"
}
}
}
}
}
| Option | Description |
|---|---|
host | LDAP server address (required) |
port | LDAP server port, default 389 |
use_ssl | Whether to use LDAPS (SSL), default false |
use_starttls | Whether to use StartTLS, default false |
bind_dn | Bind DN for user searches (recommended) |
bind_password | Bind password, supports environment variables |
base_dn | Base DN for searches (required) |
user_search_filter | User search filter, default (uid=%s) |
user_search_base | User search base DN, defaults to base_dn |
username_attribute | Username attribute, default uid |
email_attribute | Email attribute, default mail |
name_attribute | Name attribute, default cn |
user_dn_pattern | User DN pattern for direct bind |
{
"account_id": {
"mode": "team",
"source": "dn_attribute",
"attribute": "ou",
"prefix": "team-"
}
}
{
"account_id": {
"mode": "team",
"source": "attribute",
"attribute": "department",
"mapping": {
"Engineering": "eng-team",
"Sales": "sales-team"
}
}
}
{
"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"
}
}
Clients submit credentials using Basic Auth:
curl https://openviking.example.com/api/v1/fs/ls?uri=viking:// \
-u alice:password123
Trusted mode skips user-key lookup and instead trusts explicit identity headers on each request:
{
"server": {
"auth_mode": "trusted",
"host": "127.0.0.1"
}
}
X-OpenViking-Account and X-OpenViking-User are required on tenant-scoped requests.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.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.root_api_key is configured, it acts as proof that the caller is an approved trusted upstream.root_api_key is also configured, every request must still provide a matching API key.Trusted deployments can also call Admin API through a trusted gateway. There are two supported patterns:
root_api_key. For /api/v1/admin/*, the server treats the request as ROOT after validating that key.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.Example using a trusted upstream identity:
# First, register the gateway admin (do this once in api_key mode)
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": "platform", "admin_user_id": "gateway-admin"}'
# Then use that identity in trusted mode; admin authorization comes from root_api_key
curl -X POST http://localhost:1933/api/v1/admin/accounts \
-H "X-API-Key: your-secret-root-key" \
-H "X-OpenViking-Account: platform" \
-H "X-OpenViking-User: gateway-admin" \
-H "Content-Type: application/json" \
-d '{
"account_id": "acme",
"admin_user_id": "alice"
}'
ovcli.conf
{
"url": "http://localhost:1933",
"auth_mode": "trusted",
"api_key": "your-trusted-server-key",
"account": "acme",
"user": "alice"
}
curl Example
curl http://localhost:1933/api/v1/fs/ls?uri=viking:// \
-H "X-OpenViking-Account: acme" \
-H "X-OpenViking-User: alice"
Python SDK
import openviking as ov
client = ov.SyncHTTPClient(
url="http://localhost:1933",
account="acme",
user="alice",
)
When auth_mode = "dev" (or auto-detected when no root_api_key is configured), authentication is disabled. All requests are accepted as ROOT with the default account. 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.
{
"server": {
"host": "127.0.0.1",
"port": 1933
}
}
Or explicitly:
{
"server": {
"auth_mode": "dev",
"host": "127.0.0.1",
"port": 1933
}
}
Security note: The default
hostis127.0.0.1. If you need to expose the server on the network, you must configureroot_api_key.
| Role | Scope | Capabilities |
|---|---|---|
| ROOT | Global | All operations + Admin API (create/delete accounts, manage users) |
| ADMIN | Own account | Regular operations + manage users in own account |
| USER | Own account | Regular operations (ls, read, find, sessions, etc.) |
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 also allow a trusted ROOT fallback when no explicit identity is provided.
The /health endpoint never requires authentication. This allows load balancers and monitoring tools to check server health.
curl http://localhost:1933/health
| Method | Endpoint | Role | Description |
|---|---|---|---|
| POST | /api/v1/admin/accounts | ROOT | Create account with first admin |
| GET | /api/v1/admin/accounts | ROOT | List all accounts |
| DELETE | /api/v1/admin/accounts/{id} | ROOT | Delete account |
| POST | /api/v1/admin/accounts/{id}/users | ROOT, ADMIN | Register user |
| GET | /api/v1/admin/accounts/{id}/users | ROOT, ADMIN | List users |
| DELETE | /api/v1/admin/accounts/{id}/users/{uid} | ROOT, ADMIN | Remove user |
| PUT | /api/v1/admin/accounts/{id}/users/{uid}/role | ROOT, ADMIN | Promote a user to ADMIN; ADMIN is limited to its own account |
| POST | /api/v1/admin/accounts/{id}/users/{uid}/key | ROOT, ADMIN | Regenerate user key |
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.
openviking.server.auth.plugin.AuthPlugin)| Method | Purpose |
|---|---|
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. |
requires_api_key_manager() | Whether Admin API routes need an APIKeyManager. |
can_skip_api_key_for_bot_proxy() | Whether the bot proxy may skip API key validation (e.g., dev mode). |
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.
The built-in Role class supports dynamic registration of custom roles with privilege ranks:
from openviking.server.identity import Role
Role.register("operator", rank=1) # Between USER (0) and ADMIN (1)
Custom roles work with require_role() and require_auth_role() decorators out of the box.
The OpenViking CLI (ov) supports LDAP authentication. Once configured, all CLI commands automatically use LDAP credentials.
Edit ~/.openviking/ovcli.conf to add LDAP authentication settings:
{
"url": "http://localhost:1933",
"auth_mode": "ldap",
"ldap_username": "alice",
"ldap_password": "password123",
"account": "default"
}
Configuration Fields:
| Field | Required | Description |
|---|---|---|
url | Yes | OpenViking server URL |
auth_mode | Yes | Authentication mode, set to "ldap" to enable LDAP |
ldap_username | Yes | LDAP username (UID) |
ldap_password | No | LDAP password (omit to skip password in CLI) |
account | No | OpenViking account ID (defaults to "default") |
You can configure some settings in the file and override others with environment variables (e.g. OPENVIKING_URL, OPENVIKING_ACCOUNT).
Once configured, all CLI commands automatically use LDAP authentication:
# 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!"
If you modify the Rust CLI source code, rebuild it:
make build-cli
The built binary is located at openviking/bin/ov.
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.
"Missing LDAP credentials" error:
auth_mode is set to "ldap"username and password are configured correctly"LDAP authentication failed" error:
"Permission denied" error:
Debug mode:
# Enable verbose logging
RUST_LOG=debug ov ls viking://
# Check configuration
ov doctor