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 calculationscheck_username— whether to include the username (identifier) in calculationsthreshold— 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:
login_failed→ callsupdate_score(..., amount=-1)identification_failed→ callsupdate_score(..., amount=-1)user_logged_in→ callsupdate_score(..., amount=+1)
The update_score() Function
The update_score() function (signals.py#L23-L48) is the central scoring engine. It performs four operations:
- Extracts the client IP from the request metadata
- Clamps the score delta using tenant-wide limits (
tenant.reputation_lower_limitandtenant.reputation_upper_limit) - Executes an UPSERT (
ON CONFLICT … UPDATE) on the(ip, identifier)pair, atomically adjusting the score while keeping it within bounds - 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 onCONFIG["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:
- Extracts the client IP from the request
- Builds a query filtering
Reputationrows by:- IP address (if
check_ip=True) - Username/identifier (if
check_username=True)
- IP address (if
- Aggregates the
scorefield across all matching rows usingSum() - Compares the total against
threshold - Returns
PolicyResult(True)if the aggregate score meets or exceeds the threshold, otherwisePolicyResult(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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →