How the Authentik Reputation Policy Works for User Reputation Scoring

The authentik reputation policy evaluates login trustworthiness by aggregating per-IP and per-username reputation scores that increment on successful logins and decrement on failures, then compares the total against a configurable threshold.

The reputation policy in authentik is a built-in security mechanism that protects against brute-force attacks and rewards legitimate users. This article explains the complete technical implementation—from signal handlers that capture authentication events to the policy evaluation logic that permits or denies requests—based on the goauthentik/authentik source code.

Core Components of the Reputation System

ReputationPolicy Model

The ReputationPolicy class in [authentik/policies/reputation/models.py](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/models.py#L28-L34) defines how the policy behaves. It stores three critical settings:

  • check_ip — whether to include the client IP in reputation calculations
  • check_username — whether to include the username (identifier) in calculations
  • threshold — the minimum aggregate score required for the policy to pass (negative values block suspicious actors)

Reputation Data Model

The Reputation model (models.py#L70-L83) persists scoring data with these fields:

Field Description
identifier The username or user identifier
ip The client IP address
score Current integer score (positive = trusted, negative = suspicious)
ip_geo_data Geographic location metadata
ip_asn_data Autonomous System Number metadata
expires Timestamp when the entry should be purged

Each Reputation entry is uniquely keyed by the combination of (ip, identifier).

How Scores Are Updated: The Signal Pipeline

Authentication Event Handlers

The [authentik/policies/reputation/signals.py](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/signals.py#L22-L68) file contains Django signal handlers that respond to three authentication events:

  1. login_failed → calls update_score(..., amount=-1)
  2. identification_failed → calls update_score(..., amount=-1)
  3. user_logged_in → calls update_score(..., amount=+1)

The update_score() Function

The update_score() function (signals.py#L23-L48) is the central scoring engine. It performs four operations:

  1. Extracts the client IP from the request metadata
  2. Clamps the score delta using tenant-wide limits (tenant.reputation_lower_limit and tenant.reputation_upper_limit)
  3. Executes an UPSERT (ON CONFLICT … UPDATE) on the (ip, identifier) pair, atomically adjusting the score while keeping it within bounds
  4. Refreshes metadata by updating geo/ASN data and resetting the expiry timestamp
from authentik.policies.reputation.signals import update_score
from django.test import RequestFactory

# Simulate a request from a suspicious IP

rf = RequestFactory()
request = rf.get("/", REMOTE_ADDR="203.0.113.5")

# Manually penalize a user by 2 points (useful in custom scripts)

update_score(request, identifier="alice@example.com", amount=-2)

Tenant Configuration and Expiry

Score bounds and data retention are controlled at the tenant level (models.py#L23-L26):

  • reputation_lower_limit — minimum score floor (default: -5)
  • reputation_upper_limit — maximum score ceiling (default: 5)
  • reputation_expiry() — calculates expiration based on CONFIG["reputation.expiry"] (default: 24 hours)

Policy Evaluation: The passes() Method

When a request reaches a reputation policy, the passes() method (models.py#L45-L56) executes:

  1. Extracts the client IP from the request
  2. Builds a query filtering Reputation rows by:
    • IP address (if check_ip=True)
    • Username/identifier (if check_username=True)
  3. Aggregates the score field across all matching rows using Sum()
  4. Compares the total against threshold
  5. Returns PolicyResult(True) if the aggregate score meets or exceeds the threshold, otherwise PolicyResult(False) with a denial message

# Simplified logic from passes()

reputation = Reputation.objects.aggregate_score(
    request.http_request.ip_address,
    request.user.username
)
if reputation >= self.threshold:
    return PolicyResult(True)

Creating and Managing Reputation Policies

Via REST API

Create a reputation policy that blocks IPs and usernames with scores below -5:

import requests

payload = {
    "name": "Block brute-force attempts",
    "check_ip": True,
    "check_username": True,
    "threshold": -5,  # Policy passes when aggregate score >= -5

}

resp = requests.post(
    "https://auth.example.com/api/v3/policies/reputation/",
    json=payload,
    headers={"Authorization": "Bearer <admin-token>"},
)
print(resp.json())

The serializer validating check_ip and check_username is defined in [api.py](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/api.py#L20-L27).

Querying Current Reputation Scores

Inspect stored reputation data via the API:

curl -H "Authorization: Bearer <admin-token>" \
     "https://auth.example.com/api/v3/reputation/?identifier_in=alice@example.com"

The ReputationViewSet (api.py#L73-L86) provides this endpoint, returning score, ip, identifier, and last update timestamps.

Key Implementation Files

File Purpose
authentik/policies/reputation/models.py ReputationPolicy, Reputation model, and passes() evaluation logic
authentik/policies/reputation/api.py Serializers and viewsets for REST API access
authentik/policies/reputation/signals.py Signal handlers and update_score() utility
website/docs/customize/policies/types/reputation.md End-user documentation

Summary

The authentik reputation scoring system combines these mechanisms:

  • Event-driven scoring: Failed logins decrease scores; successful logins increase them
  • Dual-key storage: Scores are tracked per (IP, identifier) combination
  • Tenant boundedness: Score ranges and expiry are configurable per tenant
  • Aggregate evaluation: Policies sum all relevant scores and compare against a threshold
  • Atomic UPSERT operations: Concurrent updates are handled safely via database-level conflict resolution

Frequently Asked Questions

How does authentik prevent reputation scores from growing indefinitely?

The update_score() function clamps all score adjustments using tenant.reputation_lower_limit and tenant.reputation_upper_limit (typically -5 and +5). This ensures scores stay within predictable bounds regardless of authentication history.

Can I use reputation policy without checking IP addresses?

Yes. Set check_ip=False when configuring the policy. The passes() method will then only aggregate scores matching the username, making the policy identity-focused rather than network-focused.

What happens when a reputation entry expires?

Expired entries are automatically purged based on the reputation_expiry() timestamp. Once expired, the (ip, identifier) pair starts fresh with a neutral score (typically 0), effectively giving users and IPs a clean slate after the configured retention period.

How can I manually reset a user's reputation score?

Either wait for natural expiry, or use the REST API to delete specific Reputation entries. The API endpoint at /api/v3/reputation/ supports DELETE operations on individual records, immediately removing the score influence from future policy evaluations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →