Introduction
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
expif 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. attributesare normalized toHashMap<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_addressfield for the server. - version “2.0.0”: Current format with separate
serverandmetricssections 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:
- YAML file: Base configuration values.
- 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.
| Field | Type | Default | Description |
|---|---|---|---|
| host | string | “0.0.0.0” | The network interface to bind to |
| port | integer | required | The 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.
| Field | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Whether to start the metrics server |
| port | integer | 9090 | The 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:
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | The provider type (determines which provider is instantiated) |
| name | string | yes | A unique identifier for this provider instance |
| realm | string | yes | The 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:
| Field | Type | Description |
|---|---|---|
| users | array | List of user objects with username, password, and roles |
Each user object has:
username: The login namepassword: 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:
| Field | Type | Description |
|---|---|---|
| cert_uri | string | URL to the JWKS endpoint |
| iam_realm | string | The 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:
| Field | Type | Description |
|---|---|---|
| uri | string | Base 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:
| Field | Type | Description |
|---|---|---|
| uri | string | Base 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:
| Field | Type | Description |
|---|---|---|
| cert_uri | string | URL to the JWKS endpoint |
| public_client_id | string | Client ID for public token introspection |
| private_client_id | string | Client ID for private token exchange |
| private_client_secret | string | Secret for the private client |
| iam_url | string | Base 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:
| Field | Type | Description |
|---|---|---|
| cert_uri | string | URL to the JWKS endpoint |
| client_id | string | OAuth client ID |
| client_secret | string | OAuth client secret |
| token_generator_url | string | URL 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:
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | The augmenter type |
| name | string | yes | A unique identifier for this augmenter |
| realm | string | yes | Only 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:
| Field | Type | Description |
|---|---|---|
| roles | map | Map 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:
| Field | Type | Description |
|---|---|---|
| match | object | Conditions that must be met for augmentation to apply |
| augment | object | Roles and attributes to add when conditions match |
Match conditions:
| Field | Type | Description |
|---|---|---|
| username | array | List of usernames that trigger this augmenter |
| role | array | List 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:
| Field | Type | Description |
|---|---|---|
| roles | array | Roles to add to the user |
| attributes | map | Key-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:
| Field | Type | Description |
|---|---|---|
| uri | string | LDAP server URI (e.g., ldap://ldap.example.com:389) |
| search_base | string | Base DN for searches (e.g., ou=users,dc=example,dc=com) |
| filter | string | Single LDAP filter template (optional, uses {username} placeholder) |
| filters | array | Multiple LDAP filter templates (optional, alternative to single filter) |
| bind_dn | string | DN to bind with for searching (service account) |
| ldap_password | string | Password for the bind DN |
Filter vs Filters:
- Use
filterfor a single search that returns group CNs directly. - Use
filtersfor 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:
| Field | Type | Required | Description |
|---|---|---|---|
| type | string | yes | Must be “mongo” |
| uri | string | yes | MongoDB connection URI |
| database | string | yes | Database 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 onuser_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
| Field | Type | Required | Description |
|---|---|---|---|
| iss | string | yes | The issuer claim, identifies who issued the token |
| aud | string | no | Reserved for future use (not currently included in issued JWTs) |
| exp | integer | yes | Token expiration time in seconds |
| secret | string | yes | The 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:
| Claim | Description |
|---|---|
| sub | Subject, formatted as “{realm}-{username}” |
| iss | Issuer, from the iss config field |
| exp | Expiration time, calculated as min(config_exp, user_attribute_exp) |
| iat | Issued at timestamp |
| roles | Array of role strings from the user |
| username | The authenticated username |
| realm | The authentication realm |
| scopes | Array of scope strings (if any) |
| attributes | Map of additional user attributes |
The exp claim uses the minimum of:
- The configured expiration time
- Any
expattribute 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
audclaim 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
| Field | Type | Default | Description |
|---|---|---|---|
| level | string | info | Minimum log level to output |
| format | string | console | Output format: “json” or “console” |
| service_name | string | authotron | Service identifier in logs |
| service_version | string | (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.
| Level | Description |
|---|---|
| trace | Very detailed internal state information |
| debug | Information useful for debugging |
| info | General operational information |
| warn | Warning conditions that are not errors |
| error | Error 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:
| Field | Description |
|---|---|
| severityText | Log level as text (INFO, ERROR, etc.) |
| severityNumber | Numeric log level code |
| body | The log message |
| timestamp | ISO 8601 timestamp |
| resource | Resource attributes including service name and version |
| attributes | Additional 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.
| Domain | Description |
|---|---|
| auth | Authentication flow events |
| providers | Provider lifecycle and validation |
| augmenters | Augmenter lifecycle and enrichment |
| store | Token store operations |
| routes | HTTP route handler events |
| startup | Server 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.
| Aspect | Details |
|---|---|
| Request headers | Authorization: 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 |
| Notes | Supports 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.
| Aspect | Details |
|---|---|
| Auth | Required |
| Success (200) | {"token": "<uuid>"} |
| Failure (503) | Token store disabled |
GET /tokens
Lists all opaque tokens belonging to the authenticated user.
| Aspect | Details |
|---|---|
| Auth | Required |
| Success (200) | {"tokens": [...]} |
| Failure (503) | Token store disabled |
DELETE /token/
Deletes a specific opaque token.
| Aspect | Details |
|---|---|
| Auth | Required |
| Param | {token} - UUID of token to delete |
| Success (204) | No content |
| Failure (503) | Token store disabled |
GET /providers
Returns configured authentication providers.
| Aspect | Details |
|---|---|
| Auth | None |
| Success (200) | {"providers": [{"name": "...", "type": "...", "realm": "..."}]} |
GET /augmenters
Returns configured augmenters.
| Aspect | Details |
|---|---|
| Auth | None |
| Success (200) | {"augmenters": [{"name": "...", "type": "...", "realm": "..."}]} |
GET /
Landing page with service info and version.
| Aspect | Details |
|---|---|
| Auth | None |
| Success (200) | HTML page with service info |
Both Ports (8080 and 9090)
GET /health
Health check for load balancers and monitoring.
| Aspect | Details |
|---|---|
| Ports | Both 8080 and 9090 |
| Success (200) | Text: OK |
| Use | NGINX checks, K8s probes |
| Note | Available on the metrics port only when metrics.enabled: true |
Metrics Port Only (9090)
GET /metrics
Prometheus metrics endpoint.
| Aspect | Details |
|---|---|
| Port | 9090 only |
| Format | Prometheus text |
| Content | Auth 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
| Method | Path | Port | Auth | Purpose |
|---|---|---|---|---|
| GET | /authenticate | 8080 | Credentials | Main auth, returns JWT |
| GET | /token | 8080 | JWT | Create opaque token |
| GET | /tokens | 8080 | JWT | List tokens |
| DELETE | /token/{token} | 8080 | JWT | Delete token |
| GET | /providers | 8080 | None | List providers |
| GET | /augmenters | 8080 | None | List augmenters |
| GET | /health | Both | None | Health check |
| GET | /metrics | 9090 | None | Prometheus metrics |
| GET | / | 8080 | None | Service 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:
| Variant | Description | Use Case |
|---|---|---|
| release | Distroless image with minimal footprint | Production deployments |
| debug | Debian-slim with bash and ca-certificates | Development 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
| Header | Description |
|---|---|
| Authorization | The JWT token issued by Auth-O-Tron |
| X-Auth-Username | Authenticated username (requires include_legacy_headers: true) |
| X-Auth-Realm | Authentication realm (requires include_legacy_headers: true) |
| X-Auth-Roles | Comma-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
| Variable | Description | Default |
|---|---|---|
AOT_CONFIG_PATH | Path 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
| Variable | Config Path | Example Value |
|---|---|---|
AOT_SERVER__HOST | server.host | 0.0.0.0 |
AOT_SERVER__PORT | server.port | 8080 |
AOT_METRICS__ENABLED | metrics.enabled | true |
AOT_METRICS__PORT | metrics.port | 9090 |
AOT_JWT__ISS | jwt.iss | my-issuer |
AOT_JWT__SECRET | jwt.secret | my-secret |
AOT_JWT__EXP | jwt.exp | 3600 |
AOT_LOGGING__LEVEL | logging.level | info |
AOT_LOGGING__FORMAT | logging.format | json |
AOT_AUTH__TIMEOUT_IN_MS | auth.timeout_in_ms | 5000 |
AOT_STORE__ENABLED | store.enabled | false |
Precedence
Environment variables take precedence over YAML configuration values. The loading order is:
- Load YAML configuration from
AOT_CONFIG_PATH - Merge environment variable overrides
- Deserialize into the versioned config (v1 configs are converted to v2)
- 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 Name | Type | Labels | Description |
|---|---|---|---|
auth_requests_total | Counter | result, realm | Total authentication requests |
auth_duration_seconds | Histogram | result, realm | Authentication latency distribution |
auth_provider_attempts_total | Counter | provider_name, provider_type, realm, result | Attempts per authentication provider |
auth_provider_duration_seconds | Histogram | provider_name, provider_type, realm | Provider-specific latency |
augmenter_attempts_total | Counter | augmenter_name, augmenter_type, realm, result | Token augmentation attempts |
augmenter_duration_seconds | Histogram | augmenter_type, realm | Augmentation 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