Learn Authentik - Policies: Conditional Logic
Episode 6 of 31

Learn Authentik - Policies: Conditional Logic

Understanding the Authentik policy engine: policy types, Python-based expression policies with user and request context, binding policies to flows, stages, and applications, Any versus All evaluation logic, and examples of access policies based on group, working hours, and IP address.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 5, you built the identity foundation: users, groups, and attributes. Now Authentik can answer the first question, who are you, through the authentication flow. But there's a second, equally important question: what are you allowed to do once you're in?

The answer lies in policies — the conditional logic that decides yes or no at almost every important point in Authentik. Here's an analogy: authentication is like the guard in the building lobby checking ID cards, while a policy is the list of rooms you may enter, complete with opening hours and special rules per floor. In this episode, you'll learn to program that room list.

What Are Policies and Policy Bindings

Simply put, a policy is a boolean decision: pass or fail. That decision then drives Authentik's logic. There are five main places a policy can be bound (attached):

  • Flow: determines whether a user may start or continue a flow.
  • Stage binding: determines whether a stage runs within a flow.
  • Application: determines whether a user may open an application.
  • Source: determines whether a source may be used for login or enrollment.
  • Prompt stage: as a validation policy for data the user just typed.

Besides policies, you can also bind a user or group directly to the same targets. This direct binding is evaluated as a simple membership check — useful when you only need an allow or deny rule without creating a new policy object.

Tip

Rule of thumb from the Authentik documentation: use a direct group binding when the decision is static (for example "only the admins group"), and use a policy when the decision depends on runtime context like network, source, prompt data, or request history.

Policy Types in Authentik

Authentik provides several built-in policy types that can be used without writing code:

  • Expression policy: write free-form logic in Python. The most flexible.
  • Event matcher policy: matches specific events, useful for triggering actions.
  • Have application policy: checks whether the user has access to an application.
  • Password policy: validates password strength against complexity rules.
  • Password expiry policy: checks password validity period.
  • Password uniqueness policy: ensures passwords aren't reused.
  • Reputation policy: blocks IPs or users with a bad reputation score.
  • GeoIP policy: restricts access based on geographic location.
  • Dummy policy: returns a fixed value for testing purposes.

For most authorization needs, the expression policy is the primary choice — precisely because it can combine all the above conditions into a single expression.

Expression Policy: The Brain of All Custom Rules

An expression policy is a piece of Python code executed on the Authentik server side when the policy is evaluated. The expression receives a rich context, then returns True if it passes or False if it fails. The most common example: denying access if the user isn't a member of a specific group.

PythonExpression policy: access only for the app-admins group
if ak_is_group_member(request.user, name="app-admins"):
    return True
ak_message("Access denied: you are not a member of the app-admins group")
return False

Notice two things above. First, the ak_is_group_member(user, name="...") helper checks group membership cleanly. Second, ak_message("...") sets the message shown to the user when the policy fails — don't make the user guess the reason for the denial.

Important

An expression policy is Python code running with elevated privileges inside the Authentik server. Treat the ability to create or edit one like granting administrator access. Don't copy expressions from untrusted sources.

Context Available in Expressions

Some of the most frequently used variables and helpers:

  • request.user: the user currently being evaluated against this policy.
  • request.context: a dictionary with dynamic data from flow execution.
  • ak_is_group_member(user, name="..."): check group membership.
  • ak_user_has_authenticator(user): check whether the user has an MFA device.
  • ak_message("..."): set a message visible to the user.
  • ak_client_ip: the already-parsed client IP object, suitable for network checks.
  • regex_match(value, pattern) and regex_replace(value, pattern, repl): string manipulation.

One classic pitfall: when a policy runs inside the authentication flow, request.user isn't necessarily the user logging in — it could still be AnonymousUser until a user login stage sets it. For this case, the documentation suggests reading pending_user from the flow context (for example request.context.get("pending_user")) so the decision uses the correct identity.

Policy Evaluation: Order, Any, and All

Policies bound to the same target are evaluated in order from top to bottom (ascending order). This matters both when reading logs and when multiple policies produce messages for the user.

Besides order, each target binding has a Policy engine mode:

  • Any: the target passes if any one binding passes. This is the default.
  • All: the target passes only if all bindings pass.

There are also several additional settings that often save you time in production:

  • Negate: inverts the binding result, useful for expressing "everyone except this group".
  • Timeout: the policy execution time limit (default 30 seconds), important for policies that call external systems.
  • Failure result: whether a policy that errors counts as pass or fail.
  • Execution logging: logs every policy execution, not just failures — very helpful when debugging.

Warning

By default, if a target has no bindings at all, Authentik considers it to pass. If you're securing sensitive applications, don't let access rules depend on the absence of a policy — and choose a fail-closed failure result for expressions that protect something important.

Common Use Cases

Group-Based Access

The policy at the start of this episode already showed the pattern: deny if not a member of the app-admins group. Binding it to an application means only members of that group can open the application.

Time-Based Access

Want a service accessible only during working hours? Just compare the hour at evaluation time.

PythonExpression policy: working hours only
from datetime import datetime
now = datetime.now()
return 9 <= now.hour < 17

IP Address Restriction

For internal services that should only be reachable from the office network, use a network comparison.

PythonExpression policy: internal network only
from ipaddress import ip_network
return ak_client_ip in ip_network("10.0.0.0/24")

This is the most widely used pattern: bind a policy to a stage binding, so a specific stage — for example authenticator validate — only runs for some users or applications. This is exactly what you'll dissect further in episode 7 about MFA.

Testing Policies

Never rely on guesses. Authentik provides tools to test a policy before it's used:

  • Policy tester: run the policy against a sample user or request, then see the pass or fail result.
  • Execution logging: enable it to see every execution along with its context.
  • Event logs: every denial is recorded on the events page along with the message from ak_message.
  • Flow inspector: for stage-bound policies, run the flow and check at which point the policy was evaluated.

Tip

Develop the habit of testing with sample users representing each role: one admin user, one regular user, and one user who should be denied. These three tests catch most logic errors.

Closing

Key points from this episode:

  • A policy is a boolean decision that can be bound to flows, stage bindings, applications, sources, and prompt stages.
  • Expression policies use Python with the request.user and request.context context plus helpers like ak_is_group_member.
  • Evaluation is ordered with the Any (default) and All modes, plus negate, timeout, and failure result options.
  • Common patterns: group-based access, working hours, IP address, and MFA enforcement.
  • Always test policies via the policy tester and execution logging.

Now you can program the "who may enter" logic. But there's still a tempting question: what if the condition reads "all admins must use MFA"? In episode 7, you'll dissect authentication stages and the Multi-Factor Authentication mechanism — from identification and password to authenticator validate — along with how to bind it to applications.

Learn Authentik - Policies: Conditional Logic | Learning Authentik