Skip to main content

GET /subjects/:id/lockout

Requires view-users. The subject's run of failed sign-ins as the lockout reads it: failure_count, last_failure_at, locked_until, and locked — whether an attempt made now would be refused, judged by the server's clock so a caller never compares locked_until against its own. A lock that has run out answers locked: false with its count kept, since the run is only forgotten after brute_force_failure_reset_seconds of quiet (the brute-force section of the request paths has the arithmetic). A subject that has never failed answers a zero count and nulls. An unknown subject, one in another tenant, or one with no users row answers 404.

Described at the top of this page, in a tenant lockout-demo made by odudu seed --tenant lockout-demo --client lockout-demo-app --redirect-uri https://app.example/callback --user hana, on ines, a second subject seeded there with odudu seed user and signed in by nothing before this. signin is the helper the section below defines, pointed at this tenant, this client, ines and port 3080 — as run:

AUTHORIZE='http://localhost:3080/tenants/lockout-demo/protocol/openid-connect/auth?response_type=code&client_id=lockout-demo-app&redirect_uri=https%3A%2F%2Fapp.example%2Fcallback&scope=openid&state=xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256'
signin() {
sid=$(curl -sS "$AUTHORIZE" | sed -n 's/.*name="auth_session_id" value="\([^"]*\)".*/\1/p' | head -1)
curl -sS -D - -o body.html \
--data-urlencode "auth_session_id=$sid" \
--data-urlencode "username=ines" \
--data-urlencode "password=$1" \
http://localhost:3080/tenants/lockout-demo/login-actions/authenticate | grep -iE '^(HTTP|location)'
grep -o '<title>[^<]*</title>' body.html || true
}

Read first, then five wrong passwords and the right one — all six refused, the sixth for the lock — then read again:

INES=http://localhost:3080/admin/tenants/lockout-demo/subjects/01a0ec85-0c4c-7ab4-821c-a4fad1d15482
curl -sS -H "Authorization: Bearer $ADMIN_TOKEN" "$INES/lockout"
for attempt in 1 2 3 4 5; do signin 'not the password'; done
signin 'correct horse battery staple'
curl -sS -H "Authorization: Bearer $ADMIN_TOKEN" "$INES/lockout"
{"locked":false,"locked_until":null,"failure_count":0,"last_failure_at":null}
HTTP/1.1 200 OK
<title>Sign in</title>
HTTP/1.1 200 OK
<title>Sign in</title>
HTTP/1.1 200 OK
<title>Sign in</title>
HTTP/1.1 200 OK
<title>Sign in</title>
HTTP/1.1 200 OK
<title>Sign in</title>
HTTP/1.1 200 OK
<title>Sign in</title>
{"locked":true,"locked_until":"2026-09-29T09:37:48.522Z","failure_count":6,"last_failure_at":"2026-09-29T09:35:48.522Z"}

The sixth attempt counted too, so the lock is the doubled one, two minutes. Clearing it, reading again, then an id no user subject in lockout-demo holds — the tenant's own id:

curl -sS -D - -X DELETE -H "Authorization: Bearer $ADMIN_TOKEN" "$INES/lockout"
curl -sS -H "Authorization: Bearer $ADMIN_TOKEN" "$INES/lockout"
curl -sS -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:3080/admin/tenants/lockout-demo/subjects/01a0ec84-9897-7740-a17f-83b4de29cdda/lockout
HTTP/1.1 204 No Content
x-request-id: 01a0ec85-3777-7b4c-bbd6-5aed404a90af
cache-control: no-store
Date: Tue, 29 Sep 2026 09:35:48 GMT
Connection: keep-alive
Keep-Alive: timeout=72

{"locked":false,"locked_until":null,"failure_count":0,"last_failure_at":null}
{"type":"about:blank","title":"Not Found","status":404,"detail":"no user subject 01a0ec84-9897-7740-a17f-83b4de29cdda","instance":"01a0ec85-37a5-7747-b4ff-2114cc1f7d80"}