read_after_login
The history-dependent version of the getting-started tour (Step 4 — a decision that
depends on history, which a single Cedar request cannot see): permit Read only if the same user
successfully logged in within the last hour. The when temporal { … } clause
reads the accumulated event history, and the { input.user: context.input.user }
pin correlates the past Login response’s user with the current Read request’s
user.
The trace shows both an allow and a deny:
@0—Loginrequest by alice. The policy gatesRead, notLogin, so nopermitmatches → deny (the login request still lands in the history).@5—Loginresponse (history-only; records that the login succeeded).@10— alice reads, 10s after the login → allow (a matching login response is inside the 1h window).@7200— alice reads again, two hours later; the only login has expired (7200s > 3600s) → deny.
Referenced by guide/01-getting-started.md.
Policy
// Permit Read only if the same user successfully logged in within the last
// hour. The `when temporal { … }` clause reads the accumulated event history;
// the `{ input.user: context.input.user }` pin correlates the past login's
// user with the current request's user. This is what makes the authorizer
// stateful.
@id("read_after_login")
permit (
principal,
action == Drupe::Action::"Read",
resource
)
when temporal {
formerly within 1h Drupe::Action::"Login"::response{ input.user: context.input.user }
};
Schema
namespace Drupe {
type LoginInput = { user: String };
type ReadInput = { user: String };
entity Gateway;
entity OAuthUser = { id: String } tags String;
action "Login" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: LoginInput }
};
action "Read" appliesTo {
principal: [OAuthUser],
resource: [Gateway],
context: { input: ReadInput }
};
}
Trace
@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Login"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@5 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Login"::response(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@10 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Read"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@7200 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { user: "alice" }) Drupe::Action::"Read"::request(input: { user: "alice" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
Expected Output
@0 (time point 0): DENY
@10 (time point 1): ALLOW [rules: 0]
@7200 (time point 2): DENY