# How to Use Connection Hooks for Custom Authentication in Iroh

> Implement custom authentication in Iroh using connection hooks. Customize connection validation with EndpointHooks at before_connect and after_handshake stages for secure communication.

- Repository: [number zero/iroh](https://github.com/n0-computer/iroh)
- Tags: how-to-guide
- Published: 2026-07-10

---

**You can implement custom authentication in Iroh by creating a struct that implements the `EndpointHooks` trait and registering it via `Endpoint::builder().hooks()`, which intercepts connections at `before_connect` and `after_handshake` lifecycle points to accept or reject based on your validation logic.**

Iroh provides a pluggable hook system that lets you inspect and control the connection lifecycle without modifying core library code. By implementing the `EndpointHooks` trait, you can inject custom authentication logic that executes during the QUIC handshake process. This approach allows you to validate remote identities, check tokens, or reject connections based on arbitrary criteria before data transfer begins.

## How Connection Hooks Work in Iroh

Iroh’s endpoint architecture is built around **hooks** – pluggable components that are invoked at specific points of the connection lifecycle. The hook system is defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) and integrated into the endpoint builder in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs).

### Hook Registration and Execution Order

When constructing an `Endpoint`, you attach hooks via the builder method `Endpoint::builder().hooks(my_hook)`. According to the source code in [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs), hooks are stored in an `EndpointHooksList` and are executed in the order they were added.

### Lifecycle Hook Points

The `EndpointHooks` trait defines two async callbacks that control connection flow:

- **`before_connect(&self, remote_addr: &SocketAddr, alpn: &str) -> BeforeConnectOutcome`** – Called **before** the QUIC handshake starts. You can reject connections early based on IP address or ALPN protocol.

- **`after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome`** – Called **after** the handshake has completed and the remote endpoint’s identity is known. This is where you implement identity validation.

Both callbacks return an enum with `Accept` or `Reject` variants. Returning `Reject` aborts the connection immediately and prevents later hooks from running.

## Implementing Custom Authentication Logic

To create a custom authentication hook, you define a struct that holds your validation state and implement the `EndpointHooks` trait.

### Define the Hook Structure

Store any state you need for authentication, such as shared secrets, token validators, or database clients:

```rust
struct MyAuthHook {
    secret: Vec<u8>,  // Your secret or validator
}

```

### Implement the EndpointHooks Trait

Use `async_trait` to implement the required methods. In `after_handshake`, you receive a `&Connection` which provides the remote endpoint ID via `conn.remote_id()` and a weak handle for later interaction:

```rust
#[async_trait::async_trait]
impl EndpointHooks for MyAuthHook {
    async fn before_connect(
        &self,
        _remote_addr: &SocketAddr,
        _alpn: &str,
    ) -> BeforeConnectOutcome {
        // Optional: Reject connections early based on IP, etc.
        BeforeConnectOutcome::Accept
    }

    async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome {
        // The remote endpoint ID is now known:
        let remote_id = conn.remote_id();
        
        // Extract authentication token (typically sent via a dedicated QUIC stream)
        let token = match conn.receive_auth_token().await {
            Ok(t) => t,
            Err(_) => return AfterHandshakeOutcome::Reject,
        };

        // Validate the token using your secret
        if token == self.secret {
            AfterHandshakeOutcome::Accept
        } else {
            AfterHandshakeOutcome::Reject
        }
    }
}

```

**Note:** The actual transport of the token is not prescribed by Iroh. You typically open a **dedicated QUIC stream** after the handshake and exchange a small authentication payload. The hook decides whether to allow the connection to proceed.

### Register the Hook with the Endpoint Builder

Install your hook when building the endpoint:

```rust
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?;

```

Now every incoming connection passes through `MyAuthHook`. If the hook returns `Reject`, the connection closes immediately and the remote side receives a handshake abort error.

## Client-Side Authentication Patterns

For mutual authentication where the client must prove its identity to the server, create a **pair of hooks**:

- **Outgoing hook** – Sends a token once the connection is established
- **Incoming hook** – Validates the token as shown above

The repository’s [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/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 JWT, API keys, or TLS-derived secrets.

## Key Source Files and Implementation Details

| Feature | File | Purpose |
|---------|------|---------|
| Hook trait definition & outcome enums | [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) | Core hook API (`EndpointHooks`, `BeforeConnectOutcome`, `AfterHandshakeOutcome`) |
| Builder integration | [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) | `Builder::hooks` method and storage of `EndpointHooksList` |
| Connection handling that invokes hooks | [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) | Calls `before_connect` and `after_handshake` at the right moments |
| Example authentication hook | [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) | Full runnable demo of outgoing & incoming auth hooks |
| Remote-map hook (per-connection state) | [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) | Demonstrates a hook that records remote endpoint information |
| Monitoring connection example | [`iroh/examples/monitor-connections.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/monitor-connections.rs) | Shows a hook that logs connection details for diagnostics |

## Summary

- **Register** your hook with `Endpoint::builder().hooks(my_hook)` to install it in the endpoint's `EndpointHooksList`.
- Implement `before_connect` to filter connections early by IP or ALPN, and `after_handshake` to validate the authenticated peer's identity.
- Use `AfterHandshakeOutcome::Reject` to abort unauthenticated connections before data transfer begins.
- Access the remote endpoint ID via `conn.remote_id()` inside `after_handshake` for identity validation.
- Combine an outgoing hook (client) and an incoming hook (server) 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 protocol string. The `after_handshake` hook runs after the cryptographic handshake completes, giving you access to the authenticated `Connection` object including the remote endpoint ID. Use `before_connect` for IP-based filtering and `after_handshake` for identity verification.

### How do I access the remote peer's identity in an authentication hook?

Inside the `after_handshake` method, call `conn.remote_id()` on the `&Connection` parameter to retrieve the remote endpoint's identity. This method returns the verified public key or identifier of the peer, which you can validate against your authentication database or token.

### Can I maintain state between hook invocations?

Yes. Since you define the hook struct yourself, you can store any stateful components such as database connections, cache clients, or shared secrets. For per-connection state tracking, see the [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) example which demonstrates how to record remote endpoint information across the connection lifecycle.

### What happens if a hook returns Reject?

When any hook returns `BeforeConnectOutcome::Reject` or `AfterHandshakeOutcome::Reject`, the connection is aborted immediately. The QUIC handshake fails, and the remote peer receives an error indicating the connection was rejected. Subsequent hooks in the `EndpointHooksList` are not executed for rejected connections.