Skip to content

Custom Middleware Guide

Learning Signals

  • Level: Intermediate
  • Time: 10 minutes
  • Prerequisites: Middleware

This guide shows how to implement custom authorization rules that validate session tokens across the Nalix inbound pipeline.

Implementation overview

Implementing custom middleware involves creating a class that implements IPacketMiddleware<TPacket>. For general-purpose security middleware, you should typically target IPacket, the base interface for all packets.

Step 1. Implement packet middleware for session validation

The following middleware checks for a valid session token in the connection attributes and verifies permissions against the packet's metadata.

using System;
using System.Threading;
using System.Threading.Tasks;
using Nalix.Abstractions.Middleware;
using Nalix.Abstractions.Networking;
using Nalix.Abstractions.Networking.Packets;
using Nalix.Abstractions.Networking.Sessions;
using Nalix.Abstractions.Primitives;
using Nalix.Codec.DataFrames.SignalFrames;
using Nalix.Framework.Injection;

[MiddlewareOrder(-60)]
[MiddlewareStage(MiddlewareStage.Inbound)]
public sealed class SessionAuthorizationMiddleware : IPacketMiddleware<IPacket>
{
    private readonly IConnectionHub? _hub = InstanceManager.Instance.GetExistingInstance<IConnectionHub>();

    public async ValueTask InvokeAsync(
        IPacketContext<IPacket> context,
        Func<CancellationToken, ValueTask> next)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(next);

        // 1) Permission gate from packet metadata.
        if (context.Attributes.Permission is not null &&
            context.Connection.Level < context.Attributes.Permission.Level)
        {
            context.Connection.Disconnect("Permission denied.");
            return;
        }

        // 2) Session token gate from connection attributes + session store.
        if (!TryGetSessionToken(context.Connection, out ulong sessionToken))
        {
            context.Connection.Disconnect("Missing session token.");
            return;
        }

        if (_hub is null)
        {
            context.Connection.Disconnect("Session service unavailable.");
            return;
        }

        SessionEntry? entry = await _hub.SessionStore
            .RetrieveAsync(sessionToken, context.CancellationToken)
            .ConfigureAwait(false);

        if (entry is null)
        {
            context.Connection.Disconnect("Session expired or revoked.");
            return;
        }

        entry.Return();
        await next(context.CancellationToken).ConfigureAwait(false);
    }

    private static bool TryGetSessionToken(IConnection connection, out ulong token)
    {
        if (connection.Attributes.TryGetValue(ConnectionAttributes.SessionToken, out object? raw) &&
            raw is ulong sessionToken)
        {
            token = sessionToken;
            return true;
        }

        token = default;
        return false;
    }
}

Step 2. Register middleware in the host dispatch

Middleware is registered fluently during server setup using the ConfigureDispatch method.

using Nalix.Hosting;

using NetworkApplication app = NetworkApplication.CreateBuilder()
    .ConfigureDispatch(options =>
    {
        _ = options.WithMiddleware(new SessionAuthorizationMiddleware());
    })
    .BindTcp<MyProtocol>().Bind()
    .Build();

Step 3. Add packet metadata on handlers

The middleware will now automatically enforce permissions based on the attributes applied to your handler methods.

[PacketController("SecureHandlers")]
public sealed class SecureHandlers
{
    [PacketOpcode(0x1201)]
    [PacketPermission(PermissionLevel.USER)]
    public ValueTask HandleAsync(IPacketContext<MyPacket> context)
    {
        // Business logic execution
        return ValueTask.CompletedTask;
    }
}

Flow summary

flowchart LR
    A["Socket buffer"] --> B["Deserialization"]
    B --> C["SessionAuthorizationMiddleware"]
    C --> D["Handler"]