Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Auth-O-Tron Auth-O-Tron

Auth-O-Tron is an authentication gateway designed to work with your ingress layer (e.g., NGINX auth_request). It validates credentials from multiple sources, enriches users with roles and attributes, and returns signed JWTs that your backend services can trust.

Key Features

  • Multiple Auth Providers: Run JWT, OpenID Connect, API key validation, and basic auth simultaneously. Each provider can target a different realm.
  • NGINX Integration: Drop-in compatibility with auth_request. Auth-O-Tron returns 200/401 responses that NGINX uses to allow or block requests.
  • JWT Proxying: Authenticated users receive a signed JWT that downstream services can verify without calling back to the gateway.
  • Attribute-Based Access Control: Define rules based on user roles, groups, or custom claims from your identity providers.
  • Production-Ready: Written in Rust, ships as a single binary with no runtime dependencies. Starts fast, uses minimal memory, runs well in containers.

Architecture

graph LR
    C[Client] -- "1: Request" --> N[NGINX / Ingress]
    N -- "2: auth_request" --> A[Auth-O-Tron]
    A -. "3: validate" .-> P[Providers]
    A -. "4: enrich" .-> Aug[Augmenters]
    A -- "5: 200 + JWT" --> N
    N -- "6: Request + JWT" --> B[Backend Services]

Auth-O-Tron handles authentication at the edge. Once validated, NGINX forwards the original request to your backend with the signed JWT attached. Your backends stay simple — they just trust the token.

Source Code

https://github.com/ecmwf/auth-o-tron

Installation

Docker

Pull the latest image from the ECMWF container registry:

docker pull eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.3.0

Build from Source

Clone the repository and build with Cargo:

git clone https://github.com/ecmwf/auth-o-tron.git
cd auth-o-tron
cargo build --release

The binary is written to target/release/authotron.

Requirements

  • Rust stable (edition 2024)

Configuration

Auth-O-Tron reads its configuration from a YAML file. Set the path with the AOT_CONFIG_PATH environment variable, or place it at ./config.yaml (the default).

See the Configuration section for the full reference.

Quick Start

This guide gets Auth-O-Tron running locally with a basic auth provider for testing.

Minimal Configuration

Create config.yaml with this content:

version: "2.0.0"

providers:
  - name: "local"
    type: "plain"
    realm: "default"
    users:
      - username: "test_user"
        password: "secret123"

augmenters: []

store:
  enabled: false

services: []

jwt:
  iss: "auth-o-tron"
  exp: 3600
  secret: "your-secret-key-for-local-testing-only"

logging:
  level: "info"
  format: "console"

server:
  host: "0.0.0.0"
  port: 8080

metrics:
  enabled: false

Run the Server

AOT_CONFIG_PATH=config.yaml ./target/release/authotron

You should see startup logs indicating the server is listening on port 8080.

Test Authentication

Send a request with basic auth credentials:

curl -i -H "Authorization: Basic dGVzdF91c2VyOnNlY3JldDEyMw==" \
  http://localhost:8080/authenticate

The response should be:

HTTP/1.1 200 OK
Authorization: Bearer <jwt-token>

Inspect the JWT

Copy the token and decode it to see the claims:

python3 -c "import base64,sys,json; p=sys.argv[1].split('.')[1]; p+='='*(-len(p)%4); print(json.dumps(json.loads(base64.urlsafe_b64decode(p)),indent=2))" "<jwt-token>"

You will see the user identity, roles, and other attributes encoded in the payload.

Next Steps

For a complete NGINX integration example with docker-compose, see examples/nginx-auth in the repository. This demonstrates how to protect backend services using Auth-O-Tron as an authentication sub-request handler.

How It Works

Auth-O-Tron acts as a gateway between clients and your services. It validates credentials from multiple sources and returns a signed JWT that downstream services can trust.

Authentication Flow

Here is the complete flow from request to JWT:

sequenceDiagram
    participant Client
    participant AOT as Auth-O-Tron
    participant Provider as Auth Provider
    participant Augmenter

    Client->>AOT: GET /authenticate<br/>Authorization: Basic/Bearer
    AOT->>AOT: Parse header, normalize schemes
    AOT->>AOT: Filter providers by scheme + realm

    par Try matching providers (first success wins)
        AOT->>Provider: Validate credentials (5s timeout)
        Provider-->>AOT: User {username, realm, roles}
    end

    loop Each augmenter (filtered by realm)
        AOT->>Augmenter: Enrich user
        Augmenter-->>AOT: Add roles / attributes
    end

    AOT->>AOT: Generate JWT (HS256)
    AOT-->>Client: 200 OK<br/>Authorization: Bearer <jwt>

Realms

A realm is a named authentication boundary. Every provider and augmenter belongs to exactly one realm. Realms let you run completely separate authentication setups side by side — different user databases, different token validation, different role sources — within a single Auth-O-Tron instance.

Usernames must be unique within a realm. The JWT sub claim is formatted as {realm}-{username}, so internal-alice and external-alice are distinct identities.

Clients can target a specific realm by sending the X-Auth-Realm header. Without it, all providers are eligible regardless of realm.

Provider Chain

Auth-O-Tron can run multiple authentication providers side by side. You might have:

  • Plain provider (Basic auth) for internal users
  • JWT provider (Bearer tokens) for API clients
  • OpenID Connect offline tokens for external partners

Each provider targets a specific realm. When a client sends X-Auth-Realm: internal, only providers matching that realm will attempt authentication. Without the header, all providers are eligible.

The provider chain uses a “first match wins” strategy. All eligible providers run in parallel with a configurable timeout. The first successful result is used. If all fail, the client receives a 401 response with WWW-Authenticate challenges listing the available schemes.

Augmenters

After authentication succeeds, augmenters enrich the user object with additional roles and attributes. Common use cases:

  • LDAP augmenter: Query your directory to add group memberships as roles
  • Plain augmenter: Static role mappings based on username patterns
  • Plain advanced: Match against usernames or existing roles, then add new roles and attributes

Augmenters also respect realm filtering. If your LDAP augmenter targets realm: internal, it only runs for users authenticated against providers in that realm. This lets you maintain separate attribute sources for different user populations.

Non-plain_advanced augmenters (LDAP, plain) run in parallel. plain_advanced augmenters run sequentially after all parallel augmenters complete, so they can match on roles added by other sources. Augmenters add roles and attributes but never remove existing ones.

JWT Claims

The resulting JWT contains these standard and custom claims:

  • sub: Subject, formatted as {realm}-{username}
  • iss: Configured issuer (your organization)
  • exp: Expiration time (configurable, respects user attribute exp if present)
  • iat: When the token was issued
  • roles: Array of role strings from augmenters
  • username: Human-readable username
  • realm: The realm that authenticated this user
  • scopes: Permissions or scopes if provided by the auth source
  • attributes: Key-value map of additional user properties from augmenters

Downstream services validate this JWT using the shared secret and extract user context without needing to query Auth-O-Tron again.

Consumer contract notes

If you consume JWTs through authotron-client (instead of decoding JWTs yourself), two normalization rules apply:

  • A synthetic "default" role is injected and deduplicated so every decoded user has at least one role.
  • attributes are normalized to HashMap<String, String>:
    • JSON strings are passed through unchanged
    • numbers/booleans/null/arrays/objects are stringified to JSON form

For Polytope/BITS integration, the canonical downstream payload is stored under job.user.auth and currently uses version: 1.

Configuration Overview

Auth-O-Tron is configured through a YAML file. By default, it looks for ./config.yaml in the working directory. You can specify a different path using the AOT_CONFIG_PATH environment variable.

Config File Location

# Default location
./config.yaml

# Custom location via environment variable
export AOT_CONFIG_PATH=/etc/auth-o-tron/config.yaml

Configuration Versioning

The configuration file has a version field that determines the schema:

  • version “1.0.0”: Legacy format with a single bind_address field for the server.
  • version “2.0.0”: Current format with separate server and metrics sections for more flexible configuration.

When Auth-O-Tron loads a version 1.0.0 configuration, it automatically converts it to version 2.0.0 at runtime. This conversion maps the legacy bind_address to the new server section. It is recommended to update your configuration files to version 2.0.0 for clarity and future compatibility.

Environment Variable Overrides

Any configuration value can be overridden using environment variables. The prefix is AOT_, and double underscores (__) are used to represent nested configuration keys.

For example, to override the JWT issuer:

AOT_JWT__ISS=my-custom-issuer

This would set jwt.iss in the configuration.

Precedence

Configuration values are resolved in this order:

  1. YAML file: Base configuration values.
  2. Environment variables: Override values from the YAML file.

This means environment variables always take precedence over file-based settings.

Complete Example

Here is a complete version 2.0.0 configuration that demonstrates all major sections:

version: "2.0.0"

server:
  host: "0.0.0.0"
  port: 8080

metrics:
  enabled: true
  port: 9090

providers:
  - type: plain
    name: local_users
    realm: internal
    users:
      - username: admin
        password: adminpass
        roles: [admin, user]
      - username: guest
        password: guestpass
        roles: [readonly]

  - type: jwt
    name: jwt_validation
    realm: external
    cert_uri: https://auth.example.com/.well-known/jwks.json
    iam_realm: example-realm

augmenters:
  - type: plain_advanced
    name: admin_override
    realm: internal
    match:
      username: [admin]
    augment:
      roles: [superuser]
      attributes:
        department: engineering

jwt:
  iss: auth-o-tron.example.com
  aud: my-application
  exp: 3600
  secret: your-secret-key-here

store:
  enabled: true
  type: mongo
  uri: mongodb://localhost:27017
  database: auth_o_tron

logging:
  level: info
  format: json
  service_name: auth-o-tron
  service_version: 1.0.0

Server & Metrics

The server and metrics sections control how Auth-O-Tron exposes its services.

Server Configuration

The server block configures the main application server that handles authentication requests.

FieldTypeDefaultDescription
hoststring“0.0.0.0”The network interface to bind to
portintegerrequiredThe port number to listen on
server:
  host: "0.0.0.0"
  port: 8080

Metrics Configuration

The metrics block configures a separate server for Prometheus-compatible metrics and health checks.

FieldTypeDefaultDescription
enabledbooleantrueWhether to start the metrics server
portinteger9090The port number for metrics
metrics:
  enabled: true
  port: 9090

Endpoint Mapping

graph TB
    subgraph "Application Server (port 8080)"
        A1["/authenticate"]
        A2["/token · /tokens"]
        A3["/providers · /augmenters"]
        A4["/health"]
        A5["/ (homepage)"]
    end

    subgraph "Metrics Server (port 9090)"
        M1["/metrics"]
        M2["/health"]
    end

    Clients -->|"External traffic"| A1
    Prometheus -->|"Scraping"| M1
    K8s[K8s Probes] -->|"Liveness / Readiness"| M2

The application server and metrics server expose different endpoints:

Application Server (configured server.port):

  • /authenticate - Authentication endpoint
  • /tokens - Token management
  • /providers - Provider information
  • /augmenters - Augmenter information
  • /health - Health check

Metrics Server (configured metrics.port):

  • /metrics - Prometheus metrics
  • /health - Health check

Note that /health is available on both servers when metrics are enabled. When metrics.enabled: false, /health is only served on the application port.

Port Collision Detection

Auth-O-Tron validates that the application port and metrics port are different. If you accidentally configure them to the same value, the server will fail to start with an error message explaining the conflict.

# This configuration will FAIL - ports are the same
server:
  port: 8080

metrics:
  enabled: true
  port: 8080  # ERROR: Cannot be the same as server.port

Example Configurations

Minimal configuration (metrics disabled):

version: "2.0.0"

server:
  port: 8080

metrics:
  enabled: false

Full configuration with custom ports:

version: "2.0.0"

server:
  host: "127.0.0.1"
  port: 8080

metrics:
  enabled: true
  port: 9090

Providers

Providers are authentication backends that validate credentials. Auth-O-Tron can run multiple providers simultaneously. Each incoming authentication request is tried against all configured providers until one succeeds or all fail.

All providers share these common fields:

FieldTypeRequiredDescription
typestringyesThe provider type (determines which provider is instantiated)
namestringyesA unique identifier for this provider instance
realmstringyesThe authentication realm this provider handles (see Realms)

Provider Types

1. Plain Provider

Type: plain

The plain provider implements HTTP Basic Authentication against a static list of users defined in the configuration file. This is useful for development, testing, or simple deployments.

Fields:

FieldTypeDescription
usersarrayList of user objects with username, password, and roles

Each user object has:

  • username: The login name
  • password: The password (compared as plaintext)
  • roles: Array of role strings assigned to the user (optional, defaults to empty)

Example:

providers:
  - type: plain
    name: local_users
    realm: internal
    users:
      - username: alice
        password: alicepass123
        roles: [admin, developer]
      - username: bob
        password: bobpass456
        roles: [developer]

2. JWT Provider

Type: jwt

The JWT provider validates JSON Web Tokens using a JWKS (JSON Web Key Set) endpoint. It fetches public keys from the configured URI and validates token signatures.

Fields:

FieldTypeDescription
cert_uristringURL to the JWKS endpoint
iam_realmstringThe realm/issuer to validate against

The provider caches the JWKS for 600 seconds to avoid repeated requests to the certificate endpoint.

Example:

providers:
  - type: jwt
    name: jwt_validation
    realm: external
    cert_uri: https://auth.example.com/.well-known/jwks.json
    iam_realm: example-realm

3. ECMWF API Provider

Type: ecmwf-api

This provider validates tokens against the ECMWF (European Centre for Medium-Range Weather Forecasts) API. It makes an HTTP request to the ECMWF identity service to verify the token.

Fields:

FieldTypeDescription
uristringBase URL of the ECMWF API

The provider calls {uri}/who-am-i?token={token} to validate credentials. Results are cached for 60 seconds to reduce API load. Contact ECMWF for the correct uri value.

Example:

providers:
  - type: ecmwf-api
    name: ecmwf_validator
    realm: ecmwf
    uri: https://api.example.com

4. EFAS API Provider

Type: efas-api

This provider validates tokens against the EFAS (European Flood Awareness System) API. Similar to the ECMWF provider but with a different endpoint pattern.

Fields:

FieldTypeDescription
uristringBase URL of the EFAS API

The provider calls {uri}?token={token} to validate credentials. Results are cached for 60 seconds. Contact ECMWF for the correct uri value.

Example:

providers:
  - type: efas-api
    name: efas_validator
    realm: efas
    uri: https://efas.example.com/api

5. OpenID Connect Offline Token Provider

Type: openid-offline

This provider handles OpenID Connect offline tokens, commonly used with Keycloak. It introspects the token, exchanges it for an access token, and validates the result via JWKS.

Fields:

FieldTypeDescription
cert_uristringURL to the JWKS endpoint
public_client_idstringClient ID for public token introspection
private_client_idstringClient ID for private token exchange
private_client_secretstringSecret for the private client
iam_urlstringBase URL of the identity management server

The flow is: introspect the offline token, exchange for an access token, validate the access token via JWKS.

Example:

providers:
  - type: openid-offline
    name: keycloak_offline
    realm: keycloak
    cert_uri: https://keycloak.example.com/realms/master/protocol/openid-connect/certs
    public_client_id: public-client
    private_client_id: private-client
    private_client_secret: super-secret-value
    iam_url: https://keycloak.example.com

6. ECMWF Token Generator Provider

Type: ecmwf-token-generator

This provider integrates with the ECMWF token generator service. It validates tokens and can exchange them for access tokens from the ECMWF identity system.

Fields:

FieldTypeDescription
cert_uristringURL to the JWKS endpoint
client_idstringOAuth client ID
client_secretstringOAuth client secret
token_generator_urlstringURL of the ECMWF token generator

The flow is: validate the presented token, exchange for an access token, validate the access token via JWKS.

Contact ECMWF for the correct cert_uri and token_generator_url values for your environment.

Example:

providers:
  - type: ecmwf-token-generator
    name: ecmwf_token_gen
    realm: ecmwf
    cert_uri: https://auth.example.com/realms/default/protocol/openid-connect/certs
    client_id: my-client-id
    client_secret: my-client-secret
    token_generator_url: https://auth.example.com/token-generator

Multiple Providers

You can configure multiple providers to support different authentication methods simultaneously:

providers:
  - type: plain
    name: dev_users
    realm: development
    users:
      - username: dev
        password: devpass
        roles: [admin]

  - type: jwt
    name: production_jwt
    realm: production
    cert_uri: https://auth.company.com/.well-known/jwks.json
    iam_realm: company-realm

When a request arrives, Auth-O-Tron runs all matching providers in parallel. The first successful result wins.

Augmenters

Augmenters enrich authenticated users with additional roles and attributes. They run after a provider successfully authenticates a user but before the final token is generated. Augmenters can be filtered by realm, so different augmenters apply to users from different authentication sources.

All augmenters share these common fields:

FieldTypeRequiredDescription
typestringyesThe augmenter type
namestringyesA unique identifier for this augmenter
realmstringyesOnly apply to users authenticated through this realm

Augmenter Types

1. Plain Augmenter

Type: plain

The plain augmenter is a simple role mapper that assigns roles based on username. It is deprecated and will be removed in a future version. Use plain_advanced instead.

When this augmenter runs, it logs a deprecation warning.

Fields:

FieldTypeDescription
rolesmapMap of role names to lists of usernames that should receive that role

Example:

augmenters:
  - type: plain
    name: basic_roles
    realm: internal
    roles:
      admin: [alice, bob]
      readonly: [guest, anonymous]

In this example, users “alice” and “bob” receive the “admin” role, while “guest” and “anonymous” receive the “readonly” role.

2. Plain Advanced Augmenter

Type: plain_advanced

The plain advanced augmenter provides conditional role and attribute injection based on username or existing roles. It is the recommended replacement for the deprecated plain augmenter.

Fields:

FieldTypeDescription
matchobjectConditions that must be met for augmentation to apply
augmentobjectRoles and attributes to add when conditions match

Match conditions:

FieldTypeDescription
usernamearrayList of usernames that trigger this augmenter
rolearrayList of existing roles that trigger this augmenter

A user matches if their username is in the username list OR if they have any of the listed role entries.

Augmentation:

FieldTypeDescription
rolesarrayRoles to add to the user
attributesmapKey-value attributes to add or update on the user

Example:

augmenters:
  - type: plain_advanced
    name: admin_boost
    realm: internal
    match:
      username: [admin, root]
      role: [superuser]
    augment:
      roles: [full_access, audit]
      attributes:
        department: engineering
        clearance: top-secret

In this example, any user with username “admin” or “root”, OR any user who already has the “superuser” role, receives the “full_access” and “audit” roles plus the department and clearance attributes.

3. LDAP Augmenter

Type: ldap

The LDAP augmenter queries a Lightweight Directory Access Protocol server to extract roles and group memberships for authenticated users. This is useful when user roles are managed in an enterprise directory like Active Directory or OpenLDAP.

Fields:

FieldTypeDescription
uristringLDAP server URI (e.g., ldap://ldap.example.com:389)
search_basestringBase DN for searches (e.g., ou=users,dc=example,dc=com)
filterstringSingle LDAP filter template (optional, uses {username} placeholder)
filtersarrayMultiple LDAP filter templates (optional, alternative to single filter)
bind_dnstringDN to bind with for searching (service account)
ldap_passwordstringPassword for the bind DN

Filter vs Filters:

  • Use filter for a single search that returns group CNs directly.
  • Use filters for multiple searches that return hierarchical paths like /TeamA/Admin.

When using filters, each filter is executed and the results are combined. The filter template can use {username} as a placeholder.

Results are cached for 120 seconds to reduce LDAP server load.

Example with single filter:

augmenters:
  - type: ldap
    name: ldap_groups
    realm: corporate
    uri: ldap://ldap.company.com:389
    search_base: ou=groups,dc=company,dc=com
    filter: (member=uid={username},ou=users,dc=company,dc=com)
    bind_dn: cn=service,dc=company,dc=com
    ldap_password: service-password

Example with multiple filters:

augmenters:
  - type: ldap
    name: ldap_teams
    realm: corporate
    uri: ldap://ldap.company.com:389
    search_base: ou=teams,dc=company,dc=com
    filters:
      - (member=uid={username},ou=users,dc=company,dc=com)
      - (manager=uid={username},ou=users,dc=company,dc=com)
    bind_dn: cn=service,dc=company,dc=com
    ldap_password: service-password

Multiple Augmenters

You can configure multiple augmenters to build up a user’s final set of roles and attributes:

augmenters:
  - type: ldap
    name: ldap_roles
    realm: internal
    uri: ldap://ldap.example.com
    search_base: ou=groups,dc=example,dc=com
    filter: (member=uid={username},ou=users,dc=example,dc=com)
    bind_dn: cn=reader,dc=example,dc=com
    ldap_password: secret

Non-plain_advanced augmenters (such as LDAP) run in parallel. plain_advanced augmenters run sequentially after all parallel augmenters complete. This lets advanced augmenters match on roles added by LDAP or other parallel sources.

Token Store

The token store provides optional persistence for opaque tokens. By default, token storage is disabled and all token operations return an error.

When to Use a Token Store

A token store is needed when you want to:

  • Issue and validate opaque tokens (random strings) instead of JWTs
  • Revoke tokens before they expire
  • Track active token sessions
  • Share token state across multiple Auth-O-Tron instances

If you only use JWT-based authentication, you do not need to configure a token store.

NoStore (Default)

When no store is configured or when the store is explicitly disabled, Auth-O-Tron uses the NoStore backend. This backend returns errors for all token storage operations.

To explicitly disable the store:

store:
  enabled: false

MongoDB Backend

Type: mongo

The MongoDB backend stores tokens in a MongoDB database. This enables persistent, shared token state across multiple server instances.

Fields:

FieldTypeRequiredDescription
typestringyesMust be “mongo”
uristringyesMongoDB connection URI
databasestringyesDatabase name to use

Collections:

The MongoDB backend creates and uses two collections:

  • tokens: Stores token documents with a unique index on token.token_string
  • users: Stores user documents with a unique compound index on username + realm, and a unique index on user_id

Example:

store:
  enabled: true
  type: mongo
  uri: mongodb://localhost:27017
  database: auth_o_tron

Example with authentication:

store:
  enabled: true
  type: mongo
  uri: mongodb://username:password@mongodb.example.com:27017/auth_o_tron?authSource=admin
  database: auth_o_tron

Example with replica set:

store:
  enabled: true
  type: mongo
  uri: mongodb://mongo1.example.com:27017,mongo2.example.com:27017/auth_o_tron?replicaSet=rs0
  database: auth_o_tron

Configuration Examples

Disabled store (default):

version: "2.0.0"

server:
  port: 8080

store:
  enabled: false

MongoDB store:

version: "2.0.0"

server:
  port: 8080

store:
  enabled: true
  type: mongo
  uri: mongodb://localhost:27017
  database: auth_o_tron

JWT Signing

The jwt section configures how Auth-O-Tron generates and signs JSON Web Tokens for authenticated users. This section is required if you want Auth-O-Tron to issue JWTs.

Configuration Fields

FieldTypeRequiredDescription
issstringyesThe issuer claim, identifies who issued the token
audstringnoReserved for future use (not currently included in issued JWTs)
expintegeryesToken expiration time in seconds
secretstringyesThe HMAC secret key used for signing

Algorithm

Auth-O-Tron uses the HS256 (HMAC with SHA-256) algorithm for signing JWTs. This is a symmetric algorithm where the same secret is used for both signing and verification.

Important: Keep your secret secure. Anyone with the secret can forge valid tokens.

Generated Claims

When Auth-O-Tron issues a JWT, it includes the following claims:

ClaimDescription
subSubject, formatted as “{realm}-{username}”
issIssuer, from the iss config field
expExpiration time, calculated as min(config_exp, user_attribute_exp)
iatIssued at timestamp
rolesArray of role strings from the user
usernameThe authenticated username
realmThe authentication realm
scopesArray of scope strings (if any)
attributesMap of additional user attributes

The exp claim uses the minimum of:

  • The configured expiration time
  • Any exp attribute on the user object (useful for short-lived tokens)

This allows per-user token lifetime overrides.

Example Configuration

jwt:
  iss: auth-o-tron.example.com
  aud: my-application
  exp: 3600
  secret: your-256-bit-secret-key-here-minimum-32-characters

This configuration:

  • Sets the issuer to “auth-o-tron.example.com”
  • Sets a default expiration of 1 hour (3600 seconds)
  • Uses the provided secret for HMAC signing

Example JWT Payload

A token generated with the above configuration might have this payload:

{
  "sub": "internal-alice",
  "iss": "auth-o-tron.example.com",

  "exp": 1704067200,
  "iat": 1704063600,
  "roles": ["admin", "developer"],
  "username": "alice",
  "realm": "internal",
  "scopes": ["read", "write"],
  "attributes": {
    "department": "engineering",
    "team": "platform"
  }
}

Security Considerations

  • Use a secret that is at least 32 bytes (256 bits) for HS256
  • Store the secret securely, such as in a secrets manager or environment variable
  • Rotate secrets periodically
  • Use short expiration times and require re-authentication
  • Consider using the aud claim to prevent token replay across different services

Logging

The logging section configures how Auth-O-Tron outputs diagnostic and operational information. Proper logging is essential for debugging, monitoring, and auditing.

Configuration Fields

FieldTypeDefaultDescription
levelstringinfoMinimum log level to output
formatstringconsoleOutput format: “json” or “console”
service_namestringauthotronService identifier in logs
service_versionstring(from crate)Version identifier in logs

Log Levels

The level field controls which messages are emitted. Messages at the configured level and above are logged.

LevelDescription
traceVery detailed internal state information
debugInformation useful for debugging
infoGeneral operational information
warnWarning conditions that are not errors
errorError conditions

Log Formats

Console Format

The console format outputs human-readable log lines with color coding:

2024-01-15T10:30:00.123Z  INFO auth_o_tron::server: Server started on 0.0.0.0:8080

JSON Format

The JSON format outputs structured logs following OpenTelemetry conventions, aligned with the ECMWF Codex Observability guidelines. This is recommended for production deployments and integration with log aggregation systems.

JSON format includes these fields:

FieldDescription
severityTextLog level as text (INFO, ERROR, etc.)
severityNumberNumeric log level code
bodyThe log message
timestampISO 8601 timestamp
resourceResource attributes including service name and version
attributesAdditional structured attributes

Example JSON log entry:

{
  "severityText": "INFO",
  "severityNumber": 9,
  "body": "Server started",
  "timestamp": "2024-01-15T10:30:00.123Z",
  "resource": {
    "service.name": "auth-o-tron",
    "service.version": "1.2.0"
  },
  "attributes": {
    "server.host": "0.0.0.0",
    "server.port": 8080
  }
}

Event Naming

Auth-O-Tron uses structured event naming following the pattern domain.component.action. Events are grouped by domain, allowing you to filter and analyze related operations.

DomainDescription
authAuthentication flow events
providersProvider lifecycle and validation
augmentersAugmenter lifecycle and enrichment
storeToken store operations
routesHTTP route handler events
startupServer initialization events

Example Configuration

Development (console output, debug level):

logging:
  level: debug
  format: console

Production (JSON output, info level):

logging:
  level: info
  format: json
  service_name: auth-o-tron-prod
  service_version: 1.2.0

Minimal configuration:

logging:
  level: warn

HTTP Endpoints

Auth-O-Tron exposes endpoints on two ports. The main application port (default 8080) handles authentication and token management. The metrics port (default 9090) exposes health checks and Prometheus metrics.

Main Application Port (8080)

GET /authenticate

Primary authentication endpoint. Validates credentials and returns a JWT.

AspectDetails
Request headersAuthorization: Basic <base64> or Authorization: Bearer <token>. Optional: X-Auth-Realm: <realm>
Success (200)Authorization: Bearer <jwt> header in response
Failure (401)WWW-Authenticate challenge header listing available schemes
NotesSupports comma-separated credentials. If multiple credentials share the same scheme, the last one is used.

Example:

curl -H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
  http://localhost:8080/authenticate

GET /token

Creates an opaque token for the authenticated user. Requires the token store to be enabled.

AspectDetails
AuthRequired
Success (200){"token": "<uuid>"}
Failure (503)Token store disabled

GET /tokens

Lists all opaque tokens belonging to the authenticated user.

AspectDetails
AuthRequired
Success (200){"tokens": [...]}
Failure (503)Token store disabled

DELETE /token/

Deletes a specific opaque token.

AspectDetails
AuthRequired
Param{token} - UUID of token to delete
Success (204)No content
Failure (503)Token store disabled

GET /providers

Returns configured authentication providers.

AspectDetails
AuthNone
Success (200){"providers": [{"name": "...", "type": "...", "realm": "..."}]}

GET /augmenters

Returns configured augmenters.

AspectDetails
AuthNone
Success (200){"augmenters": [{"name": "...", "type": "...", "realm": "..."}]}

GET /

Landing page with service info and version.

AspectDetails
AuthNone
Success (200)HTML page with service info

Both Ports (8080 and 9090)

GET /health

Health check for load balancers and monitoring.

AspectDetails
PortsBoth 8080 and 9090
Success (200)Text: OK
UseNGINX checks, K8s probes
NoteAvailable on the metrics port only when metrics.enabled: true

Metrics Port Only (9090)

GET /metrics

Prometheus metrics endpoint.

AspectDetails
Port9090 only
FormatPrometheus text
ContentAuth attempts, latency histograms

Example output:

# HELP auth_requests_total Total authentication requests
# TYPE auth_requests_total counter
auth_requests_total{result="success",realm="internal"} 42

# HELP auth_duration_seconds Authentication latency
# TYPE auth_duration_seconds histogram
auth_duration_seconds_bucket{result="success",realm="internal",le="0.1"} 35

Endpoint Summary

MethodPathPortAuthPurpose
GET/authenticate8080CredentialsMain auth, returns JWT
GET/token8080JWTCreate opaque token
GET/tokens8080JWTList tokens
DELETE/token/{token}8080JWTDelete token
GET/providers8080NoneList providers
GET/augmenters8080NoneList augmenters
GET/healthBothNoneHealth check
GET/metrics9090NonePrometheus metrics
GET/8080NoneService info

Docker

Auth-O-Tron publishes container images to the ECMWF Container Registry.

Image Location

eccr.ecmwf.int/auth-o-tron/auth-o-tron

Image Variants

Two variants are available for different use cases:

VariantDescriptionUse Case
releaseDistroless image with minimal footprintProduction deployments
debugDebian-slim with bash and ca-certificatesDevelopment and debugging

Both variants support multiple architectures: amd64 and arm64.

Running the Container

Mount your configuration file and set the AOT_CONFIG_PATH environment variable:

docker run -d \
  --name auth-o-tron \
  -p 8080:8080 \
  -p 9090:9090 \
  -v $(pwd)/config.yaml:/etc/auth-o-tron/config.yaml \
  -e AOT_CONFIG_PATH=/etc/auth-o-tron/config.yaml \
  eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.3.0

Docker Compose Example

The examples/nginx-auth directory contains a complete setup with Auth-O-Tron, NGINX, and an httpbin backend:

version: "3.8"

services:
  auth-o-tron:
    image: eccr.ecmwf.int/auth-o-tron/auth-o-tron:release
    volumes:
      - ./auth-o-tron/config.yaml:/etc/auth-o-tron/config.yaml:ro
    environment:
      AOT_CONFIG_PATH: /etc/auth-o-tron/config.yaml
    networks:
      - authnet

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - auth-o-tron
      - httpbin
    networks:
      - authnet

  httpbin:
    image: kennethreitz/httpbin
    networks:
      - authnet

networks:
  authnet:

This example demonstrates the auth_request pattern where NGINX delegates authentication to Auth-O-Tron before proxying requests to the protected backend.

Kubernetes & Helm

Auth-O-Tron is designed to run as a containerized workload in Kubernetes. An official Helm chart provides a streamlined installation path.

Helm Chart

The chart is maintained in a separate repository:

https://github.com/ecmwf/auth-o-tron-chart

Installation

Clone the chart repository and install:

git clone https://github.com/ecmwf/auth-o-tron-chart.git
helm install auth-o-tron ./auth-o-tron-chart

Configuration

The following values are commonly customized in values.yaml:

Server Settings

server:
  port: 8080       # Application port
  
metrics:
  port: 9090       # Metrics port (internal only)
  enabled: true

Auth-O-Tron Configuration

The config section is mounted as a ConfigMap and contains the full Auth-O-Tron configuration:

config: |
  server:
    host: 0.0.0.0
    port: 8080
  metrics:
    enabled: true
    port: 9090
  # ... additional config

Service Configuration

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

Monitoring

Enable Prometheus ServiceMonitor for metrics scraping:

metrics:
  enabled: true
  serviceMonitor:
    enabled: true

Secrets via Environment Variables

Use extraEnv to inject sensitive values from Kubernetes secrets:

extraEnv:
  - name: AOT_JWT__SECRET
    valueFrom:
      secretKeyRef:
        name: auth-o-tron-secrets
        key: jwt-secret

Pod Annotations

podAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "9090"

Health Probes

Liveness and readiness probes are configured automatically on the /health endpoint. When metrics.enabled: true, /health is available on both the application and metrics ports. Otherwise it is only on the application port.

Network Architecture

The metrics port is internal to the cluster and should not be exposed via ingress. Configure your ingress to route only to the application port (8080).

Minimal Override Example

server:
  port: 8080

metrics:
  enabled: true
  port: 9090
  serviceMonitor:
    enabled: true

config:
  version: "2.0.0"
  server:
    host: "0.0.0.0"
    port: 8080
  jwt:
    iss: my-org
    exp: 3600
    secret: changeme
  providers:
    - type: plain
      name: default
      realm: default
      users:
        - username: admin
          password: changeme

NGINX Integration

Auth-O-Tron integrates with NGINX using the auth_request module. This pattern delegates authentication decisions to Auth-O-Tron via an internal subrequest, keeping your backend services simple and stateless.

How It Works

sequenceDiagram
    participant Client
    participant NGINX
    participant AOT as Auth-O-Tron
    participant Backend

    Client->>NGINX: GET /api/resource<br/>Authorization: Basic ...
    NGINX->>AOT: Subrequest /_auth<br/>(auth_request)
    
    alt Valid credentials
        AOT-->>NGINX: 200 OK + Authorization: Bearer <jwt>
        NGINX->>Backend: GET /resource<br/>Authorization: Bearer <jwt>
        Backend-->>NGINX: 200 OK + Response
        NGINX-->>Client: 200 OK + Response
    else Invalid credentials
        AOT-->>NGINX: 401 Unauthorized
        NGINX-->>Client: 302 Redirect → /authenticate
    end

Example Configuration

The examples/nginx-auth directory contains a complete working setup. Here is the NGINX configuration with explanations:

events {
    worker_connections 1024;
}

http {
    # Upstream definitions
    upstream authotron {
        server auth-o-tron:8080;
    }

    upstream api_backend {
        server httpbin:80;
    }

    server {
        listen 80;

        # Public endpoint: triggers authentication flow
        location /authenticate {
            proxy_pass http://authotron;
            proxy_set_header Host $host;
        }

        # Internal endpoint: subrequest for auth validation
        location /_auth {
            internal;
            proxy_pass http://authotron;
            proxy_pass_request_body off;
            proxy_set_header Content-Length "";
            proxy_set_header X-Original-URI $request_uri;
            proxy_set_header X-Original-Method $request_method;
        }

        # Named location: redirect on 401 to trigger auth
        location @trigger_auth {
            return 302 /authenticate?redirect=$request_uri;
        }

        # Protected API: requires valid authentication
        location /api/ {
            auth_request /_auth;
            auth_request_set $auth_status $upstream_status;

            # Forward JWT to backend
            auth_request_set $jwt $upstream_http_authorization;
            proxy_set_header Authorization $jwt;

            # Forward legacy headers (if include_legacy_headers is enabled)
            auth_request_set $auth_username $upstream_http_x_auth_username;
            auth_request_set $auth_realm $upstream_http_x_auth_realm;
            auth_request_set $auth_roles $upstream_http_x_auth_roles;
            proxy_set_header X-Auth-Username $auth_username;
            proxy_set_header X-Auth-Realm $auth_realm;
            proxy_set_header X-Auth-Roles $auth_roles;

            proxy_pass http://api_backend;
            proxy_set_header Host $host;

            error_page 401 = @trigger_auth;
        }
    }
}

Configuration Breakdown

Upstream definitions establish the backend services. authotron points to the Auth-O-Tron container, and api_backend is your protected application.

The /authenticate location is included in this example for demonstration and browser-based testing. In production, you likely want to remove it — clients should send credentials directly on the protected endpoints and let the auth_request subrequest handle validation transparently.

The /_auth location is marked internal, meaning only NGINX can access it. The auth_request directive sends a subrequest to Auth-O-Tron to validate the client’s credentials.

The @trigger_auth named location redirects unauthenticated browser requests. This is useful for development but in API-only deployments you may prefer to return 401 directly instead of redirecting.

The /api/ location is protected. The auth_request directive triggers the subrequest to /_auth. On success, the JWT and any legacy headers are captured from the Auth-O-Tron response and forwarded to the backend. On 401, the error page redirects to @trigger_auth.

Headers Forwarded to Backend

HeaderDescription
AuthorizationThe JWT token issued by Auth-O-Tron
X-Auth-UsernameAuthenticated username (requires include_legacy_headers: true)
X-Auth-RealmAuthentication realm (requires include_legacy_headers: true)
X-Auth-RolesComma-separated roles (requires include_legacy_headers: true)

Testing the Setup

Unauthenticated requests are redirected to the login flow:

curl -i http://localhost/api/get
# HTTP/1.1 302 Found
# Location: /authenticate?redirect=/api/get

Authenticated requests with valid Basic credentials succeed:

curl -i -H "Authorization: Basic dGVzdF91c2VyOnNlY3JldDEyMw==" http://localhost/api/get
# HTTP/1.1 200 OK

The base64 string dGVzdF91c2VyOnNlY3JldDEyMw== decodes to test_user:secret123.

Environment Variables

Auth-O-Tron requires a YAML configuration file, but any value in it can be overridden through environment variables. This makes it easy to customize deployments in containers and cloud environments without modifying the base config file.

Core Variables

VariableDescriptionDefault
AOT_CONFIG_PATHPath to the YAML configuration file./config.yaml

Configuration Override Convention

All configuration values from the YAML file can be overridden via environment variables using the AOT_ prefix and double underscores to represent nesting.

The pattern is: AOT_<SECTION>__<KEY>

For nested sections, add more double underscores: AOT_<SECTION>__<SUBSECTION>__<KEY>

Common Overrides

VariableConfig PathExample Value
AOT_SERVER__HOSTserver.host0.0.0.0
AOT_SERVER__PORTserver.port8080
AOT_METRICS__ENABLEDmetrics.enabledtrue
AOT_METRICS__PORTmetrics.port9090
AOT_JWT__ISSjwt.issmy-issuer
AOT_JWT__SECRETjwt.secretmy-secret
AOT_JWT__EXPjwt.exp3600
AOT_LOGGING__LEVELlogging.levelinfo
AOT_LOGGING__FORMATlogging.formatjson
AOT_AUTH__TIMEOUT_IN_MSauth.timeout_in_ms5000
AOT_STORE__ENABLEDstore.enabledfalse

Precedence

Environment variables take precedence over YAML configuration values. The loading order is:

  1. Load YAML configuration from AOT_CONFIG_PATH
  2. Merge environment variable overrides
  3. Deserialize into the versioned config (v1 configs are converted to v2)
  4. Fail fast on parse errors or invalid values (e.g., port collision)

This means you can maintain a base configuration file and selectively override specific values per environment using environment variables.

Example

export AOT_CONFIG_PATH=/etc/auth-o-tron/config.yaml
export AOT_SERVER__PORT=8080
export AOT_JWT__SECRET=$(cat /run/secrets/jwt_secret)
export AOT_METRICS__ENABLED=true

./authotron

Metrics

Auth-O-Tron exposes Prometheus-compatible metrics on a dedicated port for monitoring and observability, following the ECMWF Codex Observability guidelines.

Endpoint

Metrics are served at:

GET /metrics

By default, this endpoint is available on port 9090. The format follows the Prometheus text exposition format.

Metric Families

Metric NameTypeLabelsDescription
auth_requests_totalCounterresult, realmTotal authentication requests
auth_duration_secondsHistogramresult, realmAuthentication latency distribution
auth_provider_attempts_totalCounterprovider_name, provider_type, realm, resultAttempts per authentication provider
auth_provider_duration_secondsHistogramprovider_name, provider_type, realmProvider-specific latency
augmenter_attempts_totalCounteraugmenter_name, augmenter_type, realm, resultToken augmentation attempts
augmenter_duration_secondsHistogramaugmenter_type, realmAugmentation latency

Label Values

result (auth_requests_total): success, no_auth_header, invalid_header, all_failed

result (auth_provider_attempts_total): success, error, timeout

result (augmenter_attempts_total): success, error

realm: The configured authentication realm name, or unknown when the request fails before realm resolution

provider_name: Identifier for the authentication provider

provider_type: Type of provider (plain, jwt, ecmwf-api, efas-api, openid-offline, ecmwf-token-generator)

augmenter_name: Identifier for the token augmenter

augmenter_type: Type of augmenter

PromQL Examples

Request Rate

rate(auth_requests_total[5m])

Authentication Latency (99th Percentile)

histogram_quantile(0.99, rate(auth_duration_seconds_bucket[5m]))

Error Rate by Realm

rate(auth_requests_total{result!="success"}[5m])

Slow Providers (95th Percentile)

histogram_quantile(0.95, rate(auth_provider_duration_seconds_bucket[5m]))

Provider Success Rate

rate(auth_provider_attempts_total{result="success"}[5m])

Alerting Recommendations

Consider alerting on:

  • High error rates: rate(auth_requests_total{result!="success"}[5m]) > 0.1
  • Elevated latency: histogram_quantile(0.95, rate(auth_duration_seconds_bucket[5m])) > 1.0
  • Provider failures: rate(auth_provider_attempts_total{result=~"error|timeout"}[5m]) > 0