Subject = {
ID: "anonymous",
Attributes: {}
}
Objects, Templating & Co
The dynamic nature of mechanisms is given by the ability to work on different objects and making use of templates and expressions to implement different use cases.
E.g. in one case, you want to have access to a particular request header. In another case you would like to add specific data to the resulting JWT created by heimdall. And in yet another case, you may want to check whether some expectations apply. These capabilities are described on this page.
Objects
Objects represent state in the execution of a particular rule. These are the entities, either created or used by particular mechanisms and can represent things, like the actual request, the authenticated subject of the request and many more.
Subject
This object is created by an authenticator which was able to verify the authentication claim available in the request, and contains the information about the authenticated subject. It has the following properties:
ID
: stringThe identifier of the subject.
Attributes
: mapContains all attributes, which are known about the subject.
Each object of this type can be thought as a JSON object. Here some examples:
Subject = {
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"]
}
}
}
Request
This object contains information about the request handled by heimdall and has the following attributes and methods:
Method
: stringThe HTTP method used, like
GET
,POST
, etc.
URL
: URLThe URL of the matched request. This object has the following properties and methods:
Captures
: mapAllows accessing of the values captured by the named wildcards used in the matching path expression of the rule.
Host
: stringThe host part of the url.
Hostname()
: methodThis method returns the host name stripping any valid port number if present.
Port()
: methodReturns the port part of the
Host
, without the leading colon. IfHost
doesn’t contain a valid numeric port, returns an empty string.Path
: stringThe path part of the url.
Query()
: methodThe parsed query with each key-value pair being a string to array of strings mapping.
RawQuery
: stringThe raw query part of the url.
Scheme
: stringThe HTTP scheme part of the url.
String()
: methodThis method returns the URL as valid URL string of a form
scheme:host/path?query
.
ClientIPAddresses
: string arrayThe 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 returnedstring
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 withjson
oryaml
subtype, as well asapplication/x-www-form-urlencoded
. If MIME type is unsupported, the method returns a string with the actual body contents.The actual request body is parsed only on the first use of this function. All subsequent calls return the cached result. Example 3. Example resultsIf the
Content-Type
header is set toapplication/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 toapplication/yaml
and the actual request body is a valid YAML object, shown belowcontext: heimdall
The call to the
Body()
function will return{ "context": "heimdall" }
representation as a map.If the
Content-Type
header is set toapplication/x-www-form-urlencoded
and the actual request body is a valid object, shown belowcontext=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:
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. It is used by pipeline steps to store or read results of particular step executions. Mechanism id used by a pipeline step is used as a key and the value is the corresponding result.
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 some examples:
The following JSON object is a typical response from OPA.
Payload = { "result": true }
Payload = "SomeStringValue"
Error
This object represents an error, which has been raised during the execution of a rule and is available in if
CEL expressions of Error Handlers. Following properties are available:
Source
: stringThe ID of the mechanism, 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 the value being of string type. Though, 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:
Values = {
"some-key-1": "value-1",
"some-key-2": "value-2"
}
Templating
Some mechanisms support templating using Golang Text Templates. Templates can act on all objects described above (Subject, Outputs, Request, Payload and Values). Which exactly are supported is mechanism specific.
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. Is handy if you need to generate request body or query parameters e.g. for communication with further systems.atIndex
- Implements python-like access to arrays and takes as a single argument the index to access the element in the array at. With index being a positive values it works exactly the same way, as with the usage of the built-in index function to access array elements. With negative index value, one can access the array elements from the tail of the array. -1 is the index of the last element, -2 the index of the element before the last one, etc.Example:
{{ atIndex 2 [1,2,3,4,5] }}
evaluates to3
(behaves the same way as theindex
function) and{{ atIndex -2 [1,2,3,4,5] }}
evaluates to4
.splitList
- Splits a given string using a separator (part of the sprig library, but not documented). The result is a string array.Example:
{{ splitList "/" "/foo/bar" }}
evaluates to the["", "foo", "bar"]
array.
Imagine, we have a POST
request for the URL https://foobar.baz/zab?foo=bar
, with a header X-Foo
set to bar
value, for which heimdall was able to identify a subject, with ID=foo
and which Attributes
contain an entry email: foo@bar
, then you can generate a JSON object with this information with the following template:
{
"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 }}
}
Please note how the access to the foo
query parameter is done. Since .Request.URL.Query.foo
returns an array of strings, the first element is taken to render the value for the foo_value
key.
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"
}
Imagine, we have a POST
request to the URL https://foobar.baz/zab/1234
, with 1234
being the identifier of a file, which should be updated with the contents sent in the body of the request, and you would like to control access to the aforesaid object using e.g. 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
Please note how the "object"
is set in the payload
property above. When the payload
template is rendered and for the above said request heimdall was able to identify the subject with ID=foo
, following JSON object will be created:
{
"user": "user:foo",
"relation": "write",
"object": "file:1234"
}
You can find further examples as part of mechanism descriptions, supporting templating.
Expressions
Expressions can be used to execute conditional logic. As of today only CEL is supported as expression language. All standard, as well as extension functions are available. Which of the evaluation objects are available to the expression depends on the mechanism.
In addition to the built-in extension methods and functions, as well as the methods available on the evaluation objects, the following functions are available as well:
split
- this function works on strings and expects a separator as a single argument. The result is a string array.Example:
"/foo/bar/baz".split("/")
returns["", "foo", "bar", "baz"]
.regexFind
- this function returns the first (left most) match of a regular expression in the given string.Example:
"abcd1234".regexFind("[a-zA-Z][1-9]")
returns"d1"
.regexFindAll
- this function returns an array of all matches of a regular expression in the given string.Example:
"123456789".regexFindAll("[2,4,6,8]")
returns["2","4","6","8"]
.at
- this function implements python-like access to arrays and takes as a single argument the index to access the element in the array at. With index being a positive values it works exactly the same way, as with the usage of[]
to access array elements. With negative index value, one can access the array elements from the tail of the array. -1 is the index of the last element, -2 the index of the element before the last one, etc.Example:
[1,2,3,4,5].at(2)
returns3
and[1,2,3,4,5].at(-2)
returns4
.last
- this function works on arrays and returns the last element of an array ornil
if the array is empty.Example:
[1,2,3,4,5].last()
returns5
Some examples:
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
has(Subject.Attributes.groups) &&
Subject.Attributes.groups.exists(g, g == "admin")
Request.URL.Path.split("/").last()
type(Error) == authentication_error && Error.Source == "foo"
Last updated on Feb 19, 2025