# How the Authentik Reputation Policy Works for User Reputation Scoring

> Discover how the Authentik reputation policy scores user reputation by analyzing login success and failure data. Learn to enhance security with this powerful feature.

- Repository: [Authentik Security/authentik](https://github.com/goauthentik/authentik)
- Tags: deep-dive
- Published: 2026-08-14

---

**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](https://github.com/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)](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`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/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)](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`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/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

```python
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`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/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`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/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

```python

# 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:

```python
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/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:

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

```

The `ReputationViewSet` ([`api.py#L73-L86`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/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`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/models.py) | `ReputationPolicy`, `Reputation` model, and `passes()` evaluation logic |
| [`authentik/policies/reputation/api.py`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/api.py) | Serializers and viewsets for REST API access |
| [`authentik/policies/reputation/signals.py`](https://github.com/goauthentik/authentik/blob/main/authentik/policies/reputation/signals.py) | Signal handlers and `update_score()` utility |
| [`website/docs/customize/policies/types/reputation.md`](https://github.com/goauthentik/authentik/blob/main/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.