Back to flin
flin

Custom Guards and Security Middleware

How FLIN developers create custom guards and security middleware for application-specific access control -- verified email guards, subscription checks, IP restrictions, and audit logging.

Thales & Claude | March 25, 2026 7 min flin
flinguardsmiddlewarecustomsecurity

FLIN's nine built-in guards cover the most common access control patterns: authentication, roles, rate limiting, CSRF, API keys, IP whitelists, time restrictions, resource ownership, and method filtering. But real applications have domain-specific security requirements that no framework can anticipate in advance.

A SaaS application needs to check subscription tiers. An educational platform needs to verify enrollment status. A financial application needs audit logging on every mutation. A multi-tenant system needs to ensure users only access their own tenant's data.

FLIN's guard_definition syntax and middleware system let developers create these custom security layers with the same declarative API as built-in guards.

Defining Custom Guards

The guard_definition keyword creates a new guard type that can be used anywhere the built-in guards are used:

flin// Define a custom guard
guard_definition verified_email {
    user = request.user
    if user == none || !user.emailVerified {
        return response {
            status: 403
            body: { error: "Email verification required" }
        }
    }
}

The guard definition contains the check logic. If the function returns a response, the guard fails and that response is sent to the client. If the function returns nothing (falls through), the guard passes.

Use the custom guard exactly like a built-in guard:

flinguard auth
guard verified_email

route POST {
    // Only authenticated users with verified email reach here
}

Parameterized Custom Guards

Custom guards can accept parameters for configurable behavior:

flinguard_definition subscription(required_plan) {
    user = request.user
    if user == none {
        return response {
            status: 401
            body: { error: "Authentication required" }
        }
    }

    plan_levels = { "free": 0, "starter": 1, "pro": 2, "enterprise": 3 }
    user_level = plan_levels[user.plan] || 0
    required_level = plan_levels[required_plan] || 0

    if user_level < required_level {
        return response {
            status: 403
            body: {
                error: "This feature requires the " + required_plan + " plan",
                current_plan: user.plan,
                required_plan: required_plan,
                upgrade_url: "/pricing"
            }
        }
    }
}

// Usage
guard auth
guard subscription("pro")

route POST {
    // Only pro and enterprise users
}

The error response includes context that the frontend can use to show an upgrade prompt.

Real-World Custom Guard Patterns

Enrollment Check for Education Platform

flinguard_definition enrolled(course_id_param) {
    user = request.user
    if user == none {
        return response { status: 401, body: { error: "Authentication required" } }
    }

    course_id = to_int(params[course_id_param])
    enrollment = Enrollment.where(
        user_id == user.id && course_id == course_id
    ).first

    if enrollment == none {
        return response {
            status: 403
            body: {
                error: "You are not enrolled in this course",
                course_id: course_id,
                enroll_url: "/courses/" + to_text(course_id) + "/enroll"
            }
        }
    }

    // Attach enrollment to request for downstream use
    request.enrollment = enrollment
}

// app/api/courses/[course_id]/lessons/[id].flin
guard auth
guard enrolled("course_id")

route GET {
    // request.enrollment is available
    lesson = Lesson.find(params.id)
    lesson
}

Multi-Tenant Isolation

flinguard_definition tenant_member {
    user = request.user
    if user == none {
        return response { status: 401, body: { error: "Authentication required" } }
    }

    tenant_id = to_int(params.tenant_id || headers["X-Tenant-ID"])
    membership = TenantMember.where(
        user_id == user.id && tenant_id == tenant_id
    ).first

    if membership == none {
        return response {
            status: 403
            body: { error: "You do not have access to this tenant" }
        }
    }

    request.tenant_id = tenant_id
    request.tenant_role = membership.role
}

guard_definition tenant_admin {
    if request.tenant_role != "admin" && request.tenant_role != "owner" {
        return response {
            status: 403
            body: { error: "Admin access required for this tenant" }
        }
    }
}

// app/api/tenants/[tenant_id]/settings.flin
guard auth
guard tenant_member
guard tenant_admin

route PUT {
    // Only tenant admins and owners
}

Feature Flags

flinguard_definition feature(flag_name) {
    flag = FeatureFlag.where(name == flag_name).first

    if flag == none || !flag.enabled {
        return response {
            status: 404
            body: { error: "This feature is not available" }
        }
    }

    // Check user-specific rollout
    if flag.rollout_percent < 100 {
        user = request.user
        if user != none {
            user_hash = hash_string(to_text(user.id) + flag_name) % 100
            if user_hash >= flag.rollout_percent {
                return response {
                    status: 404
                    body: { error: "This feature is not available" }
                }
            }
        }
    }
}

// app/api/beta/ai-search.flin
guard auth
guard feature("ai_search_beta")

route POST {
    // Only available when the ai_search_beta flag is enabled
    // and the user is in the rollout percentage
}

Security Middleware Patterns

While guards handle per-route access control, middleware handles cross-cutting security concerns. Custom middleware is defined in _middleware.flin files.

Audit Logging Middleware

flin// app/api/_middleware.flin

middleware {
    start = now()

    next()

    // Log after handler completes
    if request.method != "GET" && request.method != "OPTIONS" {
        save AuditLog {
            user_id: request.user_id || 0,
            method: request.method,
            path: request.path,
            ip: request.ip,
            status: response.status,
            duration_ms: (now() - start),
            user_agent: request.user_agent,
            body_summary: truncate(to_text(body), 500)
        }
    }
}

This middleware logs every mutating request (POST, PUT, DELETE, PATCH) with the user, the endpoint, the response status, and a summary of the request body. The audit trail is automatic for every API endpoint.

Request Signing Middleware

For APIs that require request signing (webhook receivers, partner APIs):

flin// app/api/webhooks/_middleware.flin

middleware {
    signature = headers["X-Signature-256"]
    if signature == "" {
        return response { status: 401, body: { error: "Missing signature" } }
    }

    expected = hmac_sha256(request.raw_body, env("WEBHOOK_SECRET"))
    if !constant_time_eq(signature, "sha256=" + expected) {
        return response { status: 401, body: { error: "Invalid signature" } }
    }

    next()
}

Geoblocking Middleware

flin// app/api/restricted/_middleware.flin

middleware {
    country = geoip_country(request.ip)

    blocked = ["XX", "YY"]  // Sanctioned countries
    if blocked.contains(country) {
        return response {
            status: 403
            body: { error: "Service not available in your region" }
        }
    }

    request.country = country
    next()
}

Guard Composition Patterns

Custom guards compose naturally with built-in guards using AND logic:

flin// Maximum security for financial operations
guard auth
guard role("accountant", "admin")
guard verified_email
guard subscription("enterprise")
guard tenant_member
guard rate_limit(10, 60)
guard time("08:00", "18:00")

route POST {
    // This endpoint requires:
    // 1. Authentication
    // 2. Accountant or admin role
    // 3. Verified email address
    // 4. Enterprise subscription
    // 5. Tenant membership
    // 6. Under rate limit
    // 7. During business hours
}

Seven guards, seven lines. Each is evaluated in order. If any fails, the request is rejected with the appropriate status code and error message.

Testing Custom Guards

Custom guards should be tested with the same rigor as built-in guards:

flin// test/guards/subscription_guard_test.flin

test "subscription guard rejects free user for pro feature" {
    ctx = mock_request_context({ user: { plan: "free" } })
    result = guard_subscription(ctx, ["pro"])
    assert result.status == 403
}

test "subscription guard accepts pro user for pro feature" {
    ctx = mock_request_context({ user: { plan: "pro" } })
    result = guard_subscription(ctx, ["pro"])
    assert result == pass
}

test "subscription guard accepts enterprise user for pro feature" {
    ctx = mock_request_context({ user: { plan: "enterprise" } })
    result = guard_subscription(ctx, ["pro"])
    assert result == pass
}

The Architecture of Extensible Security

FLIN's security architecture follows a principle: defaults are non-negotiable, extensions are unlimited.

The nine built-in guards and the automatic security headers cannot be removed or weakened. They provide a baseline that every FLIN application inherits. But above that baseline, developers can add any number of custom guards, custom middleware, and custom validation rules to implement their specific security requirements.

This architecture means: - A new FLIN developer gets enterprise-grade security out of the box. - An experienced developer can extend the security model without fighting the framework. - Security features are visible, declarative, and testable. - The attack surface is known and bounded.

This concludes Arc 10 -- FLIN's security features. Ten articles covering OWASP Top 10 coverage, Argon2 password hashing, JWT authentication, rate limiting, security headers, 2FA, OAuth2, WhatsApp OTP, input validation, security testing, and custom guards. In Arc 11, we enter the most innovative part of FLIN: the AI and Intent Engine, where natural language meets database queries.


This is Part 115 of the "How We Built FLIN" series, documenting how a CEO in Abidjan and an AI CTO designed and built a programming language from scratch.

Series Navigation: - [114] 75 Security Tests: How We Verified Everything - [115] Custom Guards and Security Middleware (you are here) - [116] The Intent Engine: Natural Language Database Queries - [117] Semantic Search and Vector Storage

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles