How iroh Connection Hooks Work for Authentication and Authorization

iroh connection hooks let you intercept and control connection attempts by implementing the EndpointHooks trait, which provides before_connect and after_handshake callbacks to reject unauthorized peers before or after the QUIC/TLS handshake completes.

The n0-computer/iroh library exposes a hook-based interception system that allows applications to enforce authentication and authorization policies at the network layer. By implementing the EndpointHooks trait and registering hooks via the endpoint builder, you can inspect and abort connection attempts based on custom logic. This mechanism operates on verified identity data from the underlying QUIC/TLS security layer.

The EndpointHooks Trait Lifecycle

Hooks in iroh are stored in an EndpointHooksList maintained by the endpoint builder. When you install hooks using Builder::hooks, they are invoked sequentially at specific connection lifecycle events. If any hook returns a Reject outcome, the remaining hooks are skipped and the connection terminates immediately.

The EndpointHooks trait is defined in iroh/src/endpoint/hooks.rs and provides two primary async callbacks:

Intercepting Outgoing Connections with before_connect

The before_connect callback runs immediately before an outgoing connection attempt starts, triggered by Endpoint::connect or connect_with_opts. This hook receives the connection options and can return BeforeConnectOutcome::Reject to abort the attempt before any packets are transmitted.

This is useful for client-side rate limiting or preventing connections to blacklisted endpoints before network resources are consumed.

Validating Established Connections with after_handshake

The after_handshake callback executes after the QUIC/TLS handshake completes for both inbound and outbound connections. At this point, the remote peer's identity (EndpointId) has been cryptographically verified by the underlying QUIC layer.

The hook can return AfterHandshakeOutcome::Reject { error_code, reason } to close the connection immediately with a specific error code. This is the primary mechanism for authorization, as it allows you to make decisions based on a trustworthy peer identifier.

Building an Authentication System with Hooks

The iroh/examples/auth-hook.rs file demonstrates a complete pre-authentication flow using connection hooks. This pattern separates the authentication handshake (verifying a shared secret) from subsequent authorized connections.

Server-Side Validation with IncomingAuthHook

The IncomingAuthHook struct implements after_handshake to enforce access control. It maintains an allowed_remotes set containing authenticated EndpointId values.

When a new connection arrives:

  1. The hook checks if the remote's EndpointId exists in allowed_remotes
  2. If present, it returns Accept and the connection proceeds
  3. If absent, it returns AfterHandshakeOutcome::Reject with error code 403 and reason "Unauthorized", causing immediate termination

Because this runs after the TLS handshake, the EndpointId is cryptographically verified and cannot be spoofed.

The Authentication Protocol Handler

AuthProtocol is a custom protocol mounted on the router that handles the initial credential exchange. When a peer connects using a dedicated ALPN:

  1. The protocol handler reads a shared secret token from a uni-directional stream
  2. If the token matches the expected value, the remote's EndpointId is inserted into allowed_remotes
  3. The connection is closed with status code CLOSE_ACCEPTED
  4. If the token is invalid, the connection closes with CLOSE_DENIED

This separation allows the authentication logic to run as a distinct protocol while the connection hooks enforce the policy for all other traffic.

Client-Side Authentication Flow

The outgoing side uses a helper function that implements the token submission:

  1. Opens a uni-directional stream to the remote's authentication endpoint
  2. Writes the shared secret token
  3. Waits for the remote to close the connection with CLOSE_ACCEPTED
  4. Only then proceeds to open normal connections

Once this completes, the server has added the client's EndpointId to allowed_remotes, so subsequent connections pass the IncomingAuthHook check.

Complete Implementation Example

The following example shows how to wire together the authentication hook and protocol:

use iroh::endpoint::{Builder, EndpointHooks};
use iroh::example::auth_hook::{incoming, outgoing};

// Server setup
let (auth_hook, auth_proto) = incoming(b"shared-secret".to_vec());

let endpoint = Builder::default()
    .hooks(auth_hook)          // Install the IncomingAuthHook
    .router(auth_proto)        // Register the AuthProtocol handler
    .bind()
    .await?;

// Client setup
let (client_hook, token) = outgoing(b"shared-secret".to_vec());
let client = Builder::default()
    .hooks(client_hook)        // Install hook to send auth token
    .bind()
    .await?;

// After successful auth, this connection will be accepted
let conn = client.connect(endpoint.remote_id(), ALPN).await?;

This mirrors the implementation in iroh/examples/auth-hook.rs, demonstrating the complete cycle from token exchange to authorized connection.

Key Source Files and Implementation Details

The connection hook system is implemented across several core files:

  • iroh/src/endpoint/hooks.rs – Defines the EndpointHooks trait, BeforeConnectOutcome and AfterHandshakeOutcome enums, and the hook dispatch logic that handles early termination when rejection occurs.
  • iroh/examples/auth-hook.rs – Complete working example of IncomingAuthHook, AuthProtocol, and the outgoing authentication helper.
  • iroh/src/endpoint/builder.rs – Contains the Builder::hooks method used to register EndpointHooksList with the endpoint.
  • iroh/src/endpoint/router.rs – Handles protocol registration and routing of inbound streams to the appropriate protocol handlers like AuthProtocol.

Summary

  • iroh connection hooks implement the EndpointHooks trait to intercept connections at before_connect or after_handshake phases.
  • The before_connect hook can abort outgoing connections before any packets are sent by returning BeforeConnectOutcome::Reject.
  • The after_handshake hook runs after cryptographic verification, providing a trustworthy EndpointId for authorization decisions.
  • Authentication is typically implemented by pairing an after_handshake hook (for enforcement) with a custom protocol handler (for credential exchange).
  • Hooks are registered via the endpoint builder and stored in an EndpointHooksList that processes them sequentially until completion or first rejection.

Frequently Asked Questions

When do iroh connection hooks execute relative to the QUIC handshake?

The before_connect hook executes before any network packets are sent for outgoing connections, while after_handshake runs immediately after the QUIC/TLS handshake completes. This means after_handshake has access to the cryptographically verified EndpointId of the remote peer, ensuring authorization decisions are based on authentic identity data.

How do I reject a connection in a hook?

Return BeforeConnectOutcome::Reject from before_connect to stop outgoing connections before they start, or return AfterHandshakeOutcome::Reject { error_code, reason } from after_handshake to close the connection immediately after handshake completion. When any hook returns a rejection variant, the remaining hooks are skipped and the connection terminates.

Can I implement rate limiting with connection hooks?

Yes. The before_connect hook is ideal for client-side rate limiting because it runs before network resources are allocated. You can track connection attempts per destination and return BeforeConnectOutcome::Reject to prevent new connections to specific endpoints that have exceeded limits.

What identity information is available in the after_handshake hook?

The after_handshake hook receives the EndpointId of the remote peer, which has been verified during the TLS handshake. This identifier is cryptographically bound to the connection and cannot be spoofed, making it reliable for authorization checks against allowlists or authentication databases.

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 →