How JWT Authentication Works Between OP Stack Services in Base

Base uses a shared 256-bit JWT secret injected via environment variables to authenticate communication between the OP Node and execution clients like Geth, Reth, and Nethermind.

The base/node repository implements JWT authentication to secure the engine API communication between the L2 rollup node and underlying execution clients. This mechanism ensures that only authorized clients can interact with the rollup's state-transition engine, preventing unauthorized access to the critical consensus layer. The implementation relies on a pre-shared secret passed through environment variables rather than hardcoded credentials or TLS certificates.

Shared Secret Generation and Configuration

Environment Variable Setup

Authentication begins with a 256-bit secret generated beforehand and stored as a raw hex string. The repository defines this in environment files like .env.sepolia and .env.mainnet using the variable OP_NODE_L2_ENGINE_AUTH_RAW. The destination file path is specified by OP_NODE_L2_ENGINE_AUTH, which defaults to /tmp/engine-auth-jwt if not explicitly set.


# From .env.sepolia

OP_NODE_L2_ENGINE_AUTH_RAW=688f5d737bad920bdfb2fc2f488d6b6209eebda1dae949a8de91398d932c517a
OP_NODE_L2_ENGINE_AUTH=/tmp/engine-auth-jwt

Writing the Secret to Disk

Each service's entry-point script writes this raw value to the temporary file before launching the client binary. This ensures the secret remains outside the container image and is injected at runtime.

echo "$OP_NODE_L2_ENGINE_AUTH_RAW" > "$OP_NODE_L2_ENGINE_AUTH"

Source: op-node-entrypoint writes the secret at lines 45-47, while execution clients perform similar operations in their respective entry-points.

Execution Client Configuration

Each supported execution client reads the JWT secret from the shared file path and uses it to sign requests to the OP Node's engine API.

Geth Configuration

In geth/geth-entrypoint, the client launches with the --authrpc.jwtsecret flag pointing to the environment-defined path:

geth \
  --authrpc.jwtsecret="$OP_NODE_L2_ENGINE_AUTH" \
  # additional configuration flags

Reference: Lines 31-65 in geth/geth-entrypoint handle the secret writing and Geth initialization.

Reth Configuration

The Reth client follows an identical pattern in reth/reth-entrypoint, using the same flag name as Geth:

reth \
  --authrpc.jwtsecret="$OP_NODE_L2_ENGINE_AUTH" \
  # additional configuration flags

Reference: Lines 111-130 in reth/reth-entrypoint implement this configuration.

Nethermind Configuration

Nethermind uses a different flag name but the same file path. In nethermind/nethermind-entrypoint, the configuration uses:

nethermind \
  --JsonRpc.JwtSecretFile="$OP_NODE_L2_ENGINE_AUTH" \
  # additional configuration flags

Reference: Lines 26-55 in nethermind/nethermind-entrypoint handle the JWT setup for this client.

OP Node Verification

The OP Node binary reads the same secret file to validate incoming requests. The op-node-entrypoint script writes the secret to disk before executing the binary:

echo "$OP_NODE_L2_ENGINE_AUTH_RAW" > "$OP_NODE_L2_ENGINE_AUTH"
exec ./op-node

When the OP Node receives engine API calls from execution clients, it verifies the JWT signature against this shared secret. Any request lacking a valid signature or using a mismatched secret is rejected, ensuring only the authenticated execution client can trigger state transitions.

Authentication Flow

The JWT authentication between OP Stack services follows this sequence:

  1. Secret distribution – All services read the identical 256-bit hex value from OP_NODE_L2_ENGINE_AUTH_RAW
  2. File initialization – Each entry-point writes the secret to /tmp/engine-auth-jwt (or the configured path)
  3. Request signing – Execution clients create JWTs signed with the secret when calling the engine API
  4. Signature validation – The OP Node verifies tokens using the same secret, accepting only authenticated requests

This design provides mutual authentication without requiring TLS client certificates or network-level encryption between the components. The secret remains ephemeral, existing only in memory and temporary files during the container lifecycle.

Summary

  • Shared secret architecture – Base uses a single 256-bit JWT secret shared between the OP Node and all execution clients (Geth, Reth, Nethermind)
  • Environment-driven configuration – The secret is injected via OP_NODE_L2_ENGINE_AUTH_RAW and written to a temporary file defined by OP_NODE_L2_ENGINE_AUTH
  • Client-specific flags – Geth and Reth use --authrpc.jwtsecret, while Nethermind uses --JsonRpc.JwtSecretFile to reference the secret file
  • Runtime security – Secrets are never baked into container images; they are mounted at runtime via environment variables and entry-point scripts
  • Engine API protection – All engine API communication requires valid JWT signatures, preventing unauthorized state manipulation

Frequently Asked Questions

What is the purpose of JWT authentication in the OP Stack?

JWT authentication secures the engine API endpoints that allow execution clients to interact with the consensus layer. According to the base/node source code, this prevents unauthorized clients from submitting execution payloads or fork choice updates to the OP Node, protecting the integrity of the L2 chain's state transitions.

How is the JWT secret shared between services?

The secret is distributed via the OP_NODE_L2_ENGINE_AUTH_RAW environment variable, which all services read during startup. Each entry-point script (geth-entrypoint, reth-entrypoint, nethermind-entrypoint, and op-node-entrypoint) writes this value to the same file path before launching the respective binary, ensuring all components share the identical authentication key.

Where is the JWT secret stored in Base's configuration?

The secret is defined in environment files (.env.sepolia, .env.mainnet) and mounted as the OP_NODE_L2_ENGINE_AUTH_RAW variable. It is written to a temporary file at /tmp/engine-auth-jwt (or the path specified by OP_NODE_L2_ENGINE_AUTH) during container initialization. The repository contains no hardcoded secrets; operators must generate and provide their own 256-bit hex values.

Do all execution clients use the same JWT configuration format?

While all clients read from the same file path, they use different CLI flags. Geth and Reth both use --authrpc.jwtsecret, but Nethermind requires --JsonRpc.JwtSecretFile. Despite these syntactic differences in base/node, all implementations achieve the same cryptographic result: signing requests with the HMAC-SHA256 algorithm using the shared 256-bit secret.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →