Duo Security Log Format: Authentication Event Fields and Examples
Published
One record per Duo event — usually an authentication: which user was challenged, with which factor, from which device and IP, for which application, and whether the attempt was allowed, denied or reported as fraud. Administrator actions appear in the same export with a different shape.
Sample record
[{"timestamp":1705309201,"result":"ALLOW","factor":"duo_push","user":{"name":"alice","key":"DUXXXXXXXXXXXXXXXX"},"access_device":{"ip":"203.0.113.10","os":"Windows","browser":"Chrome","hostname":"DESKTOP-ALICE"},"application":{"name":"Office365 SSO"},"reason":"user_approved","event_type":"authentication"},{"timestamp":1705309262,"result":"ALLOW","factor":"totp","user":{"name":"bob","key":"DUXXXXXXXXXXXXXXXY"},"access_device":{"ip":"203.0.113.20","os":"macOS","browser":"Safari","hostname":"Bobs-MacBook"},"application":{"name":"VPN"},"reason":"valid_passcode","event_type":"authentication"},{"timestamp":1705309323,"action":"user_update","username":"admin","timestamp":1705309323,"object":"alice","description":"Updated user alias"}] From the LogTriage parser test corpus — synthetic data, no real hosts or users.
Field reference
| Field | Type | Meaning | Why it matters for security |
|---|---|---|---|
| timestampe.g. 1705309201 | Unix time (seconds) | When the event was recorded, in epoch seconds. | Convert before correlating with IdP, VPN or endpoint logs; the gap between consecutive pushes for one user is what separates a retry from push bombing. |
| event_typee.g. authentication | string | The kind of event the record describes. | Filter on it before counting outcomes — not every object in an export is an authentication. |
| resulte.g. ALLOW | string | Outcome of the attempt. Values seen in these samples: ALLOW, DENY, FRAUD. | A FRAUD record is a user-reported attempt, which is the strongest single signal in the file; an ALLOW immediately after a run of DENYs is a possible fatigue win. |
| factore.g. duo_push | string | Second factor used for the attempt. Samples contain duo_push, totp, push, sms and bypass. | bypass means a bypass code was used instead of a real factor; SMS on an account that normally uses push can indicate a fallback the attacker chose. |
| reasone.g. user_approved | string | Why the attempt ended as it did. Samples contain user_approved, valid_passcode, user_marked_fraud, anomalous_push and bypass_user. | The reason splits identical results apart — a DENY with anomalous_push is not the same investigation as an ordinary timeout or user denial. |
| user.namee.g. alice | string | Username of the account being authenticated. | Counting distinct values per source IP finds one client working through a list of accounts. |
| user.keye.g. DUXXXXXXXXXXXXXXXX | string | Duo's stable identifier for the user object. | Pivot on the key rather than the name — renaming a user changes user.name but not user.key. |
| access_device.ipe.g. 203.0.113.10 | string | IP address Duo saw the access device connect from. | The main pivot for geolocation, threat intel and grouping attempts that span several accounts. |
| access_device.ose.g. Windows | string | Operating system reported for the access device. | Linux on an estate of Windows laptops, or an OS that does not match the user's usual device, is worth a check. |
| access_device.browsere.g. Chrome | string | Browser or client reported for the access device. | Values such as python-requests or Unknown indicate automation rather than a person at a browser. |
| access_device.hostnamee.g. DESKTOP-ALICE | string or null | Hostname reported for the access device; null when it is not available. | Client-supplied, so treat it as a lead, not proof — the malicious sample carries attacker.ru in this field. |
| application.namee.g. Office365 SSO | string | Duo application (integration) the authentication was for. | Tells you what the factor actually protected — VPN, SSO and Admin Panel failures have very different blast radii. |
| actione.g. user_update | string | Administrative action performed, present on administrator events instead of result and factor. | Directory and user changes around the time of a suspicious authentication can be the attacker consolidating access. |
| usernamee.g. admin | string | Administrator who performed the action, on administrator events. | Shows which admin account made the change; compare it with that admin's own authentication records. |
| objecte.g. alice | string | Target of the administrative action. | Reveals whose enrollment, phone or alias was modified. |
| descriptione.g. Updated user alias | string | Free-text summary of the administrative change. | Short and unstructured — read it, but do not build parsing logic on its wording. |
What it records
Each object in a Duo export is one event. Authentication events (event_type of authentication) describe a single second-factor decision: the user (user.name, user.key), the factor attempted, the device Duo saw (access_device.ip, os, browser, hostname), the protected application (application.name), the outcome in result and the explanation in reason.
Administrator events have a different shape — action, username of the admin, object affected and a free-text description — and appear interleaved with authentications in the same array, as the last record of the benign sample shows.
Duo sits behind primary authentication, so these records are evidence about the factor, not about the password. An ALLOW on an account whose password was phished still looks ordinary unless you read the factor, the source IP and the application together.
Where to get it
- Duo Admin Panel → Reports → Authentication Log, with CSV export for a time range. Good for one-off lookups; column names differ from the JSON field names on this page.
- Duo Admin API. Authentication, administrator and telephony logs are separate endpoints; you need an Admin API application with log-read permission, and the returned field names match the JSON here.
- Scheduled collection. Duo publishes an open-source log-sync utility, and most SIEMs ship a Duo collector that polls the same API endpoints — the practical route for keeping months of history.
Parsing pitfalls
- Mixed record shapes. Authentication and administrator events share one array. Branch on
event_type/ the presence ofaction; code that assumesresultexists will fail on admin records. - Duplicate keys. The administrator record in the benign sample contains
timestamptwice. Permissive parsers keep the last value, strict ones error — check which yours does before trusting counts. - Nulls and missing fields.
access_device.hostnameisnullin one malicious record and absent entirely from another;browserisUnknownin a third. Optional extras such asflash_versionandjava_versionappear only on some records. - Epoch seconds.
timestampis in seconds, not milliseconds — the usual off-by-1000 error when joining with web or EDR logs. - Outcome vocabulary varies. These samples use
ALLOW,DENYandFRAUD. Different API versions and CSV exports label the same outcomes with other strings and casing, so normalize on ingest instead of hard-coding one set. - Names are mutable, keys are not. Join on
user.key;user.namechanges when an account is renamed.
Detection ideas
- MFA fatigue: several
DENYrecords for oneuser.namewithin minutes, especially withreasonofanomalous_push, followed by anALLOW— the core of the Duo MFA fatigue rule. - User-reported fraud: any
resultofFRAUDorreasonofuser_marked_fraud. Someone pressed “this wasn’t me”, which means an attacker already had the first factor. - Bypass code abuse:
factorofbypasswithreasonofbypass_user, particularly against a sensitiveapplication.nameor an administrative account. Check who issued the bypass code and when. - Scripted clients:
access_device.browservalues likepython-requests,osofLinuxon a Windows estate, or a missing/oddhostname. - One source, many accounts: several distinct
user.namevalues sharing oneaccess_device.ipacross a short window. - Admin changes after a suspicious login: an
actionsuch asuser_updateshortly after an anomalous authentication, especially if theobjectis the same account.
The free browser analyzer runs these checks on a Duo JSON export locally, without uploading it.
Detections and guides for Duo Security
Frequently Asked Questions
- Does an ALLOW in Duo mean the password was legitimate?
- No. Duo records the second-factor decision for an application integration; the primary credential check usually happens in the SSO provider, VPN concentrator or application in front of Duo. An ALLOW only proves the factor was satisfied, which is exactly what an attacker achieves with a phished passcode, a stolen bypass code or a push the user approved by mistake.
- Why do some records have no result, user or access_device?
- Because authentication and administrator events sit in the same array. The third object in the benign sample has action, username, object and description instead — a parser that keys on result or user.name will drop it or throw. Branch on event_type and the presence of action before reading authentication-only fields.
- How do I separate a user denying their own push from an attack?
- Read the reason and the surrounding records for the same user. Isolated denials with ordinary reasons are routine. A cluster of denials within minutes, from one access_device.ip the user has never used, followed by an ALLOW, is the fatigue pattern — and a FRAUD result with reason user_marked_fraud means the user explicitly reported it, so treat the password as compromised.
- Can I trust access_device.hostname and browser?
- Only as leads. Both are reported by the client, so they can be absent, null or chosen by the attacker — the malicious sample contains the hostname attacker.ru and the browser python-requests. Corroborate with the source IP, the application involved and endpoint or EDR telemetry before concluding which machine was really used.
Analyze Duo Security logs
LogTriage auto-detects this format, enriches every IP, user agent and hash with threat intelligence, and writes an incident report mapped to MITRE ATT&CK.