# How the CoSec ActionMatcher Path Matcher Works: A Technical Deep Dive

> Explore the technical details of the CoSec ActionMatcher path matcher. Learn how it handles static and dynamic patterns, evaluates requests with Spring PathPattern, and stores URI variables in SecurityContext.

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

---

**The CoSec ActionMatcher path matcher uses `PathActionMatcherFactory` to instantiate either static `PathActionMatcher` objects for fixed patterns or `ReplaceablePathActionMatcher` for dynamic SpEL templates, evaluating requests against Spring's `PathPattern` and persisting extracted URI variables in the `SecurityContext`.**

The **CoSec ActionMatcher path matcher** is the primary mechanism for URL-based authorization decisions in the ahoo-wang/cosec policy engine. Located in `cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action`, this component determines whether incoming requests satisfy policy rules by matching their paths against configured patterns. Understanding its internal implementation reveals how the framework balances high-performance static matching with flexible, context-aware dynamic authorization.

## Core Architecture and Factory Pattern

### PathActionMatcherFactory

The entry point for creating matchers is `PathActionMatcherFactory` (lines 70-95 in [`PathActionMatcher.kt`](https://github.com/ahoo-wang/cosec/blob/main/PathActionMatcher.kt)). This factory reads the configuration value associated with the constant `PATTERN_KEY` (`"pattern"`) and inspects the string for SpEL template syntax (`#{…}`). Based on this inspection, it returns either a pre-compiled static matcher or a dynamic replaceable matcher.

### Static vs. Dynamic Implementations

When the pattern contains no SpEL expressions, the factory returns a **`PathActionMatcher`** (lines 37-50). This class pre-parses the pattern using Spring's `PathPatternParser` during construction, storing the resulting `PathPattern` instance for reuse across all requests.

If the pattern includes SpEL templates such as `/users/#{principal.id}/**`, the factory creates a **`ReplaceablePathActionMatcher`** (lines 53-66). This implementation evaluates the template against the current `SecurityContext` at request time, generating a concrete pattern string before parsing and matching.

## Internal Matching Flow and Implementation

The matching logic resides in the `internalMatch()` methods. For static patterns, the `PathActionMatcher` executes the following sequence:

```kotlin
override fun internalMatch(request: Request, securityContext: SecurityContext): Boolean {
    // 1️⃣ Turn the request path into a PathContainer using the same parser options.
    PathContainer.parsePath(request.path, patternParser.pathOptions)
        .let { pathContainer ->
            // 2️⃣ Try to match the container against the stored pattern.
            val pathMatchInfo = pathPattern.matchAndExtract(pathContainer) ?: return false
            // 3️⃣ If matched, expose any URI variables (e.g. {id}) to downstream policies.
            securityContext.setPathVariables(pathMatchInfo.uriVariables)
            return true
        }
}

```

For replaceable matchers, the flow adds a template resolution step before parsing:

```kotlin
val pathPattern = requireNotNull(expression.getValue(securityContext))
val pathContainer = PathContainer.parsePath(request.path)
patternParser.parse(pathPattern).let {
    val match = it.matchAndExtract(pathContainer) ?: return false
    securityContext.setPathVariables(match.uriVariables)
    return true
}

```

Both implementations store extracted URI variables in the `SecurityContext` under the `PATH_VARIABLES` attribute key, making them available to subsequent condition matchers in the policy chain.

## Configuration Syntax and Practical Examples

### Static Path Patterns

Define fixed URL patterns using Ant-style syntax in your policy JSON:

```json
{
  "type": "path",
  "pattern": "/api/orders/**"
}

```

This configuration matches any request starting with `/api/orders/`. You can capture specific segments using `{variable}` syntax for later use.

### Dynamic SpEL Templates

For context-dependent authorization, use SpEL expressions within the pattern:

```json
{
  "type": "path",
  "pattern": "/users/#{principal.id}/profile"
}

```

At runtime, `#{principal.id}` evaluates against the current `SecurityContext`, substituting the authenticated user's ID. This ensures the pattern only matches the requesting user's own profile endpoint, enabling resource-level authorization.

### Accessing Extracted Variables

When patterns contain variables like `/orders/{orderId}`, the matcher extracts values into the security context. Downstream conditions can reference these using the `PATH_VARIABLES` key:

```json
{
  "type": "path",
  "pattern": "/orders/{orderId}"
},
{
  "condition": {
    "type": "equals",
    "left": "${PATH_VARIABLES.orderId}",
    "right": "${request.query.id}"
  }
}

```

## Parser Configuration and Customization

The **`PathPatternParsers`** utility class ([`PathPatternParsers.kt`](https://github.com/ahoo-wang/cosec/blob/main/PathPatternParsers.kt)) handles optional parser configuration. You can specify custom `PathPatternParser` options—such as case sensitivity or trailing slash handling—through the `options` key in your matcher configuration. By default, the implementation uses `PathPatternParser.defaultInstance` from Spring.

Key source files for reference:
- [`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) — Core matcher implementations and factory
- [`cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/PathPatternParsers.kt) — Parser configuration handling
- [`cosec-core/src/main/kotlin/me/ahoo/cosec/serialization/JsonActionMatcherSerializer.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/serialization/JsonActionMatcherSerializer.kt) — Policy deserialization logic
- [`cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/ActionMatcherFactoryProvider.kt`](https://github.com/ahoo-wang/cosec/blob/main/cosec-core/src/main/kotlin/me/ahoo/cosec/policy/action/ActionMatcherFactoryProvider.kt) — Factory registration and discovery

## Summary

- **PathActionMatcherFactory** instantiates matchers based on the `pattern` configuration key, detecting SpEL templates to choose between static and dynamic implementations.
- **PathActionMatcher** provides high-performance matching for fixed patterns by pre-parsing the `PathPattern` at construction time.
- **ReplaceablePathActionMatcher** evaluates SpEL templates against the `SecurityContext` at request time, enabling dynamic path construction for user-specific resources.
- **URI variables** captured from patterns like `{id}` are stored in the `SecurityContext` as `PATH_VARIABLES`, accessible to subsequent policy conditions.
- The implementation leverages Spring's `PathPatternParser` with configurable options for case sensitivity and path normalization.

## Frequently Asked Questions

### What is the difference between PathActionMatcher and ReplaceablePathActionMatcher?

**PathActionMatcher** handles static patterns and parses the `PathPattern` once during initialization for optimal performance. **ReplaceablePathActionMatcher** handles patterns containing SpEL templates (`#{expression}`), evaluating the template against the current `SecurityContext` on every request to generate the actual pattern string before matching. Use the static variant for fixed API paths and the replaceable variant for user-specific or dynamic resource paths.

### How do I access path variables in downstream condition matchers?

When a pattern like `/orders/{orderId}` matches, the `PathActionMatcher` automatically stores the extracted value in the `SecurityContext` under the `PATH_VARIABLES` key. You can reference these variables in subsequent conditions using the syntax `${PATH_VARIABLES.orderId}` or access them programmatically via `securityContext.getPathVariables()` in custom components.

### Can I customize the path parsing behavior for case sensitivity?

Yes. According to the `PathPatternParsers` implementation in [`PathPatternParsers.kt`](https://github.com/ahoo-wang/cosec/blob/main/PathPatternParsers.kt), you can provide custom parser options through the `options` configuration key in your matcher definition. These options are passed directly to Spring's `PathPatternParser`, allowing you to control case sensitivity, trailing slash handling, and other parsing behaviors beyond the default settings.

### Where is the path matcher factory registered in the CoSec policy engine?

The `PathActionMatcherFactory` is registered through `ActionMatcherFactoryProvider`, which makes the `path` matcher type available to the policy engine's `JsonActionMatcherSerializer`. This registration occurs in [`ActionMatcherFactoryProvider.kt`](https://github.com/ahoo-wang/cosec/blob/main/ActionMatcherFactoryProvider.kt), ensuring the factory is discoverable when deserializing policy JSON configurations and associating the `"type": "path"` identifier with the correct implementation.