Skip to main content

Expression Reference

This page documents the expression-policy execution environment in authentik.

Return values

An expression policy passes when it returns True and fails when it returns False.

return True
return False

To show the user why the policy failed, use ak_message() before returning False:

ak_message("Access denied")
return False

Available helper functions

ak_message(message: str)

Add a user-visible message to the current policy result.

ak_message("Access denied")
return False

regex_match(value: Any, regex: str) -> bool

Check if value matches Regular Expression regex.

Example:

return regex_match(request.user.username, '.*admin.*')

regex_replace(value: Any, regex: str, repl: str) -> str

Replace anything matching regex within value with repl and return it.

Example:

user_email_local = regex_replace(request.user.email, '(.+)@.+', '')

list_flatten(value: list[Any] | Any) -> Optional[Any]

Flatten a list by either returning its first element, None if the list is empty, or the passed in object if it's not a list.

Example:

user = list_flatten(["foo"])
# user = "foo"

ak_call_policy(name: str, **kwargs) -> PolicyResult

Call another policy with the name name. The current request is passed to the policy. Keyword arguments can be used to modify the request's context.

Example:

result = ak_call_policy("test-policy")
# result is a PolicyResult object, so you can access `.passing` and `.messages`.
# Starting with authentik 2023.4, you can also access `.raw_result`, which is the raw value returned from the called policy
# `result.passing` will always be a boolean if the policy is passing or not.
return result.passing

result = ak_call_policy("test-policy-2", foo="bar")
# Inside the `test-policy-2` you can then use `request.context["foo"]`
return result.passing

ak_is_group_member(user: User, **group_filters) -> bool

Check if user is member of a group matching **group_filters.

Example:

return ak_is_group_member(request.user, name="test_group")

ak_user_by(**filters) -> Optional[User]

Fetch a user matching **filters.

Returns None if no user was found; otherwise, returns the User object.

Example:

# Find user by username
other_user = ak_user_by(username="other_user")

# Find user by custom attribute
other_user = ak_user_by(attributes__<custom_attribute_name>="<value>")

ak_user_has_authenticator(user: User, device_type: Optional[str] = None) -> bool

Check if a user has any authenticator devices. Only fully validated devices are counted.

Optionally, you can filter by a specific device type. The following options are valid:

  • totp
  • duo
  • static
  • webauthn

Example:

return ak_user_has_authenticator(request.user)

ak_create_event(action: str, **kwargs) -> None

Create a new event with the action set to action. Any additional keyword parameters will be saved in the event context. Additionally, context will be set to the context in which this function is called.

Before saving, any data structures which are not representable in JSON are flattened, and credentials are removed.

The event is saved automatically.

Example:

ak_create_event("my_custom_event", foo=request.user)

ak_create_jwt(user: User, provider: OAuth2Provider | str, scopes: list[str], validity = "seconds=60") -> str | Noneauthentik: 2025.2.0+

Requires HTTP Request

This function will only work when there is an HTTP request in the current context.

Create a new JWT signed by the given provider for user.

The provider parameter can either be an instance of OAuth2Provider or the name of a provider instance as a string. Scopes are an array of all scopes that the JWT should have.

The JWT is valid for 60 seconds by default, and this can be customized using the validity parameter. The syntax of the parameter is hours=1,minutes=2,seconds=3. The following keys are allowed:

  • Microseconds
  • Milliseconds
  • Seconds
  • Minutes
  • Hours
  • Days
  • Weeks

All values accept floating-point values.

Example:

jwt = ak_create_jwt(request.user, "my-oauth2-provider-name", ["openid", "profile", "email"])

ak_create_jwt_raw(provider: OAuth2Provider | str, validity = "seconds=60", **kwargs) -> str | Noneauthentik: 2025.12.0+

Similar to ak_create_jwt, however this function does not require an HTTP request and allows for setting custom claims via **kwargs.

The provider parameter can either be an instance of OAuth2Provider or the name of a provider instance as a string. Scopes are an array of all scopes that the JWT should have.

The JWT is valid for 60 seconds by default, and this can be customized using the validity parameter. The syntax of the parameter is hours=1,minutes=2,seconds=3. The following keys are allowed:

  • Microseconds
  • Milliseconds
  • Seconds
  • Minutes
  • Hours
  • Days
  • Weeks

All values accept floating-point values.

Example:

jwt = ak_create_jwt_raw("my-oauth2-provider-name", my_claim="my_value")

ak_send_email(address: str | list[str], subject: str, body: str = None, stage: EmailStage = None, template: str = None, context: dict = None, cc: str | list[str] = None, bcc: str | list[str] = None) -> boolauthentik: 2025.10.0+

Send an email using authentik's email system.

The address parameter specifies the recipient email address(es). It can be:

  • A single email address as a string: "user@example.com"
  • A list of email addresses: ["user1@example.com", "user2@example.com"]
info

When using multiple recipients in the address or cc fields, all email addresses will be visible to all recipients. Use bcc to send to multiple recipients without revealing addresses to each other.

The subject parameter sets the email subject line.

You must provide either body (for plain text/HTML content) or template (for template rendering), but not both.

The stage parameter can be an EmailStage instance for custom email settings. If None, global email settings are used.

The template parameter specifies a template name to render. When using templates, you can pass additional context variables via the context parameter.

The cc parameter specifies email address(es) to carbon copy. Same format as address.

The bcc parameter specifies email address(es) to blind carbon copy. Same format as address. Recipients in bcc will not be visible to other recipients.

If the email is queued successfully, the function returns True; otherwise, it returns False.

Examples:

# Send email with plain body to single recipient
ak_send_email("user@example.com", "Welcome!", body="Welcome to our platform!")

# Send email to multiple recipients
ak_send_email(
["user1@example.com", "user2@example.com", "admin@example.com"],
"System Maintenance",
body="Scheduled maintenance will occur tonight."
)

# Send email using a template
ak_send_email("user@example.com", "Password Reset", template="email/password_reset.html")

# Send email with custom context for template to multiple recipients
ak_send_email(
["user@example.com", "admin@example.com"],
"Account Update",
template="email/event_notification.html",
context={
"title": "Profile Updated",
"body": "Your account profile has been successfully updated.",
"key_value": {"Updated Field": "Email Address", "Date": "2025-01-01"}
}
)

# Send email with custom email stage
ak_send_email("admin@example.com", "Report", body="Daily report", stage=my_custom_stage)

# Send email with CC
ak_send_email(
"user@example.com",
"Important Update",
body="Please review this update.",
cc="manager@example.com"
)

# Send email with multiple CC and BCC recipients
ak_send_email(
"user@example.com",
"Confidential Report",
body="Attached is the quarterly report.",
cc=["manager@example.com", "lead@example.com"],
bcc=["audit@example.com", "compliance@example.com"]
)

Comparing IP Addresses

To compare IP addresses or check if an IP address is within a given subnet, you can use the functions ip_address('192.0.2.1') and ip_network('192.0.2.0/24'). With these objects you can do arithmetic operations.

You can also check if an IP Address is within a subnet by writing the following:

ip_address('192.0.2.1') in ip_network('192.0.2.0/24')
# evaluates to True

DNS resolution and reverse DNS lookups

To resolve a hostname to a list of IP addresses, use the functions resolve_dns(hostname) and resolve_dns(hostname, ip_version).

resolve_dns("google.com")  # returns a list of all IPv4 and IPv6 addresses
resolve_dns("google.com", 4) # returns a list of only IPv4 addresses
resolve_dns("google.com", 6) # returns a list of only IPv6 addresses

You can also perform reverse DNS lookups.

note

Reverse DNS lookups may not return the expected host if the IP address is part of a shared hosting environment. See: https://stackoverflow.com/a/19867936

To perform a reverse DNS lookup use reverse_dns("192.0.2.0"). If no DNS records are found the original IP address is returned.

info

DNS resolving results are cached in memory. The last 32 unique queries are cached for up to 3 minutes.

Available variables and objects

  • ak_logger: structlog BoundLogger. See structlog documentation

    Example:

    ak_logger.debug("This is a test message")
    ak_logger.warning("This will be logged with a warning level")
    ak_logger.info("Passing structured data", request=request)
  • requests: requests Session object. See requests documentation

Expression-specific context

The imported object reference above covers the generic expression environment. The values below are the ones that are most specific to expression policies in authentik.

request

request is a policy request object with the following properties:

  • request.user: the current user against which the policy is being evaluated. See User.
  • request.http_request: the Django HTTP request, when one exists. See the Django request documentation.
  • request.obj: the object the policy is being evaluated against
  • request.context: a dictionary with dynamic data for the current execution
Flow User Context

When an expression policy runs inside a flow, request.user is not always the user who is currently being authenticated. Until a user_login stage updates the active user, this may still be AnonymousUser or another previously authenticated user.

In authentication and enrollment flows, use context["pending_user"] when you want the user currently being identified or enrolled.

If the user is not authenticated, request.user will be AnonymousUser, which is still an instance of authentik.core.models.User.

The most commonly used values in flow-related expressions are:

  • request.user: the current user for this policy execution
  • request.http_request: the Django request, when one exists
  • request.obj: the object the policy is being evaluated against
  • context["pending_user"]: the user currently being identified or enrolled in a flow
  • context["prompt_data"]: data collected from a prompt stage
  • context["application"]: the application involved in the current authorization flow
  • context["source"]: the source involved in the current login or enrollment
  • context["flow_plan"]: the active flow plan and its mutable context
  • context["geoip"] and context["asn"]: GeoIP and ASN lookups for the client IP
  • ak_client_ip: the parsed client IP address object
  • ak_is_sso_flow: whether the current flow was initiated by an external identity provider source

geoip

context["geoip"] contains GeoIP information for the client IP.

Available fields include:

  • continent: a two-character continent code such as NA or EU
  • country: the two-character ISO 3166-1 alpha code
  • lat: approximate latitude
  • long: approximate longitude
  • city: city name, when available
return context["geoip"]["continent"] == "EU"
Prefer GeoIP Policy

For simple country matching, prefer a GeoIP policy.

asn

context["asn"] contains autonomous system information for the client IP.

Available fields include:

  • asn: the autonomous system number
  • as_org: the organization name for that ASN
  • network: the matching network
return context["asn"]["asn"] == 6939
Prefer GeoIP Policy

For simple ASN matching, prefer a GeoIP policy.

ak_client_ip

ak_client_ip is the parsed client IP address object. You can compare it with Python's ipaddress helpers:

from ipaddress import ip_network

return ak_client_ip in ip_network("10.0.0.0/24")

You can also inspect properties on the parsed IP object, for example:

return ak_client_ip.is_private

ak_client_ip.is_private follows Python's ipaddress semantics and returns True for addresses that are not globally reachable, not only for RFC1918 private ranges.

ak_is_sso_flow

ak_is_sso_flow is True when the current flow was initiated by an external identity provider source.

Flow context keys

When the policy runs inside a flow, authentik exposes the current flow context under context.

Common keys include:

  • context["flow_plan"]: the active flow plan
  • context["flow_plan"].context: the mutable flow-plan context
  • context["flow_plan"].context["redirect"]: the post-flow redirect URL, when set
  • context["prompt_data"]: data collected from prompt stages or external sources
  • context["application"]: the application being authorized
  • context["source"]: the source being used for authentication or enrollment
  • context["pending_user"]: the user currently being identified or enrolled
  • context["is_restored"]: the flow token when the flow was restored from a link
  • context["auth_method"]: the authentication method recorded by the flow