Security API
The Security API provides tools for protecting the tenant against unauthorized access and malicious activity. Administrators can maintain IP allow/block lists, review suspicious login events, configure CAPTCHA verification, and manage account lockouts.
Auris evaluates security rules during every authentication attempt in this order: IP rules (block/allow) -> CAPTCHA verification -> rate limiting -> credential validation -> suspicious login analysis -> adaptive MFA. Each layer operates independently and can be configured separately.
IP Rules
IP rules define allow and block lists using CIDR notation. Block rules always take precedence over allow rules. Rules can be scoped to the entire tenant or to a specific application.
List IP Rules
/api/ip-rulesRequires: admin:allList all IP allow/block rules configured for the tenant. Supports filtering by rule type, scope, and active status. Rules are ordered by creation date descending.
Query parameters
| Parameter | Type | Description |
|---|---|---|
type | string | Filter by rule type: ALLOW or BLOCK |
scope | string | Filter by scope: TENANT or APPLICATION |
isActive | boolean | Filter by active status |
search | string | Search by CIDR, label, or note |
applicationId | string | Filter rules scoped to a specific application |
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50, max: 100) |
Success response
{
"success": true,
"data": [
{
"id": "ipr_abc123",
"cidr": "203.0.113.0/24",
"type": "BLOCK",
"scope": "TENANT",
"applicationId": null,
"label": "Known bad actor range",
"note": "Blocked after brute-force campaign on 2025-02-10",
"isTemporary": true,
"expiresAt": "2025-03-10T00:00:00Z",
"isActive": true,
"createdAt": "2025-02-10T14:30:00Z",
"updatedAt": "2025-02-10T14:30:00Z"
},
{
"id": "ipr_def456",
"cidr": "10.0.0.0/8",
"type": "ALLOW",
"scope": "TENANT",
"applicationId": null,
"label": "Corporate VPN",
"note": "Internal network range",
"isTemporary": false,
"expiresAt": null,
"isActive": true,
"createdAt": "2025-01-15T09:00:00Z",
"updatedAt": "2025-01-15T09:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 2,
"totalPages": 1
}
}Create IP Rule
/api/ip-rulesRequires: admin:allCreate a new IP allow or block rule. CIDR notation is required — use /32 for a single IP address.
Block rules always take precedence over allow rules during evaluation.
Request body
{
"cidr": "192.0.2.0/24",
"type": "BLOCK",
"scope": "TENANT",
"label": "Datacenter range",
"note": "Blocked due to automated scraping activity",
"isTemporary": true,
"expiresAt": "2025-04-01T00:00:00Z"
}| Field | Type | Required | Description |
|---|---|---|---|
cidr | string | Yes | IP address or range in CIDR notation (e.g., 10.0.0.1/32, 192.168.0.0/16) |
type | string | Yes | ALLOW or BLOCK |
scope | string | No | TENANT (applies to all apps) or APPLICATION (requires applicationId) |
applicationId | string | No | Required when scope is APPLICATION |
label | string | No | Human-readable label for the rule |
note | string | No | Administrative note or reason |
isTemporary | boolean | No | Whether the rule expires automatically (default: false) |
expiresAt | string | No | ISO 8601 expiry date. Required when isTemporary is true |
Success response
{
"success": true,
"data": {
"id": "ipr_jkl012",
"cidr": "192.0.2.0/24",
"type": "BLOCK",
"scope": "TENANT",
"applicationId": null,
"label": "Datacenter range",
"note": "Blocked due to automated scraping activity",
"isTemporary": true,
"expiresAt": "2025-04-01T00:00:00Z",
"isActive": true,
"createdAt": "2025-02-18T10:00:00Z",
"updatedAt": "2025-02-18T10:00:00Z"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid CIDR notation, missing required fields, or invalid scope/type combination |
DUPLICATE_RULE | 409 | A rule with the same CIDR and scope already exists |
Temporary rules are automatically cleaned up after their expiresAt timestamp. You do not need to manually delete them.
Update IP Rule
/api/ip-rules/[id]Requires: admin:allUpdate an existing IP rule. All fields are optional — only provided fields are updated. Changing the CIDR or type of a rule takes effect immediately for subsequent login attempts.
Request body
{
"label": "Updated label",
"note": "Extended block after continued activity",
"isActive": false
}Success response
{
"success": true,
"data": {
"id": "ipr_abc123",
"cidr": "203.0.113.0/24",
"type": "BLOCK",
"scope": "TENANT",
"applicationId": null,
"label": "Updated label",
"note": "Extended block after continued activity",
"isTemporary": true,
"expiresAt": "2025-03-10T00:00:00Z",
"isActive": false,
"createdAt": "2025-02-10T14:30:00Z",
"updatedAt": "2025-02-18T10:30:00Z"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | IP rule does not exist |
VALIDATION_ERROR | 400 | Invalid field value |
Delete IP Rule
/api/ip-rules/[id]Requires: admin:allPermanently delete an IP rule. The rule is removed immediately and will no longer be evaluated during authentication attempts.
Success response
{
"success": true
}Error codes
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | IP rule does not exist |
IP Rule Statistics
/api/ip-rules/statsRequires: admin:allGet statistics about IP rules configured for the tenant.
Success response
{
"success": true,
"data": {
"total": 12,
"active": 10,
"allow": 4,
"block": 8,
"temporary": 3
}
}Test IP Against Rules
/api/ip-rules/testRequires: admin:allTest an IP address against the current set of rules and return the evaluation result. Useful for verifying rule configuration before applying it to production traffic.
Request body
{
"ip": "203.0.113.50",
"applicationId": "app_prod001"
}| Field | Type | Required | Description |
|---|---|---|---|
ip | string | Yes | IP address to test |
applicationId | string | No | Scope the evaluation to a specific application |
Success response
{
"success": true,
"data": {
"allowed": false,
"matchedRule": {
"id": "ipr_abc123",
"cidr": "203.0.113.0/24",
"type": "BLOCK",
"scope": "TENANT",
"label": "Known bad actor range"
}
}
}Suspicious Login Events
Auris monitors login attempts for anomalous behavior using five detection methods: new device, new IP address, new country, impossible travel, and VPN/proxy usage. When suspicious activity is detected, an event is logged and the configured action is taken (log only, require MFA, or block).
List Suspicious Login Events
/api/suspicious-login/eventsRequires: admin:allList suspicious login events across the tenant. Events are ordered by creation date descending.
Use the reviewed filter to find events that need administrator attention.
Query parameters
| Parameter | Type | Description |
|---|---|---|
userId | string | Filter events for a specific user |
severity | string | Filter by severity: low, medium, high, critical |
reason | string | Filter by detection reason: new_device, new_ip, new_country, impossible_travel, vpn_detected |
reviewed | boolean | true for reviewed events, false for unreviewed |
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50, max: 100) |
Success response
{
"success": true,
"data": [
{
"id": "sle_abc123",
"userId": "usr_xyz789",
"userEmail": "[email protected]",
"reason": "impossible_travel",
"severity": "high",
"actionTaken": "require_mfa",
"details": {
"previousLocation": { "country": "Italy", "city": "Rome", "lat": 41.9028, "lng": 12.4964 },
"currentLocation": { "country": "Brazil", "city": "Sao Paulo", "lat": -23.5505, "lng": -46.6333 },
"distanceKm": 9187,
"timeDiffMinutes": 45,
"requiredSpeedKmh": 12249
},
"ipAddress": "198.51.100.42",
"reviewed": false,
"reviewedAt": null,
"reviewedBy": null,
"createdAt": "2025-02-18T09:15:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 47,
"totalPages": 1
}
}Mark Event as Reviewed
/api/suspicious-login/events/[id]/reviewRequires: admin:allMark a suspicious login event as reviewed. This is an administrative acknowledgment and does not affect the user’s access. Use this to track which events have been investigated.
Success response
{
"success": true,
"data": {
"id": "sle_abc123",
"reviewed": true,
"reviewedAt": "2025-02-18T11:00:00Z",
"reviewedBy": "usr_admin001"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | Event does not exist |
ALREADY_REVIEWED | 400 | Event has already been marked as reviewed |
Suspicious Login Statistics
/api/suspicious-login/statsRequires: admin:allGet statistics about suspicious login events for the tenant.
Success response
{
"success": true,
"data": {
"totalEvents": 142,
"unreviewedEvents": 12,
"eventsLast24h": 8,
"byReason": {
"new_device": 45,
"new_ip": 60,
"new_country": 20,
"impossible_travel": 10,
"vpn_detected": 7
},
"bySeverity": {
"low": 80,
"medium": 40,
"high": 18,
"critical": 4
}
}
}Get Detection Config
/api/suspicious-login/configRequires: admin:allRetrieve the current suspicious login detection configuration for the tenant.
Success response
{
"success": true,
"data": {
"detectNewDevice": true,
"detectNewIp": true,
"detectNewCountry": true,
"detectImpossibleTravel": true,
"detectVpn": false,
"actionOnNewDevice": "log",
"actionOnNewIp": "log",
"actionOnNewCountry": "require_mfa",
"actionOnImpossibleTravel": "require_mfa",
"actionOnVpn": "log",
"maxTravelSpeedKmh": 900,
"geoIpProvider": "ip-api",
"updatedAt": "2025-02-01T12:00:00Z"
}
}| Field | Type | Description |
|---|---|---|
detectNewDevice | boolean | Flag logins from previously unseen device fingerprints |
detectNewIp | boolean | Flag logins from previously unseen IP addresses |
detectNewCountry | boolean | Flag logins from a new country |
detectImpossibleTravel | boolean | Flag when consecutive logins are geographically impossible given the time elapsed |
detectVpn | boolean | Flag logins from known VPN/proxy/datacenter IPs |
actionOn* | string | Action to take: log (record only), require_mfa (force 2FA), block (deny access) |
maxTravelSpeedKmh | integer | Speed threshold for impossible travel detection (default: 900 km/h) |
Update Detection Config
/api/suspicious-login/configRequires: admin:allUpdate the suspicious login detection configuration. All fields are optional. Changes take effect immediately for subsequent login attempts.
Request body
{
"detectVpn": true,
"actionOnVpn": "require_mfa",
"actionOnImpossibleTravel": "block",
"maxTravelSpeedKmh": 1000
}Success response
{
"success": true,
"data": {
"detectNewDevice": true,
"detectNewIp": true,
"detectNewCountry": true,
"detectImpossibleTravel": true,
"detectVpn": true,
"actionOnNewDevice": "log",
"actionOnNewIp": "log",
"actionOnNewCountry": "require_mfa",
"actionOnImpossibleTravel": "block",
"actionOnVpn": "require_mfa",
"maxTravelSpeedKmh": 1000,
"geoIpProvider": "ip-api",
"updatedAt": "2025-02-18T11:30:00Z"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid action value or maxTravelSpeedKmh out of range (100-5000) |
Setting actionOnImpossibleTravel or actionOnNewCountry to block can lock out legitimate users who travel frequently or use mobile networks. Consider using require_mfa instead, which adds a verification step without denying access entirely.
CAPTCHA
CAPTCHA verification adds a human challenge to authentication flows. Auris supports three providers: Cloudflare Turnstile, hCaptcha, and reCAPTCHA v3. CAPTCHA can be triggered on every attempt, only after suspicious activity, or after a configurable number of failed login attempts.
Verify CAPTCHA Token
/api/captcha/verifyVerify a CAPTCHA token against the tenant’s configured provider. This is a public endpoint — no authentication is required. It is called by client-side login flows immediately after the user completes the CAPTCHA challenge.
Request body
{
"token": "0.Abcd1234...",
"action": "login",
"tenantId": "ten_abc123"
}| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | CAPTCHA token returned by the provider’s widget |
action | string | Yes | The action being protected (e.g., login, register, reset) |
tenantId | string | Yes | Tenant identifier — used to look up the provider configuration |
Success response
{
"success": true,
"data": {
"valid": true,
"score": 0.9,
"action": "login"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Missing token, action, or tenantId |
CAPTCHA_FAILED | 422 | Token is invalid or score is below the configured threshold |
CAPTCHA Verifications
/api/captcha/verificationsRequires: admin:allList CAPTCHA verification attempts for the tenant, ordered by most recent first.
Query parameters
| Parameter | Type | Description |
|---|---|---|
success | boolean | Filter by outcome: true for passed, false for failed |
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50, max: 100) |
Success response
{
"success": true,
"data": [
{
"id": "cvr_abc123",
"tenantId": "ten_abc123",
"action": "login",
"ipAddress": "203.0.113.50",
"success": true,
"score": 0.9,
"createdAt": "2025-02-18T09:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 1240,
"totalPages": 25
}
}CAPTCHA Statistics
/api/captcha/statsRequires: admin:allGet CAPTCHA verification statistics for the last 24 hours.
Success response
{
"success": true,
"data": {
"totalLast24h": 850,
"passedLast24h": 820,
"failedLast24h": 30,
"passRate": 0.965
}
}Get CAPTCHA Config
/api/captcha/configRequires: admin:allRetrieve the current CAPTCHA configuration for the tenant.
Success response
{
"success": true,
"data": {
"provider": "CLOUDFLARE_TURNSTILE",
"trigger": "ON_SUSPICIOUS",
"siteKey": "0x4AAAAAAA...",
"scoreThreshold": 0.5,
"enableOnLogin": true,
"enableOnRegister": true,
"enableOnReset": false,
"updatedAt": "2025-02-15T10:00:00Z"
}
}The secretKey is never returned in API responses. It can only be set via the update endpoint.
Update CAPTCHA Config
/api/captcha/configRequires: admin:allUpdate the CAPTCHA configuration. All fields are optional. Set provider to null to disable
CAPTCHA entirely. The siteKey and secretKey must be valid for the selected provider.
Request body
{
"provider": "CLOUDFLARE_TURNSTILE",
"trigger": "AFTER_FAILURES",
"siteKey": "0x4AAAAAAA_your_site_key",
"secretKey": "0x4AAAAAAA_your_secret_key",
"scoreThreshold": 0.5,
"enableOnLogin": true,
"enableOnRegister": true,
"enableOnReset": true
}| Field | Type | Description |
|---|---|---|
provider | string | CLOUDFLARE_TURNSTILE, HCAPTCHA, RECAPTCHA_V3, or null to disable |
trigger | string | ALWAYS (every attempt), ON_SUSPICIOUS (after suspicious activity detected), AFTER_FAILURES (after N failed logins) |
siteKey | string | Public site key from the CAPTCHA provider |
secretKey | string | Secret key from the CAPTCHA provider (write-only, never returned) |
scoreThreshold | number | Score threshold for reCAPTCHA v3 (0.0 - 1.0, default: 0.5). Ignored for other providers |
enableOnLogin | boolean | Enable CAPTCHA on the login page |
enableOnRegister | boolean | Enable CAPTCHA on the registration page |
enableOnReset | boolean | Enable CAPTCHA on the password reset page |
Success response
{
"success": true,
"data": {
"provider": "CLOUDFLARE_TURNSTILE",
"trigger": "AFTER_FAILURES",
"siteKey": "0x4AAAAAAA_your_site_key",
"scoreThreshold": 0.5,
"enableOnLogin": true,
"enableOnRegister": true,
"enableOnReset": true,
"updatedAt": "2025-02-18T12:00:00Z"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Invalid provider, missing siteKey/secretKey when provider is set, or scoreThreshold out of range |
Account Lockouts
When a user exceeds the maxAttempts threshold configured in attack protection settings, their account is temporarily locked. Administrators can view locked accounts and manually unlock them.
List Locked Accounts
/api/attack-protection/lockoutsRequires: admin:allList account lockout records for the tenant. By default returns only active (unresolved) lockouts.
Use the resolved parameter to include previously resolved lockouts.
Query parameters
| Parameter | Type | Description |
|---|---|---|
resolved | boolean | false (default) for active lockouts only, true for resolved lockouts |
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50, max: 100) |
Success response
{
"success": true,
"data": [
{
"id": "lock_abc123",
"userId": "usr_xyz789",
"email": "[email protected]",
"reason": "brute_force",
"lockedAt": "2025-02-18T09:00:00Z",
"unlocksAt": "2025-02-18T09:30:00Z",
"resolved": false
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 2,
"totalPages": 1
}
}Unlock Account
/api/attack-protection/lockouts/[id]/unlockRequires: admin:allImmediately unlock a user account. The failed attempt counter is reset to zero. The user can attempt to log in again immediately after being unlocked.
Success response
{
"success": true,
"data": {
"unlocked": true,
"userId": "usr_xyz789"
}
}Error codes
| Code | HTTP | Description |
|---|---|---|
NOT_FOUND | 404 | User is not currently locked out |
Account lockouts expire automatically based on the lockoutDurationMins configured in attack protection settings. Manual unlock is only needed when a legitimate user is locked out and cannot wait for the lockout to expire.
Attack Protection Configuration
/api/attack-protection/configRequires: admin:allRetrieve the current attack protection configuration for the tenant, covering brute-force lockout, breached password checks, and suspicious IP blocking.
Success response
{
"success": true,
"data": {
"bruteForce": {
"enabled": true,
"maxAttempts": 10,
"lockoutDurationMins": 30,
"resetAfterMins": 60
},
"breachedPassword": {
"enabled": false,
"blockSeverity": "warn"
},
"suspiciousIp": {
"enabled": false,
"maxFailedFromIp": 50,
"blockDurationMins": 60
}
}
}| Field | Type | Description |
|---|---|---|
bruteForce.enabled | boolean | Enable brute-force lockout protection |
bruteForce.maxAttempts | integer | Number of failed attempts before lockout |
bruteForce.lockoutDurationMins | integer | How long the lockout lasts (minutes) |
bruteForce.resetAfterMins | integer | Inactivity window that resets the failed attempt counter |
breachedPassword.enabled | boolean | Check passwords against known-breached password databases (HIBP) |
breachedPassword.blockSeverity | string | block (deny login), warn (alert user), or off |
suspiciousIp.enabled | boolean | Block IPs with excessive failed attempts across the tenant |
suspiciousIp.maxFailedFromIp | integer | Threshold of failed attempts from one IP before blocking |
suspiciousIp.blockDurationMins | integer | How long the IP block lasts (minutes) |
/api/attack-protection/configRequires: admin:allUpdate the attack protection configuration. All fields are optional — only provided fields are merged. Changes take effect immediately for subsequent login attempts.
Request body
{
"bruteForce": {
"maxAttempts": 5,
"lockoutDurationMins": 15
},
"breachedPassword": {
"enabled": true,
"blockSeverity": "block"
}
}Success response
{
"success": true,
"data": {
"bruteForce": {
"enabled": true,
"maxAttempts": 5,
"lockoutDurationMins": 15,
"resetAfterMins": 60
},
"breachedPassword": {
"enabled": true,
"blockSeverity": "block"
},
"suspiciousIp": {
"enabled": false,
"maxFailedFromIp": 50,
"blockDurationMins": 60
}
}
}Login Attempts
/api/attack-protection/attemptsRequires: admin:allList login attempts across the tenant, ordered by most recent first. Supports filtering by email, IP address, and success status.
Query parameters
| Parameter | Type | Description |
|---|---|---|
email | string | Filter attempts for a specific email address (partial match) |
ipAddress | string | Filter attempts from a specific IP address (exact match) |
success | boolean | true for successful logins, false for failed attempts |
page | integer | Page number (default: 1) |
limit | integer | Items per page (default: 50, max: 100) |
Success response
{
"success": true,
"data": [
{
"id": "lat_abc123",
"userId": "usr_xyz789",
"email": "[email protected]",
"ipAddress": "203.0.113.50",
"userAgent": "Mozilla/5.0 ...",
"success": false,
"reason": "invalid_password",
"createdAt": "2025-02-18T09:05:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 324,
"totalPages": 7
}
}Attack Protection Statistics
/api/attack-protection/statsRequires: admin:allGet attack protection statistics for the last 24 hours.
Success response
{
"success": true,
"data": {
"attemptsLast24h": 1240,
"failedAttemptsLast24h": 87,
"activeLockouts": 3,
"blockedIps": 5
}
}| Field | Type | Description |
|---|---|---|
attemptsLast24h | integer | Total login attempts in the last 24 hours |
failedAttemptsLast24h | integer | Failed login attempts in the last 24 hours |
activeLockouts | integer | Number of accounts currently locked out |
blockedIps | integer | Number of IPs blocked due to excessive failed attempts in the last 24 hours |
Security Evaluation Order
During every authentication attempt, Auris evaluates security layers in this order:
- IP Rules — If the client IP matches a BLOCK rule, the request is denied immediately with
403 IP_BLOCKED. - CAPTCHA — If CAPTCHA is configured and the trigger condition is met, the client must provide a valid CAPTCHA token.
- Rate Limiting — Sliding window rate limits are checked (configurable per tier).
- Credential Validation — Username/password or other credential verification via Keycloak.
- Brute Force / Lockout — Failed attempt counter is incremented. If threshold is reached, account is locked.
- Suspicious Login Analysis — Post-authentication analysis of device, IP, geography, and travel patterns.
- Adaptive MFA — Risk scoring may trigger step-up authentication if the risk score exceeds thresholds.
Each layer is independent and can be disabled without affecting the others.
Permissions Reference
| Permission | Description |
|---|---|
admin:all | Manage IP rules, review suspicious login events, configure CAPTCHA, and unlock accounts |
Security management operations are grouped under the admin:all permission. This ensures that only administrators with full administrative access can modify security settings that affect authentication for all users in the tenant.
Related
- Adaptive MFA & Risk Scoring — How risk scoring drives security decisions
- Attack Protection — Configure brute-force and suspicious login protection
- Threat Protection Setup — IP rules, CAPTCHA, and bot detection
- Security Settings — Manage all security features from the Console
- Risk Scoring & Adaptive MFA — Configure risk rules and thresholds