How to Add Custom Connection Hooks in Iroh for Authentication
You can add custom authentication to Iroh connections by implementing the EndpointHooks trait and registering it with Endpoint::builder().hooks(my_hook), allowing you to inspect and reject connections during the before_connect and after_handshake phases.
The n0-computer/iroh networking library provides a pluggable hook system that lets you intercept QUIC connections before and after the cryptographic handshake completes. By implementing custom connection hooks in Iroh for authentication, you can validate remote endpoint identities, inspect ALPN protocols, and abort unauthorized connections without modifying the core library.
Understanding Iroh's Connection Hook Architecture
Iroh's endpoint architecture stores hooks in an EndpointHooksList and executes them sequentially during connection establishment. The hook system is defined in iroh/src/endpoint/hooks.rs and integrated into the builder pattern in iroh/src/endpoint.rs.
Hook Registration and Execution Order
Hooks attach to the endpoint via the builder method Endpoint::builder().hooks(my_hook). The system stores these instances in an EndpointHooksList and invokes them in registration order. If any hook returns Reject, the connection aborts immediately and subsequent hooks are skipped.
Hook Lifecycle Callbacks
The EndpointHooks trait defines two async callbacks that execute at specific connection lifecycle points:
before_connect(&self, remote_addr: &SocketAddr, alpn: &str) -> BeforeConnectOutcome– Invoked iniroh/src/endpoint/connection.rsbefore the QUIC handshake starts. Use this to filter by IP address or ALPN protocol.after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome– Invoked after the handshake completes. This provides access to the remote endpoint ID viaconn.remote_id()and aWeakConnectionHandlethat can be upgraded to interact with the connection.
Both return enums with Accept and Reject variants. Returning Reject aborts the connection and prevents later hooks from running.
Implementing a Custom Authentication Hook
To create a custom authentication hook, define a struct to hold your validation state and implement the EndpointHooks trait.
Define the Hook Struct
Store your authentication credentials or validator client in the struct:
use iroh::endpoint::{EndpointHooks, BeforeConnectOutcome, AfterHandshakeOutcome, Connection};
use std::net::SocketAddr;
struct TokenAuthHook {
expected_token: Vec<u8>,
}
Implement the EndpointHooks Trait
Use #[async_trait::async_trait] to implement the required methods. The after_handshake method receives a &Connection containing the authenticated remote endpoint identity:
#[async_trait::async_trait]
impl EndpointHooks for TokenAuthHook {
async fn before_connect(
&self,
_remote_addr: &SocketAddr,
_alpn: &str,
) -> BeforeConnectOutcome {
// Optional: Pre-filter by IP or ALPN before cryptographic handshake
BeforeConnectOutcome::Accept
}
async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome {
let remote_id = conn.remote_id();
// Authentication logic: Open a QUIC stream to exchange tokens
// or validate against pre-shared keys. Here we illustrate validation.
if validate_peer(remote_id, &self.expected_token) {
AfterHandshakeOutcome::Accept
} else {
AfterHandshakeOutcome::Reject
}
}
}
Register the Hook with the Endpoint Builder
Install your hook when constructing the endpoint in iroh/src/endpoint.rs:
let auth_hook = TokenAuthHook { expected_token: b"secret-token".to_vec() };
let endpoint = iroh::Endpoint::builder()
.bind_addr("[::]:0".parse().unwrap())
.hooks(auth_hook)
.build()
.await?;
Now every incoming connection passes through TokenAuthHook. If the hook returns Reject, the connection closes immediately and the remote side receives a handshake abort error.
Client-Side Authentication Hooks
For mutual authentication, implement symmetrical hooks on both client and server. The repository provides a reference implementation in iroh/examples/auth-hook.rs demonstrating this pattern with auth::outgoing and auth::incoming helpers that return (hook, task) tuples. The outgoing hook sends authentication tokens once the connection establishes, while the incoming hook validates them using the after_handshake callback.
Source Code Reference
Key files in the n0-computer/iroh repository for custom authentication:
iroh/src/endpoint/hooks.rs– Defines theEndpointHookstrait and outcome enums (BeforeConnectOutcome,AfterHandshakeOutcome).iroh/src/endpoint.rs– Contains theBuilder::hooksmethod andEndpointHooksListstorage.iroh/src/endpoint/connection.rs– Implements the invocation ofbefore_connectandafter_handshakeat the correct connection lifecycle points.iroh/examples/auth-hook.rs– Complete runnable example of outgoing and incoming authentication hooks.iroh/examples/remote-info.rs– Demonstrates maintaining per-connection state within hooks.iroh/examples/monitor-connections.rs– Shows diagnostic logging using the hook system.
Summary
- Register hooks using
Endpoint::builder().hooks(my_hook)before building the endpoint. - Implement
before_connectto filter connections early by IP address or ALPN protocol. - Implement
after_handshaketo access the authenticatedremote_idand decide whether toAcceptorReject. - Return
Rejectfrom any hook to immediately abort the connection and prevent further processing. - Combine client and server hooks for mutual authentication schemes.
Frequently Asked Questions
What is the difference between before_connect and after_handshake hooks?
The before_connect hook runs before the QUIC handshake begins and only has access to the remote socket address and ALPN string. The after_handshake hook runs after cryptographic verification completes and provides access to the Connection object, including the authenticated remote endpoint ID via conn.remote_id().
Can I access connection data inside an authentication hook?
Yes. Inside after_handshake, you receive a &Connection parameter that exposes conn.remote_id() and provides a WeakConnectionHandle that can be upgraded to a ConnectionHandle. This allows you to inspect peer identity and open streams for token exchange, though the actual transport of authentication data is implementation-specific.
How do I reject a connection in Iroh?
Return BeforeConnectOutcome::Reject or AfterHandshakeOutcome::Reject from the respective hook method. This immediately aborts the connection and prevents subsequent hooks in the EndpointHooksList from executing, as implemented in iroh/src/endpoint/connection.rs.
Where can I find a complete example of custom authentication?
The repository includes a full implementation in iroh/examples/auth-hook.rs, which demonstrates both outgoing and incoming hook patterns with token validation logic. Additional examples in iroh/examples/remote-info.rs and iroh/examples/monitor-connections.rs show how to maintain state and log connections using the same hook architecture.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →