# Failed Logins

LLMS index: [llms.txt](/llms.txt)

---

<div id="pslens-context-panel" class="card border-info mb-4 d-none">
  <div class="card-header bg-light text-info py-2 fw-bold d-flex align-items-center border-bottom border-info-subtle">
    <i class="bi bi-info-circle-fill me-2"></i>
    <span>Tailored Operational Context</span>
  </div>
  <div class="card-body p-0">
    <ul class="list-group list-group-flush">
      <li id="row-db" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Target Database:</strong>
        <span id="ctx-db" class="badge bg-secondary font-monospace">&mdash;</span>
      </li>
      <li id="row-type" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Context Type:</strong>
        <span id="ctx-type" class="badge bg-light text-dark border font-monospace text-uppercase">&mdash;</span>
      </li>
      <li id="row-severity" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Alert Severity:</strong>
        <span id="ctx-severity" class="badge">&mdash;</span>
      </li>
      <li id="row-time" class="list-group-item d-flex align-items-center justify-content-between py-2 d-none">
        <strong>Triggered Time:</strong>
        <span id="ctx-time" class="text-muted small">&mdash;</span>
      </li>
      <li id="row-details" class="list-group-item py-2 d-none">
        <strong id="label-details" class="d-block mb-1">Firing Context:</strong>
        <code id="ctx-details" class="d-block p-2 bg-light border rounded small" style="white-space: pre-wrap; word-break: break-all;">&mdash;</code>
      </li>
    </ul>
  </div>
</div>

<script>
  (function() {
    const params = new URLSearchParams(window.location.search);
    const metadata = params.get('metadata');
    if (!metadata) return;

    try {
      
      const base64 = metadata.replace(/-/g, '+').replace(/_/g, '/');
      const jsonStr = decodeURIComponent(escape(window.atob(base64)));
      const data = JSON.parse(jsonStr);

      if (data) {
        let hasData = false;

        if (data.db) {
          document.getElementById('ctx-db').textContent = data.db;
          document.getElementById('row-db').classList.remove('d-none');
          hasData = true;
        }

        if (data.type) {
          document.getElementById('ctx-type').textContent = data.type;
          document.getElementById('row-type').classList.remove('d-none');
          hasData = true;
        }

        if (data.severity) {
          const severityBadge = document.getElementById('ctx-severity');
          const severity = data.severity.toLowerCase();
          severityBadge.textContent = severity.toUpperCase();
          if (severity === 'critical') {
            severityBadge.className = 'badge bg-danger';
          } else if (severity === 'warning') {
            severityBadge.className = 'badge bg-warning text-dark';
          } else {
            severityBadge.className = 'badge bg-info';
          }
          document.getElementById('row-severity').classList.remove('d-none');
          hasData = true;
        }

        if (data.t) {
          const date = new Date(data.t * 1000);
          document.getElementById('ctx-time').textContent = date.toLocaleString();
          document.getElementById('row-time').classList.remove('d-none');
          hasData = true;
        }

        if (data.details) {
          document.getElementById('ctx-details').textContent = data.details;

          
          const labelDetails = document.getElementById('label-details');
          if (data.type === 'object') {
            labelDetails.textContent = 'Object Metadata Details:';
          } else if (data.type === 'report') {
            labelDetails.textContent = 'Report Description:';
          } else {
            labelDetails.textContent = 'Firing Context:';
          }

          document.getElementById('row-details').classList.remove('d-none');
          hasData = true;
        }

        if (hasData) {
          document.getElementById('pslens-context-panel').classList.remove('d-none');
        }
      }
    } catch (e) {
      console.error('Failed to parse operational context metadata:', e);
    }
  })();
</script>


## Failed Logins Alert

**Alert ID:** `failed_logins`
**Category:** Security
**Default threshold:** 5 failed attempts

## What This Alert Detects

This alert finds PeopleSoft users with excessive failed login attempts by querying the **PSPTLOGINAUDIT** table. It only reports users whose most recent login attempt was a failure (PT_SIGNON_STATUS = 1).

PSPTLOGINAUDIT stores only the **last** login state per user. Once a user successfully logs in, their failure count resets. This means the alert reflects the current state: users who are actively failing to log in right now.

A high number of failed logins may indicate:

- A brute-force attack against a user account
- A user who has forgotten their password
- An integration or batch account with stale credentials
- An account lockout situation that needs admin attention

## Severity Logic

|               Condition               | Severity |
| ------------------------------------- | -------- |
| Failed logins >= `thresholdCount`     | Warning  |
| Failed logins >= `thresholdCount x 2` | Critical |

For example, with the default threshold of 5:

- A user with 6 failed logins -> **Warning**
- A user with 10 or more failed logins -> **Critical**

## What Gets Checked

The alert queries PSPTLOGINAUDIT for rows where:

- `PT_SIGNON_STATUS = '1'` (last attempt was a failure)
- `FAILEDLOGINS >= threshold` (failed count meets or exceeds the configured threshold)

Results are ordered by FAILEDLOGINS descending (highest failure counts first).

## Alert Details

Each alert item includes:

- Signon ID (PTSIGNONID) — the username entered at the login screen
- OPRID — the resolved PeopleSoft user ID
- Number of failed login attempts
- Authentication type (Token/SSO, Signon PeopleCode, or Standard)
- Timestamp of the last failed login attempt
- A link to the User detail page (when the OPRID is resolved)

## Configuration

```yaml
alerts:
  checks:
    failed_logins:
      enabled: true
      thresholdCount: 5    # Failed attempts before flagging as Warning
```

|     Setting      | Default |                                     Description                                      |
| ---------------- | ------- | ------------------------------------------------------------------------------------ |
| `thresholdCount` | `5`     | Number of failed logins to trigger a Warning alert. Critical fires at 2x this value. |

## How to Respond

1. Click the alert link to go to the User detail page for the affected account
2. Check the authentication type. Token/SSO failures may indicate a misconfigured integration
3. Review the timestamp. Recent failures are more concerning than old ones
4. Check if the user's account is locked (ACCTLOCK in PSOPRDEFN)
5. If the failures look like a brute-force attempt, consider locking the account and investigating the source
6. For legitimate users, help them reset their password and unlock their account

## PeopleSoft Table Reference

This alert queries the **PSPTLOGINAUDIT** Tools table. For more details on this table, see [Exploring the PSPTLOGINAUDIT Tools Table](https://www.cedarhillsgroup.com/knowledge-base/kbarticles/exploring-the-psptloginaudit-tools-table/).

### Prerequisites

The PSPTLOGINAUDIT table must be **whitelisted** in the PeopleSoft SWS framework on each target environment. If the table is not whitelisted, this alert will log an error on each check cycle but will not affect other alerts.
