Security Events
The Security Events page is an append-only log of authentication, identity, credential, and policy-change events. It is the primary source of SOC 2 evidence for change management and access control and is read-only; events cannot be edited or deleted from the UI.
Access
The page requires the View Audit Logs permission. This is granted to the Admin role by default. Find it on the Security Events tab of the Audit page (/audit/security-events), next to Change History.
What is logged
Tagnet writes a security event in the following situations. Events are grouped by category below.
Authentication events
| Event Type | When it fires |
|---|---|
UserLoginSucceeded | A user successfully signed in. Details include the auth scheme (cookie or jwt). Actor is the authenticated user. |
UserLoginFailed | A sign-in attempt failed. Details include the failure reason (bad_password, account_disabled, account_locked, or user_not_found) and the attempted email. The attempted password is never logged. See the AuthAudit policy note regarding user_not_found events. |
UserLogout | A user signed out. Details include the auth scheme and, for an API revoke-all, the number of refresh tokens revoked. |
UserAccountLocked | A user account was locked out because the failed sign-in threshold was exceeded. Actor type is System; Identity performs the lockout automatically, not a human. Details include the user id, email, and lockout expiry timestamp. |
UserAccountUnlocked | A locked account was manually unlocked by an administrator from the Users page. Actor is the admin. Details include the target user id and email. |
Account lifecycle events
| Event Type | When it fires |
|---|---|
UserCreated | A new user account was created, either by an admin directly or when an invited user accepted their invitation. Actor is always the admin who initiated the creation; for invitation-sourced accounts, this is the inviting admin recorded on the invitation, not the new user. Details include the new user id, email, assigned roles, and source (admin or invitation). |
UserDeactivated | A user account was deactivated. Inactive users cannot sign in. Actor is the admin. |
UserReactivated | A previously deactivated account was reactivated. Actor is the admin. |
UserInvited | An administrator created an invitation for a new user. Details include the invitation id, target email (if specified), role, and assigned access scope ids. The plaintext invitation token is never logged. |
UserInvitationAccepted | An invitee completed the accept-invite flow and a user account was created. Actor is the new user. Details include the invitation id and the new user id. |
Credential events
| Event Type | When it fires |
|---|---|
UserPasswordChanged | A user changed their own password using the in-app self-service change-password flow. Actor is the user. |
UserPasswordResetByAdmin | An administrator reset another user's password from the Users page. Actor is the admin. Details include the target user id and email. The password itself is never logged. Distinct from UserPasswordReset, which covers the Recover Account out-of-band path. |
UserPasswordReset | A user's password was reset from the Recover Account page in the Tagnet Admin Console. Actor type is AdminTool; the actor field contains the Windows username of the person who ran the recovery. Details include the user's email, username, and the number of active sessions revoked. |
UserPasswordResetRequested | A user submitted their email through the self-service Forgot Password form and the email matched an existing active account. A reset link was dispatched by email. Actor is the resolved user. Details include the user id and email. Note: this event is only emitted when the email resolves to a real active account; anonymous misses are silently no-oped and not logged. The reset token itself is never logged. |
UserPasswordResetCompleted | A user successfully redeemed a self-service password reset link and set a new password. Actor is the user. Distinct from UserPasswordChanged (which requires the current password). Details include the user id and email. The new password is never logged. |
UserApiKeyCreated | A new API key was created (including via regenerate, in which case the source field in Details will be regenerate). Details include the key id, name, role, key prefix, and expiry. The raw key material is never logged. |
UserApiKeyUpdated | An API key's role, access scopes, or expiry was changed via the edit flow. Details carry {old, new} blocks for each field that actually changed. Name-only edits do not emit this event; only security-relevant changes are recorded. |
UserApiKeyEnabled | An API key was re-enabled (toggled from inactive to active). Actor is the admin. |
UserApiKeyDisabled | An API key was disabled (toggled from active to inactive). Disabling is reversible; the key can be re-enabled later. This is distinct from UserApiKeyRevoked, which is a permanent, terminal action. Actor is the admin. |
UserApiKeyRevoked | An API key was permanently revoked. Unlike disabling, revocation cannot be undone. Details include the key id, name, and revocation reason (if provided). Actor is the admin. |
UserApiKeyLocked | A previously-disabled (inactive, revoked, or expired) API key was presented 25 consecutive times, so Tagnet locks the key for 15 minutes to stop the holder from probing for state changes. Random brute-force guesses against unknown keys do not increment this counter and do not trigger this event. Actor type is System. Details include the key id, name, failed-attempt count, and lockout expiry timestamp. |
UserApiKeyUnlocked | A locked-out API key was manually unlocked by an administrator before the 15-minute window expired. Details include the key id, name, and the prior lockout-end timestamp. Mirrors UserAccountUnlocked for user accounts. |
Authorization events
| Event Type | When it fires |
|---|---|
UserRoleAssigned | A role was assigned to a user. Actor is the admin. Details include the target user id, email, and role name. |
UserRoleRemoved | A role was removed from a user. Actor is the admin. Details include the target user id, email, and role name. |
UserAccessScopeAssigned | An access scope was assigned to a user. Actor is the admin. Details include the target user id, email, and the scope id and name. |
UserAccessScopeRemoved | An access scope assignment was removed from a user. Actor is the admin. Details include the target user id, email, and the scope id and name. |
AccessScopeCodeChanged | An access scope's Code was changed, including the system scope's. Because external API clients reference scopes by Code, a rename is a security-relevant configuration change: clients configured with the old Code stop resolving the scope. Actor is the admin. Details include the scope id, scope name, old Code, and new Code. |
SystemScopeRecordsMoved | Every operational record left in the system scope (devices, device groups, locations, inventory, floor plans, pending mappings, commission batches) was moved to a site scope with the Move to a site action on the Access Scopes page. Actor is the admin. Details include the target scope id and name and how many records of each kind moved. |
Device and credential events
| Event Type | When it fires |
|---|---|
CredentialCreated | A new device credential is created. |
CredentialRotated | An existing device credential's password is changed. |
CredentialViewed | A device credential's plaintext password is revealed in the UI. |
CredentialDecryptFailed | A stored credential could not be decrypted at runtime (auto-flagged for rotation). |
CredentialDeleted | A device credential is deleted. |
DeviceDeleted | A device is deleted (alongside per-credential CredentialDeleted events). Also recorded in Change History. |
ReaderFactoryReset | A Zebra FX reader is reset to factory defaults from Tagnet, which also resets its admin username and password to the factory defaults. Logged at Warning. Recorded whether the reset succeeded or failed, because a failed reset may have partly reset the reader. Actor is the user who ran it. Details include the reader name, model, and IP address, plus the step that failed, or the error message when the reset stopped on an error. |
ReaderDebugBundleCollected | An administrator collected a debug bundle from a reader. Recorded whether the collection succeeded or failed. Actor is the administrator. Details include the reader name, id, and model, the status of each item collected (collected, none, not-supported, or failed, with the HTTP status code where there is one), and the error message when the collection failed. The bundle contents are never recorded. |
Device activation, deactivation, and event delivery changes now appear in Change History instead of Security Events. Older DeviceActivated, DeviceDeactivated, and DeviceEventDeliveryChanged entries remain here; Tagnet no longer writes new ones.
Connection profile events
| Event Type | When it fires |
|---|---|
ConnectionTokenRevealed | An administrator revealed a cached token on a connection profile's detail page for troubleshooting. Actor is the admin. Details include the profile name. The token itself is never logged. |
ConnectionTokenAcquireFailed | Token acquisition from a connection profile's token endpoint failed (bad credentials, unreachable endpoint, malformed response, and similar). Actor type is System. Details include the failure reason. Never includes credentials or token material. |
ConnectionProfileSecretsDecryptFailed | A connection profile's stored secrets could not be decrypted at runtime, most often after an encryption key rotation (auto-flagged for rotation). Logged at Critical. Actor type is System. Fires once, on the transition into the flagged state, not on every failed attempt afterward. Details include the profile name. Never includes credentials. |
ConnectionProfileDeleted | A connection profile was deleted. Because the profile row is removed entirely, this event is the surviving record of the deletion. Actor is the admin who deleted it. Details include the profile name and authentication mode. Never includes credentials. |
Outbound delivery events
| Event Type | When it fires |
|---|---|
IntegrationDeliveryRequeued | An administrator sent a failed or dead-lettered delivery to an external system again, from the Integration Outbox page. A bulk requeue records one event for the batch, not one per delivery, with the filter it ran against and how many moved. Details name the target host, never the stored request: no full URL, no request body, no header values. A bulk requeue also echoes the filter the administrator typed, including any URL search text, trimmed to 60 characters. |
IntegrationDeliveryDeadLettered | An administrator used Stop retrying on a delivery that was still waiting to retry, moving it to dead-lettered, so the external system will not receive it unless it is requeued later. Logged at Warning. Details include the delivery ID, the target host, and the stated reason. |
IntegrationDeliveryAbandoned | An administrator abandoned a failed or dead-lettered delivery from the Integration Outbox: it will not be resent and no longer counts as needing attention. Logged at Warning. Details include the delivery ID, the target host, and the reason the administrator gave. A bulk abandon records one event for the batch, with the filter it ran against, how many moved, and the reason. |
Viewing deliveries is deliberately not recorded. Both events above outlive the delivery itself: the outbox row is eventually purged by retention, and these remain.
Integration testing events
| Event Type | When it fires |
|---|---|
IntegrationTestSentWithRealData | An admin ran a test send on an integration endpoint using real inventory data rather than synthetic sample values. Logged at Warning with Outcome=Initiated, written before the request is attempted because the payload leaves the network whether or not the target accepts it. Actor is the admin who ran the test. Details include the target URL and the device and inventory item ids the payload was built from. A test run against sample data emits no event. |
Alert events
| Event Type | When it fires |
|---|---|
AlertAcknowledged | A user acknowledged an operational alert, claiming ownership of the problem. Actor is the acknowledging user. Details include the alert id and its alert type. |
AlertResolved | An operational alert was resolved. Fired both when a user closes an alert by hand (actor is the user) and when the underlying condition clears on its own (actor type System). Manual resolution silences an operational signal, so the record of who closed it outlives the alert. Details include the alert (or its condition identity, for automatic resolution) and its alert type. |
NotificationGroupChanged | A notification group was created, updated, or deleted. Group membership decides who hears about operational problems, so changes to it are security-relevant. Actor is the admin. Details include the group name, the action, and the member count, never a full list of member email addresses. |
AlertRoutingRuleChanged | An alert routing rule was created, updated, enabled or disabled, or deleted. Disabling a rule silences alert delivery for everything it matched, so the record of who changed routing must outlive the rule. Actor is the admin. Details include the rule name, the action, and a summary of its alert-type selectors. |
Policy and system events
| Event Type | When it fires |
|---|---|
PolicyChanged | A configuration policy is created, updated, enabled, or disabled. The Details field contains old/new configuration JSON. |
KeyRingRotation | The data-protection key ring is rotated (initiated and completed). |
ServiceAccountChanged | The Windows account the Tagnet service logs on as was changed, or its stored password was updated, from the Service Account page in the Tagnet Admin Console. Logged at Warning; Outcome is Succeeded when the service came back up under the new account and Failed otherwise (including a rollback). Actor type is AdminTool; the actor field is the Windows username of the person who ran the console. Target type is WindowsService. Details include the action (AccountChanged or PasswordUpdated), the previous and new account names, and a summary of what happened. Passwords are never logged. The same record is appended to logs\admin-console.log under the Tagnet data folder, which is where to look when the database was unreachable at the time. |
LicenseImported | A license file is successfully applied. Details include customer id, customer name, expiry, and trial flag. |
LicenseImportRejected | A license file is rejected during validation. Details include the error code and a description of the failure. |
LicenseCapExceeded | A device create or reactivation attempt was blocked because it would have exceeded the active license's cap for that device type. Emitted with Outcome=Denied. |
LicenseTrialExpired | The trial license passed its expiry and the license gate transitioned to Locked. |
OperationDeniedByLicenseGate | An HTTP request was denied because the license gate is Locked. Deduplicated per actor and request path within a lock session to prevent bot traffic from flooding the log. |
This list grows as new privileged operations are added to Tagnet. Events that are not security-relevant (operational data edits like renaming a location) live in a separate audit log.
Account lockout and unlock flow
When a user enters the wrong password repeatedly, ASP.NET Core Identity locks the account automatically after five failed attempts (the default threshold). Tagnet records this sequence in the audit log so you can reconstruct exactly what happened:
- Attempts 1–4 each fire a
UserLoginFailedevent with reasonbad_password. - On the fifth wrong attempt, two events fire in sequence:
UserLoginFailed(bad_password)(the triggering attempt) andUserAccountLocked(the lockout itself). The lockout event uses actor typeSystembecause Identity performs the lock; no human is the actor. - Subsequent attempts against the locked account fire
UserLoginFailedwith reasonaccount_locked. - When an admin unlocks the account from the Users page, a
UserAccountUnlockedevent fires with the admin as actor.
Natural lockout expiry (the account unlocks automatically after the lockout window passes) does not emit a UserAccountUnlocked event; only admin-initiated unlocks do.
AuthAudit policy
The Security > AuthAudit policy controls whether UserLoginFailed events with reason user_not_found are written to the audit log. This reason fires when someone attempts to sign in with an email address that does not exist in the system.
- Enabled (default): All failed sign-in reasons are logged, including
user_not_found. This gives you complete visibility into credential-stuffing and reconnaissance attempts. - Disabled:
user_not_foundevents are suppressed. The other failure reasons (bad_password,account_disabled,account_locked) always fire regardless of this setting because they involve a known user account.
When to disable: On internet-exposed hosted deployments, bot traffic can generate thousands of user_not_found events per day using attacker-controlled email strings. If this noise obscures legitimate audit signals, disable the policy. On on-premises deployments with a controlled login surface, leave it enabled; the user_not_found events are useful signal.
Configure this policy on the Policies page under the Security category. See Policies for details on enabling and disabling policies.
What is not yet logged
The following event types are planned but not yet implemented:
- MFA events: Tagnet does not yet have multi-factor authentication. MFA-related events will be added under the Enterprise Sign-In feature.
- Refresh token lifecycle events: Deferred until a session-management UI exists.
- User hard-delete events: Tagnet only supports deactivation today; hard delete is not available.
- Operational data audit: Changes to inventory items, locations, and other non-security configuration not yet covered by Change History are planned for that log over time.
- Connection profile create and edit events: Creating or editing a connection profile is not yet logged as a security event (the profile row itself records who created and last updated it); deletions, token reveals, acquisition failures, and secret decrypt failures are captured today.
Filtering and pagination
Use the filter bar to narrow results by:
- Performed by: pick a specific user to see only events that user performed
- Event: select one or more event types from the dropdown
- Severity (
Info,Warning,Critical) - Outcome (
Initiated,Succeeded,Failed,Denied) - Search: free-text search across the event, the entity, its id, and who performed it, using the names as the page shows them ("login failed", "API key", a user's name or email) or the stored values ("system", an API key id, etc.)
- Date: from and to, in your local time
Active filters appear as chips above the grid; close a chip to clear that filter, or click Clear All to reset. Use the Refresh button in the page header to reload the grid and re-check the empty state without losing your current filter selections.
Event detail
Click any row to open the event's own page at a stable, shareable URL. If a linked event id no longer resolves, for example because retention has since removed it, the page shows a friendly "not found, may have been removed by retention" message rather than an error. The page shows:
- When it happened (in your local time) and the event
- The entity and its id (e.g.,
Policy / Retention/TagRead,User / user@example.com) - Outcome and severity
- Who performed it, and the actor type
- IP address and correlation id (when present)
- The full
DetailsJSON, pretty-printed
Retention
Security events are kept for 365 days by default, from when the event happened, and are then deleted by the Retention background job. Before this policy existed, nothing ever purged security events; the log grew forever. On an install that already had security events when this policy first arrived, the window seeds at the maximum, 3650 days, so the upgrade does not delete any pre-existing history; an administrator can shorten it afterward. A fresh install seeds at the 365-day default.
The window is configured on the Retention / Audit policy; see Audit retention. Shortening the window permanently deletes older events on the next retention run, so lower it deliberately, not by accident. If a linked event id no longer resolves because retention has since removed it, its page shows a friendly "not found, may have been removed by retention" message rather than an error.
SOC 2 use
For most audit reviews you will filter by event type and date range, then export findings out-of-band (e.g., screenshot or copy/paste from the event's detail page, or share its URL directly with an auditor). The store is append-only, so an auditor's question "show me every policy change last quarter" or "show me every failed login for this user" is answered directly without trusting that the log was not tampered with.
A future release will add structured export. If your compliance obligation requires keeping security events longer than the default, raise the Security event retention (days) setting on the Retention / Audit policy; see Audit retention.