# What Are ActionMatchers in CoSec? A Guide to Request Authorization

> Discover ActionMatchers in CoSec and how they authorize HTTP requests by matching path patterns to policy rules. Learn to secure your applications effectively.

- Repository: [Ahoo Wang/cosec](https://github.com/ahoo-wang/cosec)
- Tags: how-to-guide
- Published: 2026-02-23

---

**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`](https://github.com/ahoo-wang/cosec/blob/main/cosec-api/src/main/kotlin/me/ahoo/cosec/api/policy/ActionMatcher.kt). This interface extends the generic `RequestMatcher` and defines a single, focused contract:

```kotlin
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`](https://github.com/ahoo-wang/cosec/blob/main/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`](https://github.com/ahoo-wang/cosec/blob/main/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`](https://github.com/ahoo-wang/cosec/blob/main/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`](https://github.com/ahoo-wang/cosec/blob/main/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:

```kotlin
// 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:

```kotlin
// 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.