
# 사용자의 로그인 상태 조회

`GET /v1/users/{user_id}/sign-in-state` · 권한 `users:read`

사용자가 로그인할 수 없을 때 지원팀이 필요로 하는 정보를 반환합니다. 여기에는 사용자의 열린 계정 잠금, 각 로그인 경로에서 아직 진행 중인 실패 횟수, 그리고 마지막 인증 성공 정보가 포함됩니다. 이 호출은 읽기만 수행합니다. 아무것도 변경되지 않으며 감사 기록도 남지 않습니다. 잠금을 해제하려면 [`POST /v1/users/{user_id}:unlock`](unlock.md)을 사용하십시오.

응답에는 카운터의 내부 주체, 이메일 주소, IP 주소, 기기 또는 세션 ID가 포함되지 않습니다. 사용자의 세션을 확인하려면 [`GET /v1/users/{user_id}/sessions`](list-sessions.md)를 사용하십시오.

## 조직 범위

조직 관리자를 위한 멤버 진단 화면을 구성하려면 `org_id`를 전달하십시오. 이때 키에는 `organizations:read` 권한도 필요합니다. 이 권한이 없으면 아무것도 읽기 전에 `403 insufficient_permissions`로 요청이 실패합니다. 사용자가 해당 조직의 멤버가 아니라면(`invited`, `active`, `suspended` 멤버십이 없는 경우) 존재하지 않는 사용자에 대한 응답과 동일한 `404 not_found`로 요청이 실패합니다. 사용자가 멤버인 경우 `last_sign_in`은 해당 조직 맥락의 세션에서만 가져오며, `failures`는 실패 카운터가 어떤 조직에도 속하지 않으므로 항상 빈 배열입니다. 잠금은 모든 맥락에서 계정의 로그인을 차단하므로 여전히 반환됩니다. `org_id`를 지정하지 않으면 응답은 변경되지 않습니다.

## 요청

| 파라미터 | 위치 | 필수 | 설명 |
|---|---|---|---|
| `user_id` | path | 예 | 사용자 ID, 접두사 `usr_`. |
| `org_id` | query | 아니요 | 조직 ID, 접두사 `org_`. 보기를 해당 조직으로 제한합니다. [조직 범위](#조직-범위)를 참고하십시오. `organizations:read` 권한이 필요합니다. |

## 응답

`200 OK`.

```json
{
  "locked": true,
  "locked_until": "2026-10-20T10:00:00Z",
  "locks": [
    {"id": "lck_0a1b2c3d4e5f6g7h8j9k0m1n2p", "path": "password",
     "locked_at": "2026-10-20T09:45:00Z", "expires_at": "2026-10-20T10:00:00Z"}
  ],
  "failures": [
    {"path": "totp", "count": 3, "window_ends_at": "2026-10-20T09:55:00Z"}
  ],
  "last_sign_in": {"authenticated_at": "2026-10-19T18:12:04Z", "amr": ["pwd", "otp"], "country": "DE"}
}
```

| 필드 | 설명 |
|---|---|
| `locked` | 하나 이상의 로그인 경로가 잠겨 있으면 `true`입니다. |
| `locked_until` | 마지막 열린 잠금이 끝나는 시각입니다. 잠금이 없으면 `null`입니다. |
| `locks` | 열린 잠금 목록으로, 오래된 순서이며 경로(`password`, `totp`, `sms_2fa`, `email_2fa`, `otp`, `recovery_code`)당 최대 하나입니다. |
| `failures` | 마지막 성공 또는 마지막 잠금 이후 경로별 실패 시도 횟수입니다. 집계 기간이 진행되는 동안에만 표시됩니다. `window_ends_at` 이후에는 횟수가 다시 0부터 시작합니다. 비밀번호 기간은 환경의 `brute_force.window_minutes` 값입니다. |
| `last_sign_in` | 아직 보관 중인 세션 가운데 가장 최근의 인증 성공 정보입니다. `authenticated_at`(같은 세션에서 재인증하면 이 값이 앞으로 갱신됩니다), 인증 방법(`amr`, RFC 8176), 세션의 마지막으로 알려진 국가(알 수 없으면 `null`)를 포함합니다. 보관 중인 세션이 없으면 `null`입니다. 사용자가 한 번도 로그인하지 않았거나 마지막 세션이 30일 넘게 전에 종료된 경우입니다. |

## 오류

모든 오류는 안정적인 `code`를 포함한 `application/problem+json` 본문으로 반환됩니다.

| HTTP | `code` | 발생 조건 |
|---|---|---|
| 404 | `not_found` | 이 환경에 사용자가 존재하지 않거나(또는 ID 형식이 잘못된 경우), `org_id`를 지정했는데 사용자가 해당 조직의 멤버가 아닌 경우입니다. |
| 401 | `unauthenticated` | API 키가 없거나, 유효하지 않거나, 취소되었습니다. |
| 403 | `insufficient_permissions` | 키에 `users:read` 권한이 없거나, `org_id`를 지정했는데 키에 `organizations:read` 권한이 없습니다. |
| 429 | `rate_limited` | 요청 한도를 초과했습니다. `Retry-After`를 따르십시오. |
| 503 | `service_unavailable` | 의존 서비스를 일시적으로 사용할 수 없습니다. 나중에 다시 시도하십시오. |

## 예제

```bash
curl https://api.eu.keenauth.com/v1/users/usr_0a1b2c3d4e5f6g7h8j9k0m1n2p/sign-in-state \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY"
```

CLI: `keenauth users sign-in-state usr_0a1b2c3d4e5f6g7h8j9k0m1n2p`
