How to Use Iroh Connection Hooks for Custom Authentication
Implement the EndpointHooks trait and register your hook via Endpoint::builder().hooks() to intercept connections before the QUIC handshake (before_connect) or after identity verification (after_handshake), returning Reject to abort unauthorized sessions.
The n0-computer/iroh framework provides a pluggable hook system that lets you inspect and control the connection lifecycle. By leveraging iroh connection hooks for custom authentication, you can embed token validation, secret verification, or external credential checks directly into the QUIC handshake flow without modifying core library code.
How Connection Hooks Work in Iroh
Iroh’s endpoint architecture executes registered hooks at specific lifecycle points. Hooks are stored in an EndpointHooksList and invoked sequentially, allowing you to build defense-in-depth authentication strategies.
Hook Registration and Execution Order
Attach hooks during Endpoint construction using the builder method. Hooks execute in the order they were added, and any hook can abort the connection by returning a Reject variant, preventing subsequent hooks from running.
Key Hook Points
The EndpointHooks trait in iroh/src/endpoint/hooks.rs defines two async callbacks:
before_connect(&self, remote_addr: &SocketAddr, alpn: &str) -> BeforeConnectOutcome– Invoked before the QUIC handshake starts. Use this to filter by IP address or ALPN protocol early.after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome– Invoked after the handshake completes and the remote endpoint’s identity is cryptographically verified. This provides access to the authenticatedConnectionobject for validation.
Both methods return Accept or Reject outcomes defined in BeforeConnectOutcome and AfterHandshakeOutcome enums.
Implementing a Custom Authentication Hook
To build custom authentication, define a struct holding your validation state and implement the EndpointHooks trait.
Define the Hook Structure
Store any state required for validation, such as a shared secret, JWT validator, or database client.
use iroh::endpoint::{EndpointHooks, BeforeConnectOutcome, AfterHandshakeOutcome, Connection};
struct MyAuthHook {
secret: Vec<u8>,
}
Implement the EndpointHooks Trait
Provide async implementations for both hook points. The after_handshake method receives a &Connection containing the remote endpoint ID via conn.remote_id(), which you can use to validate tokens sent over a dedicated QUIC stream.
use async_trait::async_trait;
use std::net::SocketAddr;
#[async_trait]
impl EndpointHooks for MyAuthHook {
async fn before_connect(
&self,
_remote_addr: &SocketAddr,
_alpn: &str,
) -> BeforeConnectOutcome {
// Optionally reject by IP or ALPN before crypto overhead
BeforeConnectOutcome::Accept
}
async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome {
let remote_id = conn.remote_id();
// Tokens are typically sent via a dedicated QUIC stream after handshake
let token = match receive_auth_token(conn).await {
Ok(t) => t,
Err(_) => return AfterHandshakeOutcome::Reject,
};
if token == self.secret {
AfterHandshakeOutcome::Accept
} else {
AfterHandshakeOutcome::Reject
}
}
}
async fn receive_auth_token(conn: &Connection) -> anyhow::Result<Vec<u8>> {
// Open a stream or accept an incoming stream to exchange tokens
// Implementation depends on your ALPN protocol
todo!()
}
Note: Iroh does not prescribe how authentication tokens are transported. Typically, you open a dedicated QUIC stream after the handshake completes and exchange a small payload before the hook returns Accept.
Installing the Hook on the Server Side
Register your hook during endpoint instantiation. Every incoming connection will flow through your authentication logic.
let secret = b"my-shared-secret".to_vec();
let auth_hook = MyAuthHook { secret };
let endpoint = iroh::Endpoint::builder()
.bind_addr("[::]:0".parse().unwrap())
.hooks(auth_hook) // Install the custom auth hook
.build()
.await?;
If the hook returns Reject, the connection closes immediately and the remote peer receives a handshake abort error.
Client-Side Authentication Patterns
For mutual authentication, create a paired hook system:
- Outgoing hook: Sends the authentication token once the connection is established.
- Incoming hook: Validates the token as shown above.
The repository’s iroh/examples/auth-hook.rs demonstrates this pattern with helper functions auth::outgoing and auth::incoming that return (hook, task) tuples. You can adapt these helpers to use JWTs, API keys, or TLS-derived secrets.
Source Code Reference
The following files in the n0-computer/iroh repository contain the relevant implementations:
| Feature | File | Purpose |
|---|---|---|
| Hook trait definition | iroh/src/endpoint/hooks.rs |
Defines EndpointHooks, BeforeConnectOutcome, and AfterHandshakeOutcome |
| Builder integration | iroh/src/endpoint.rs |
Contains Builder::hooks method and EndpointHooksList storage |
| Connection handling | iroh/src/endpoint/connection.rs |
Invokes before_connect and after_handshake at lifecycle points |
| Authentication example | iroh/examples/auth-hook.rs |
Full runnable demo of outgoing and incoming auth hooks |
| Per-connection state | iroh/examples/remote-info.rs |
Shows how to maintain state across hook invocations |
| Connection monitoring | iroh/examples/monitor-connections.rs |
Demonstrates diagnostic hooks for logging connection details |
Summary
- Register custom logic by implementing
EndpointHooksand passing it toEndpoint::builder().hooks(). - Intercept early using
before_connectto filter by IP or ALPN before cryptographic overhead. - Validate identity in
after_handshakewhere the remote endpoint ID is cryptographically verified. - Abort unauthorized connections by returning
AfterHandshakeOutcome::Rejectto close the connection immediately. - Combine hooks for mutual authentication, using outgoing hooks on clients and incoming hooks on servers.
Frequently Asked Questions
Can I reject connections based on IP address before the QUIC handshake starts?
Yes. Implement the before_connect method in your EndpointHooks trait and inspect the remote_addr parameter. Return BeforeConnectOutcome::Reject to block the connection before any cryptographic operations occur, saving CPU resources.
How do I transmit authentication tokens if the hook doesn't handle transport?
The hook only decides whether to accept or reject; it does not handle wire protocol. Typically, you negotiate a custom ALPN and exchange tokens over a dedicated QUIC stream opened immediately after the handshake. The after_handshake hook can then await this data before returning its outcome.
What happens when multiple hooks are registered and one returns Reject?
Hooks execute in registration order. If any hook returns Reject, the connection aborts immediately and subsequent hooks in the EndpointHooksList are not invoked. The remote peer receives an error indicating the handshake was aborted.
Can I use connection hooks for purposes other than authentication?
Yes. The hook system supports any connection lifecycle interception, including logging, metrics collection, rate limiting, or dynamic capability negotiation. The iroh/examples/monitor-connections.rs example demonstrates using hooks purely for diagnostic logging.
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 →