# How to Implement Custom Authentication Logic Using Iroh's Connection Hooks

> Implement custom authentication logic in Iroh using connection hooks. Inspect and reject connections during before connect and after handshake phases with EndpointHooks. Learn more.

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

---

**You can implement custom authentication logic in Iroh by implementing the `EndpointHooks` trait and registering it via `Endpoint::builder().hooks()`, allowing you to inspect and reject connections during the `before_connect` and `after_handshake` phases.**

Iroh's endpoint architecture provides a pluggable hook system that lets you intercept connections at critical lifecycle points without modifying the core library. By leveraging the `EndpointHooks` trait defined in [`iroh/src/endpoint/hooks.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs), you can validate peer credentials and abort unauthorized attempts before they consume resources. This pattern is demonstrated in the reference implementation at [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) in the n0-computer/iroh repository.

## How Connection Hooks Work

Iroh invokes registered hooks at specific points during the connection establishment flow. Understanding these phases is essential for placing your authentication logic correctly.

### Hook Registration

You attach hooks when constructing an `Endpoint` using the builder pattern. 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 internally in an `EndpointHooksList` and executed sequentially in the order they were added.

```rust
let endpoint = iroh::Endpoint::builder()
    .bind_addr("[::]:0".parse().unwrap())
    .hooks(my_auth_hook)
    .build()
    .await?;

```

### Hook Points

The `EndpointHooks` trait defines two async callbacks that control connection acceptance. Both return enums with `Accept` and `Reject` variants; returning `Reject` immediately aborts the connection and prevents subsequent hooks from executing.

**`before_connect`** – Called before the QUIC handshake begins. This method receives the remote address and ALPN identifier, allowing you to reject connections early based on IP allowlists or protocol requirements.

```rust
async fn before_connect(
    &self,
    remote_addr: &SocketAddr,
    alpn: &str,
) -> BeforeConnectOutcome

```

**`after_handshake`** – Called after the QUIC handshake completes and the remote endpoint's identity is cryptographically verified. This is where you implement credential validation, as you now have access to the authenticated `Connection` object.

```rust
async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome

```

### Access to Connection Data

Inside `after_handshake`, you receive a `&Connection` that provides the remote endpoint ID via `conn.remote_id()`. You also receive a `WeakConnectionHandle` that can be upgraded to a full `ConnectionHandle` if you need to interact with the connection later. The implementation in [`iroh/src/endpoint/connection.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/connection.rs) invokes these hooks at the precise moments the connection state transitions occur.

## Implementing the Authentication Hook

To create a working authentication system, define a struct to hold your validation state and implement the `EndpointHooks` trait.

### Define the Hook Struct

Store any state required for validation, such as shared secrets, token validators, or database clients.

```rust
struct MyAuthHook {
    secret: Vec<u8>,
}

```

### Implement EndpointHooks

Provide async implementations for the two required methods. The `after_handshake` method is where you validate the peer's credentials after the cryptographic identity is established.

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

    async fn after_handshake(&self, conn: &Connection) -> AfterHandshakeOutcome {
        let remote_id = conn.remote_id();
        
        // Extract token from a dedicated QUIC stream (implementation specific)
        let token = match conn.receive_auth_token().await {
            Ok(t) => t,
            Err(_) => return AfterHandshakeOutcome::Reject,
        };

        if token == self.secret {
            AfterHandshakeOutcome::Accept
        } else {
            AfterHandshakeOutcome::Reject
        }
    }
}

```

Note that Iroh does not prescribe the token transport mechanism. You typically open a dedicated QUIC stream after the handshake to exchange a small authentication payload, then validate it in this hook.

### Register with the Endpoint

Attach your hook during endpoint construction to activate the authentication check.

```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)
    .build()
    .await?;

```

Now every incoming connection triggers your validation logic. Unauthenticated connections receive an immediate handshake abort error.

## Client-Side Authentication Hooks

For mutual authentication where the client must prove its identity to the server, implement a symmetrical pair of hooks. The pattern in [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) demonstrates this with helper functions `auth::outgoing` and `auth::incoming` that return `(hook, task)` tuples.

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

You can adapt these helpers to use JWTs, API keys, or TLS-derived secrets depending on your security requirements.

## Source File 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`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint/hooks.rs) | Defines `EndpointHooks`, `BeforeConnectOutcome`, and `AfterHandshakeOutcome`. |
| Builder integration | [`iroh/src/endpoint.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/src/endpoint.rs) | Implements `Builder::hooks` and `EndpointHooksList` storage. |
| Connection invocation | [`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 lifecycle points. |
| Authentication example | [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) | Full runnable demo of outgoing and incoming auth hooks. |
| Per-connection state | [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) | Shows how to maintain state across hook invocations. |
| Connection monitoring | [`iroh/examples/monitor-connections.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/monitor-connections.rs) | Demonstrates diagnostic logging hooks. |

## Summary

- **Register hooks** using `Endpoint::builder().hooks(my_hook)` to intercept connections.
- **Implement `before_connect`** to filter connections before the QUIC handshake based on network-level attributes.
- **Implement `after_handshake`** to validate authenticated peer identities using `Connection::remote_id()` and custom credential verification.
- **Return `Reject`** from any hook to immediately abort unauthorized connections.
- **Use symmetrical hooks** for mutual authentication, combining outgoing token transmission with incoming validation.

## Frequently Asked Questions

### How do I access the remote peer's identity inside a hook?

The `after_handshake` method receives a `&Connection` object that exposes the remote endpoint ID via `conn.remote_id()`. This ID is cryptographically verified by the QUIC handshake before your hook executes, ensuring you are validating the authentic peer identity.

### Can I reject connections before the QUIC handshake completes?

Yes. Implement the `before_connect` callback to inspect the remote address and ALPN protocol. Return `BeforeConnectOutcome::Reject` to abort the connection immediately, saving the computational cost of the cryptographic handshake for unauthorized peers.

### Where can I find a complete working example of authentication hooks?

The repository includes a reference implementation at [`iroh/examples/auth-hook.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/auth-hook.rs) that demonstrates both client-side (outgoing) and server-side (incoming) authentication hooks. This example shows how to send tokens over a dedicated QUIC stream and validate them during the connection lifecycle.

### How do I maintain state between hook invocations?

Store your state in the struct implementing `EndpointHooks`. For per-connection state tracking, use the `WeakConnectionHandle` available in the `Connection` object. The example in [`iroh/examples/remote-info.rs`](https://github.com/n0-computer/iroh/blob/main/iroh/examples/remote-info.rs) demonstrates caching remote endpoint information across the connection lifetime.