What Are ActionMatchers in CoSec? A Guide to Request Authorization

ActionMatchers in CoSec are pluggable components that determine whether an HTTP request action matches a policy rule by evaluating the request path against configurable patterns.

In the ahoo-wang/cosec authorization framework, ActionMatchers serve as the primary mechanism for identifying what resource a user is attempting to access. They decouple action identification logic from the core policy engine, enabling dynamic, context-aware authorization decisions without modifying core framework code.

The ActionMatcher Interface and Core Contract

At the heart of the system lies the ActionMatcher interface, located in cosec-api/src/main/kotlin/me/ahoo/cosec/api/policy/ActionMatcher.kt. This interface extends the generic RequestMatcher and defines a single, focused contract:

fun match(request: Request, securityContext: SecurityContext): Boolean

The match method receives the incoming Request object—typically containing the HTTP path—and a mutable SecurityContext that holds authentication details and runtime attributes. A return value of true indicates the request action satisfies the policy rule currently being evaluated.

How ActionMatchers Are Created: The Factory Pattern

CoSec instantiates matchers through the ActionMatcherFactory interface (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/ActionMatcherFactory.kt). This factory pattern separates configuration from execution, allowing policies to define matchers declaratively in JSON or YAML while the framework handles instantiation.

SPI-Based Discovery

Factories are discovered at runtime using the Java Service Provider Interface (SPI) mechanism. Built-in factories are registered in cosec-core/src/main/resources/META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory. To add a custom matcher, implement ActionMatcherFactory, package it as a JAR, and register your factory class in your own META-INF/services file. CoSec will automatically pick up the implementation using the type string specified in your policy configuration.

Built-in ActionMatcher Implementations

The CoSec codebase provides four primary implementations to handle different matching scenarios:

PathActionMatcher for Static Patterns

PathActionMatcher matches requests against static Spring PathPattern expressions. Located in cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathActionMatcher.kt, this implementation evaluates literal paths such as /api/orders/** and extracts path variables (e.g., {orderId}) into the SecurityContext under the key PATH_VARIABLES for downstream use.

ReplaceablePathActionMatcher for Dynamic SpEL Templates

For scenarios requiring runtime context, ReplaceablePathActionMatcher evaluates patterns as Spring Expression Language (SpEL) templates. This enables dynamic authorization rules such as /tenant/${principal.id}/**, where the ${principal.id} placeholder resolves against values stored in the SecurityContext. This implementation supports multi-tenant and user-specific access controls without hardcoding identifiers into policy files.

CompositeActionMatcher for Complex Logic

When policies require multiple conditions to be satisfied simultaneously, CompositeActionMatcher (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/CompositeActionMatcher.kt) aggregates multiple ActionMatcher instances. This implementation effectively performs a logical "AND" operation across its contained matchers, allowing complex rules like "match /api/** AND exclude /api/public/**".

AllActionMatcher for Universal Matching

AllActionMatcher (cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/AllActionMatcher.kt) is a special pass-through implementation that always returns true. Use this matcher when a policy should apply regardless of the request action, effectively ignoring the action dimension while still evaluating subject and condition constraints.

Policy Evaluation Flow

When CoSec evaluates an authorization request, ActionMatchers participate in a specific sequence:

  1. The policy JSON supplies an action node (e.g., { "action": { "type": "path", "pattern": "/api/**" } }).
  2. CoSec deserializes this node into a Configuration object.
  3. The corresponding ActionMatcherFactory.create(configuration) method instantiates the appropriate matcher.
  4. During policy evaluation, the framework invokes matcher.match(request, securityContext).
  5. If the matcher succeeds and the pattern contains path variables, these variables are stored in the SecurityContext under PATH_VARIABLES for later retrieval by condition matchers or business logic.

Practical Implementation Examples

The following Kotlin examples demonstrate how to construct and use ActionMatchers programmatically:

// Build a static path matcher from configuration
val json = """{ "type": "path", "pattern": "/api/orders/**" }"""
val cfg = json.asConfiguration()                 
val matcher = PathActionMatcherFactory.INSTANCE.create(cfg)

// Use the matcher during a request
val request = SimpleRequest(path = "/api/orders/123")
val ctx = SimpleSecurityContext()
val matches = matcher.match(request, ctx)         
println(ctx.getPathVariables())                  // { "orderId" -> "123" }

For dynamic, context-aware matching:

// Dynamic matcher with SpEL template
val dynJson = """{ "type": "path", "pattern": "/tenant/${'$'}{principal.id}/**" }"""
val dynCfg = dynJson.asConfiguration()
val dynMatcher = PathActionMatcherFactory.INSTANCE.create(dynCfg)

// Assume the principal id is "42" in the security context
ctx.setAttributeValue("principal.id", "42")
val dynMatches = dynMatcher.match(request, ctx) 

Extending ActionMatchers with Custom Implementations

The SPI-based architecture enables seamless extension without forking the core codebase. To implement a custom ActionMatcher:

  1. Create a class implementing the ActionMatcher interface.
  2. Create a factory class implementing ActionMatcherFactory that returns your matcher instance.
  3. Register your factory in META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory.
  4. Reference your matcher in policy files using the type string returned by your factory's getType() method.

This design pattern ensures that domain-specific matching logic—such as matching against GraphQL operation names or gRPC method descriptors—can be added as first-class citizens within the CoSec authorization framework.

Summary

  • ActionMatchers are the core abstraction in ahoo-wang/cosec for determining if a request action satisfies policy requirements.
  • The ActionMatcher interface defines a single match(request, securityContext) method implemented by all concrete matchers.
  • PathActionMatcher handles static URL patterns while ReplaceablePathActionMatcher supports dynamic SpEL templates for context-aware routing.
  • The SPI factory pattern enables zero-code-integration of custom matchers by registering implementations in META-INF/services.
  • Successful matches extract path variables into the SecurityContext under PATH_VARIABLES for downstream policy evaluation.

Frequently Asked Questions

What is the difference between PathActionMatcher and ReplaceablePathActionMatcher?

PathActionMatcher evaluates static Spring PathPattern expressions literally, making it ideal for fixed API routes like /api/users/**. ReplaceablePathActionMatcher treats the pattern as a SpEL template, allowing placeholders like ${principal.id} to be resolved at runtime against the SecurityContext, which supports dynamic multi-tenant scenarios.

How does CoSec discover custom ActionMatcher implementations?

CoSec uses the Java SPI mechanism to locate ActionMatcherFactory implementations at runtime. Custom matchers must package a service registration file at META-INF/services/me.ahoo.cosec.policy.action.ActionMatcherFactory containing the fully qualified class name of their factory implementation.

Can ActionMatchers extract variables from the request path?

Yes. When using PathActionMatcher or ReplaceablePathActionMatcher with templated segments (e.g., /orders/{orderId}), successfully matched path variables are automatically stored in the SecurityContext under the PATH_VARIABLES key, making them available to condition matchers and downstream authorization logic.

What happens when multiple ActionMatchers need to be combined?

Use CompositeActionMatcher to aggregate multiple matchers into a single logical unit. This implementation requires all contained matchers to return true for the composite to match, effectively providing an "AND" semantic for complex authorization rules that must satisfy multiple path conditions simultaneously.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →