Objects, Templating & Co

The dynamic nature of mechanisms is given by the ability to work on different objects and by using templates and expressions to implement various use cases.

Mechanisms in heimdall are dynamic: they can inspect request data, act on authenticated subject information, and produce output that subsequent pipeline steps can use. This page describes the objects available during rule execution, and the templating and expression capabilities that operate on them.

Objects

Objects represent state during the execution of a particular rule. These are entities either created or used by specific mechanisms, and can represent things like the actual request, the authenticated subject of the request, and many others. Which objects are available in a particular mechanism is documented at each mechanism’s configuration reference.

Principal

A Principal represents an identity of an entity associated with the current request — such as a user, service, or machine — and verified by a particular authenticator. Principals are immutable and ephemeral — they exist only for the lifetime of a single request.

Each principal has the following properties:

  • ID: string

    The unique identifier of the principal.

  • Attributes: map

    A key-value map containing all known attributes about the principal.

Each object of this type can be represented as a JSON object. Here are some examples:

Example 1. Principal created by an Anonymous Authenticator
Principal = {
  ID: "anonymous",
  Attributes: {}
}
Example 2. Principal created by an OAuth2 Introspection Authenticator
Principal = {
  ID: "foobar",
  Attributes: {
    "sub": "foobar",
    "exp": "1670600805",
    "jti": "7b91ed8a-0251-4e02-8d51-9792785851e8",
    "iat": "1670600305",
    "iss": "https://testauthserver.local",
    "nbf": "1670600305",
    "extra": {
        "foo": ["bar", "baz"]
    }
  }
}

Subject

A Subject is a higher-order concept — a key-value collection of Principals verified by all successfully executed authenticators, where the keys represent principal names and the values are the corresponding Principal objects.

Like Principals, Subjects are immutable and ephemeral, existing only for the lifetime of a single request.

In addition to its Principal collection, each Subject exposes the following virtual properties:

  • ID: string

    The unique identifier of the Subject. Its value is taken from the Principal named "default".

  • Attributes: map

    A key-value map containing all attributes of the Principal named "default".

Subject.ID and Subject.Attributes are syntactic sugar for Subject.default.ID and Subject.default.Attributes respectively.

Each object of this type can be thought of as a JSON object. Here is an example:

Example 3. Subject created by two authenticators — one authenticating the user and one the device being used
Subject = {
  "default": Principal {
    ID: "62302d83-da01-4ba6-9440-119728757455",
    Attributes: {
      "email": "john.doe@example.com"
    }
  },
  "device": Principal {
    ID: "597ef6b5-64fc-49e3-ad1e-7599046908d9",
    Attributes: {
      "os": "Ubuntu 25.04"
    }
  }
}

Request

This object contains information about the request handled by heimdall and has the following attributes and methods:

  • Method: string

    The HTTP method used, like GET, POST, etc.

  • URL: URL

    The URL of the matched request. This object has the following properties and methods:

    • Captures: map

      Allows accessing of the values captured by the named wildcards used in the matching path expression of the rule.

    • Host: string

      The host part of the url.

    • Hostname(): method

      This method returns the host name stripping any valid port number if present.

    • Port(): method

      Returns the port part of the Host, without the leading colon. If Host doesn’t contain a valid numeric port, returns an empty string.

    • Path: string

      The path part of the url.

    • Query(): method

      The parsed query with each key-value pair being a string to array of strings mapping.

    • RawQuery: string

      The raw query part of the url.

    • Scheme: string

      The HTTP scheme part of the url.

    • String(): method

      This method returns the URL as valid URL string of a form scheme:host/path?query.

  • ClientIPAddresses: string array

    The list of IP addresses the request passed through with the first entry being the ultimate client of the request. Only available if heimdall is configured to trust the client, sending this information, e.g. in the X-Forwarded-From header (see also trusted_proxies configuration for more details).

  • Header(name): method

    This method expects the name of a header as input and returns its value as a string. If the header is not present in the HTTP request an empty string ("") is returned. If a header appears multiple times in the request, the returned string is a comma separated list of all values.

    A single header may be a comma-separated list of actual values as well. Best example is the Accept header, which might be set to e.g. text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8.
  • Cookie(name): method

    This method expects the name of a cookie as input and returns the value of it as string. If the cookie is not present in the HTTP request an empty string ("") is returned.

  • Body(): method

    The parsed body with contents depending on the Content-Type header. Supported content types are any MIME types with json or yaml subtype, as well as application/x-www-form-urlencoded. If the MIME type is unsupported or no Content-Type header is present, the method returns the raw body as a string.

    The actual request body is parsed only on the first use of this function. All subsequent calls return the cached result.
    Example 4. Example results

    If the Content-Type header is set to application/json and the actual request body is a valid JSON object, shown below

    { "context": "heimdall" }

    The call to the Body() function will return exactly this representation as a map.

    If the Content-Type header is set to application/yaml and the actual request body is a valid YAML object, shown below

    context: heimdall

    The call to the Body() function will return { "context": "heimdall" } representation as a map.

    If the Content-Type header is set to application/x-www-form-urlencoded and the actual request body is a valid object, shown below

    context=heimdall

    The call to the Body() function will return this representation as a map with each value being a string array. In this particular case as { "context": [ "heimdall" ] }.

Here is an example for a request object:

Example 5. Example request object
Request = {
  Method: "GET",
  Url: {
    Scheme: "https",
    Host: "localhost",
    Path: "/test/abc",
    RawQuery: "baz=zab&baz=bar&foo=bar",
    Captures: { "value": "abc" }
  },
  ClientIP: ["127.0.0.1", "10.10.10.10"]
}

Outputs

This object represents a pipeline execution-specific key-value map used by pipeline steps to store and share results. Each step stores its result under its own ID as the key.

Example:

Outputs = {
    "id_1": ["a", "b"],
    "id_2": { "foo": "bar", "baz": false }
}

Payload

This object represents the contents of a payload, like the request body or a response body. The contents depend on the MIME-Type of the payload. For json, yaml or x-www-form-urlencoded encoded payload, the object is transformed to a JSON object. Otherwise, it is just a string.

Here are some examples:

Example 6. Structured payload

The following JSON object is a typical response from OPA.

Payload = { "result": true }
Example 7. Unstructured payload
Payload = "SomeStringValue"

Error

This object represents an error raised during the execution of a rule. It is available in if CEL expressions of Error Handlers and is not available in templates. The following properties are available:

  • Source: string

    The ID of the mechanism which raised the error.

  • StepID: string

    The ID of the pipeline step which raised the error.

Proper error handling requires attention to the actual error type available via type(Error).

Values

This object represents a key-value map, with both the key and value being of string type. The actual values can be templated (see Templating). The contents and the variables available in templates depend on the configuration of the particular mechanism, respectively the corresponding override in a rule.

Here is an example:

Example 8. Example values object
Values = {
  "some-key-1": "value-1",
  "some-key-2": "value-2"
}

Templating

Some mechanisms support templating using Golang Text Templates. Templates can act on the objects described above — which exactly are available is documented at each mechanism’s configuration reference.

To ease the usage, all sprig functions, except env and expandenv, as well as the following functions are available:

  • urlenc - Encodes a given string using URL encoding. Useful when generating request bodies or query parameters for communication with upstream systems.

  • atIndex - Implements Python-like access to arrays. Takes a single integer argument as the index. A positive index behaves like standard array access. A negative index accesses elements from the end of the array (-1 is the last element, -2 the second-last, etc.).

    Example: {{ atIndex 2 [1,2,3,4,5] }} evaluates to 3 and {{ atIndex -2 [1,2,3,4,5] }} evaluates to 4.

  • splitList - Splits a given string using a separator. The result is a string array.

    Example: {{ splitList "/" "/foo/bar" }} evaluates to the ["", "foo", "bar"] array.

  • secret - Reads a secret value from a configured secret source (see Secret Management).

    Example: {{ secret "some_source" "my_api_key" }} reads the value at selector "my_api_key" from the "some_source" secret source.

    If this function is used in a rule, the referenced secret source must have allow_in_rules: true set, otherwise the rule will fail to load.
Example 9. Rendering a JSON object

Imagine we have a POST request for the URL https://foobar.baz/zab?foo=bar, with a header X-Foo set to bar, for which heimdall was able to identify a subject consisting of a single "default" principal with ID=foo and Attributes containing an entry email: foo@bar. The following template generates a JSON object from this information:

{
  "subject_id": {{ quote .Subject.ID }},
  "email": {{ quote .Subject.Attributes.email }},
  "request_url": {{ quote .Request.URL }},
  "foo_value": {{ index .Request.URL.Query.foo 0 | quote }}
  "request_method": {{ quote .Request.Method }},
  "x_foo_value": {{ .Request.Header "X-Foo" | quote }},
  "whatever": {{ .Outputs.whatever | quote }}
}
.Request.URL.Query.foo returns an array of strings, so the first element is accessed explicitly via index.

This will result in the following JSON object:

{
    "subject_id": "foo",
    "email": "foo@bar.baz",
    "request_url": "https://foobar.baz/zab?foo=bar",
    "foo_value": "bar",
    "request_method": "POST",
    "x_foo_value": "bar",
    "whatever": "some value"
}
Example 10. Access to captured path segments

Imagine we have a POST request to the URL https://foobar.baz/zab/1234, with 1234 being the identifier of a file to be updated with the request body, and access control is handled via OpenFGA. This can be achieved with the following authorizer:

id: openfga_authorizer
type: remote
config:
  endpoint:
    url: https://openfga/stores/files/check
  payload: |
    {
      "user": "user:{{ .Subject.ID }}",
      "relation": "write",
      "object": "file:{{ .Request.URL.Captures.id }}"
    }
  expressions:
  - expression: |
      Payload.allowed == true

When the payload template is rendered for the above request with Subject.ID=foo, the following JSON object is produced:

{
  "user": "user:foo",
  "relation": "write",
  "object": "file:1234"
}

You can find further examples as part of the mechanism descriptions that support templating.

Expressions

Expressions can be used to execute conditional logic. Currently, only CEL is supported as expression language. All standard CEL functions, as well as extension functions, are available. Which objects are available in a particular expression is documented at each mechanism’s configuration reference. The Error object is only available in Error Handler expressions.

In addition to the built-in CEL functions and extension methods, the following custom functions are also available:

  • at - Provides Python-like access to array elements. A positive index behaves like standard [] array access. A negative index accesses elements from the end of the array (-1 is the last element, -2 the second-last, etc.).

    Example: [1,2,3,4,5].at(2) returns 3 and [1,2,3,4,5].at(-2) returns 4.

  • last - Returns the last element of an array, or nil if the array is empty.

    Example: [1,2,3,4,5].last() returns 5.

  • regexFind - Returns the first (leftmost) match of a regular expression within a string. Returns an empty string if no match is found.

    Example: "abcd1234".regexFind("[a-zA-Z][1-9]") returns "d1".

  • regexFindAll - Returns an array of all matches of a regular expression within a string. Returns an empty array if no match is found.

    Example: "123456789".regexFindAll("[2,4,6,8]") returns ["2","4","6","8"].

  • split - Splits a string by the given separator and returns an array of strings.

    Example: "/foo/bar/baz".split("/") returns ["", "foo", "bar", "baz"].

  • networks - Accepts a single CIDR string or an array of CIDR strings, and returns a matcher object that can be used to check whether an IP address belongs to one of the specified ranges.

    Examples:

    Check whether all client IPs are within the specified private ranges (e.g., to enforce that a request only arrived via internal networks):

    Request.ClientIPAddresses.all(ip, ip in networks(["172.16.0.0/12", "192.168.0.0/16"]))

    Check whether at least one client IP is within a specific range (e.g., to detect that the request passed through a known proxy):

    Request.ClientIPAddresses.exists(ip, ip in networks("10.0.0.0/8"))

Some examples:

Example 11. Evaluate Payload object

Given the following Payload object

Payload = { "result": true }

a CEL expression to check whether the result attribute is set to true would look as follows:

Payload.result == true
Example 12. Check whether the user represented by the "default" Principal is member of the admin group
has(Subject.Attributes.groups) &&
   Subject.Attributes.groups.exists(g, g == "admin")

As with templates, Subject.ID and Subject.Attributes are syntactic sugar for Subject.default.ID and Subject.default.Attributes respectively.

Example 13. Access the last path part of the matched URL
Request.URL.Path.split("/").last()
Example 14. Check if an error has been raised by an authenticator with the ID "foo"
type(Error) == authentication_error && Error.Source == "foo"

For available error types, see Error State Types.

Last updated on Jun 3, 2026