Skip to main content

Why OCSF is awesome

· 6 min read

Security products have a common problem: they generate data about the same things, but they rarely describe those things in the same way.

Take authentication.

Okta, GCP, a VPN, AWS, and an endpoint agent can all tell you that a user tried to log in. But each product can structure that event differently.

For you, that means every new source becomes another parser, another mapping, and another special case in detection logic.

OCSF — the Open Cybersecurity Schema Framework — gives you a common model for these events.

My argument is simple:

OCSF is useful because it gives security data a common semantic contract between producers and consumers.

Why OCSF is awesome

The problem is bigger than parsing JSON

Suppose you want to detect this:

A user has several failed authentication attempts and then successfully authenticates from an unusual IP address.

The detection logic is straightforward. The annoying part is getting the data into a form where the detection can actually work.

Without normalization, you end up with something like:

Then the detection has to know about all of them. Add another identity provider and you potentially have to modify the detection.

The identity provider should be responsible for producing its event. The ingestion layer should handle the mapping. The detection should care about authentication, not about which vendor produced the event.

OCSF gives you a common contract at that boundary:

info
  • OCSF launched in August 2022, founded by Splunk, AWS, IBM, and 15 other companies.
  • By the 1.0 release in August 2023, it had grown eightfold to over 145 contributing organizations.
  • It's Apache 2.0 licensed and not owned by any single vendor.

What does an OCSF authentication event actually look like?

Authentication is an OCSF event class.

In OCSF 1.9, the Authentication class has a class_uid of 3002 and belongs to the Identity & Access Management category. It defines fields such as user, src_endpoint, dst_endpoint, auth_protocol, auth_factors, is_mfa, is_remote, service, session, and status_detail.

Its activity_id identifies the activity represented by the event. For the Authentication class, 1 is Logon, 2 is Logoff, while other values represent activities such as Kerberos ticket requests and preauthentication.

A simplified successful login could look like this:

{
"activity_id": 1,
"activity_name": "Logon",
"category_uid": 3,
"category_name": "Identity & Access Management",
"class_uid": 3002,
"class_name": "Authentication",
"status_id": 1,
"status": "Success",
"time": 1757155200000,

"user": {
"email_addr": "alice@example.com"
},

"src_endpoint": {
"ip": "203.0.113.42"
},

"is_mfa": true,

"metadata": {
"version": "1.9.0",
"product": {
"name": "Example IdP",
"vendor_name": "Example"
}
}
}

The event now has standardized semantics for concepts the detection needs:

  • User identity (user)
  • Source and destination (src_endpoint, dst_endpoint)
  • Authentication protocol and factors (auth_protocol, auth_factors)
  • MFA and remote access state (is_mfa, is_remote)
  • Target service and session (service, session)
  • Authentication outcome (status_id, status, status_detail)

OCSF also provides both numeric identifiers and human-readable names. For example:

"activity_id": 1,
"activity_name": "Logon"

The identifier gives consumers a consistent value, while the name makes the event easier to inspect.

Every event class also inherits attributes common across OCSF — class_uid, category_uid, severity_id, metadata — on top of the fields specific to that class.

Now the detection becomes boring

And that is a good thing.

Now a harder detection:

A user has five failed login attempts and then successfully logs in from an unusual IP address.

Without normalization, the detection has to understand how every source represents the same concepts.

An Okta event might have:

{
"user": "alice@example.com",
"clientIp": "203.0.113.42",
"status": "FAILURE"
}

Another source might use:

{
"userPrincipalName": "alice@example.com",
"source_ip": "203.0.113.42",
"result": {
"code": 1
}
}

The detection now has to contain source-specific logic:

if source == "Okta":
user = event["user"]
ip = event["clientIp"]
failed = event["status"] == "FAILURE"

elif source == "GCP":
user = event["userPrincipalName"]
ip = event["source_ip"]
failed = event["result"]["code"] != 0

Only after doing this can it implement the actual detection:

if failed:
failed_logins[user].append(event)

elif is_unusual_ip(user, ip) and len(failed_logins[user]) >= 5:
alert(user, ip)

The problem is that the detection is now responsible for two different things:

  1. Understanding each vendor's event format.
  2. Detecting suspicious authentication behavior.

The source mapper can convert both events into the same Authentication class. The detection can then work with the normalized fields:

user = event["user"]["email_addr"]
ip = event["src_endpoint"]["ip"]

if event["status_id"] == 2:
failed_logins[user].append(event)

elif event["status_id"] == 1:
if len(failed_logins[user]) >= 5 and is_unusual_ip(user, ip):
alert(user, ip)

Now the detection only describes the behavior we care about:

Five failed logins followed by a successful login from an unusual IP.

It does not need to know whether the original event came from Okta, GCP, a VPN, or another product.

That is the useful boundary OCSF provides. Vendor-specific knowledge stays in the mapping layer, while the detection works with the security semantics of the event. Personally, this is why I like the OCSF model: it makes detections more predictable, maintainable, and straightforward.

Adding another product should not break the detection

Your company adds another identity provider.

Without a common schema:

With OCSF:

The new work still exists. Someone has to understand the vendor's event format and map it correctly — once.

Without OCSF, that vendor-specific knowledge would show up again in every detection, dashboard, and query that touches this data. With it, it shows up once, in the mapper.

Where you can find OCSF today

OCSF is not just a schema sitting in a repository.

The trade you're actually making

OCSF doesn't remove the work of understanding every vendor's format. Someone still has to read Okta's docs, write the mapper, and update it when a field gets renamed. What changes is where that work happens.

Do it once at the edge, and every detection, dashboard, and query downstream gets to assume a stable shape for "a user logged in." Skip that step, and the same vendor-specific knowledge leaks into every rule you write, and every analyst has to relearn it.

That's the whole argument: normalization is a cost you pay once at the ingestion boundary, or repeatedly in the middle of your platform. OCSF just gives you a common target to pay it against.