a typed event vocabulary for the OpenID Continuous Access Evaluation Profile.
Implements OpenID CAEP 1.0 (Final Specification, 2025-08-29). Spec: https://openid.net/specs/openid-caep-1_0.html
It is the CAEP event vocabulary: the CAEP event-type URIs and a typed Go struct
for each event payload — session-revoked, token-claims-change,
credential-change, assurance-level-change, device-compliance-change,
session-established, session-presented, and risk-level-change — together
with decoders registered into the
go-secevent event registry. Once
registered, a parsed Security Event Token's events decode to typed CAEP
values, and CAEP events can be built and encoded back into a SET.
CAEP is the "something changed about an ongoing session or credential" half of
the Shared Signals Framework. Each event is a JSON object carried under its
event-type URI in a SET's events map, sharing a common envelope
(event_timestamp, initiating_entity, reason_admin, reason_user) plus
event-specific fields. The subject is named by the SET's sub_id.
The following are deliberately out of scope and belong in dedicated libraries:
- The SET envelope, parsing, validation, and the event registry — these are
go-secevent(RFC 8417). This library plugs its event types into that registry. - JWS signature verification / signing and transport — a JOSE/Shared
Signals transport layer hands
go-seceventthe decoded claims-set bytes. - RISC events (the account-lifecycle half of Shared Signals) — a sibling vocabulary that registers into the same registry.
go get github.com/hstern/go-caepA blank import registers every CAEP decoder with go-secevent. Parse takes the
already-verified, base64url-decoded claims-set bytes; Events.Typed decodes a
known event through the registry.
import (
caep "github.com/hstern/go-caep"
secevent "github.com/hstern/go-secevent"
_ "github.com/hstern/go-caep" // registers the CAEP event decoders
)
set, err := secevent.Parse(payload)
if err != nil {
return err
}
ev, ok, err := set.Events.Typed(caep.EventCredentialChange)
switch {
case err != nil:
return err // the payload was malformed for this event type
case ok:
cc := ev.(caep.CredentialChange)
fmt.Println(cc.ChangeType, cc.CredentialType)
default:
// no decoder registered for this URI: it stays raw and round-trips
}Put marshals a typed event into a SET's events map under its event-type URI.
The optional Validate enforces the CAEP required-field MUSTs before you send.
e := caep.SessionRevoked{Base: caep.Base{
EventTimestamp: secevent.NewNumericDate(time.Now()),
InitiatingEntity: caep.InitiatingEntityPolicy,
}}
if err := caep.Validate(e); err != nil {
return err
}
set := &secevent.SET{ /* iss, iat, jti, aud, sub_id … */ Events: secevent.Events{}}
if err := caep.Put(set.Events, e); err != nil {
return err
}
payload, err := set.Encode() // claims-set bytes for a JOSE signerUnrecognized members on any event are preserved as json.RawMessage and
round-trip byte-stably, so an event from a newer CAEP profile survives a
decode/encode cycle unchanged. See the
package examples
for runnable versions.
v0.1.0 — the first tagged release. As a v0.x series, the public API may
still change per SemVer before v1.0.0. Runtime
dependencies: the standard library plus go-secevent (which in turn uses
go-subjectid).