Entity Resolution with .on()
All authorization checks in cedar4s use the .on(id) pattern, which loads entities
and resolves parent relationships automatically.
Examples below assume import myapp.cedar.MyApp.* is in scope.
Overview
The .on(id) pattern takes a typed entity ID:
// Check if user can read document - parents loaded automatically
Document.Read.on(DocumentId("doc-1")).require
This loads the document from your database, extracts its parent relationships (e.g., folder), and performs the authorization check with the full entity hierarchy.
Setup
Authorization checks require CedarSession and FlatMap in scope:
given CedarSession[Future] = runtime.session(currentUser)
given FlatMap[Future] = FlatMap.futureInstance
// Authorization checks work
Document.Read.on(DocumentId("doc-1")).require
How It Works
The flow:
.on(id)creates anAuthCheckwith the typed entity ID- When executed, it calls
EntityStore.loadEntityWithParents(entityType, id) - Your
EntityFetcher.fetch(id)is called with the typed ID - The entity's parents are extracted via
toCedarEntity().parents - A
ResourceRefis built with entity type, ID, and parent IDs - The authorization check executes with the resolved resource
Entity Parent Extraction
For authorization checks to work, your Cedar entities must define their parent relationships. cedar4s generates this from your schema:
namespace DocShare {
entity Folder {}
entity Document in [Folder] {}
}
Generated entity includes parent reference with typed IDs:
case class Document(
id: DocumentId,
folderId: FolderId, // Parent reference
// ... other attributes
) extends CedarEntity {
def parents: List[(String, String)] =
List(("DocShare::Folder", folderId.value))
}
Usage Patterns
Basic Check
// Load document, extract folder parent, check authorization
Document.Read.on(DocumentId("doc-1")).require
With Context
Document.Read.on(DocumentId("doc-1"))
.withContext(ReadContext(requestTime = Instant.now()))
.require
Conditional Check
Document.Read.on(DocumentId("doc-1"))
.when(featureEnabled)
.require
Composition
Checks can be composed, with each entity resolved independently:
// Both documents loaded separately
val check =
Document.Read.on(DocumentId("doc-1")) &
Document.Read.on(DocumentId("doc-2"))
check.require
Entity Loading
Each authorization check loads the entity and its parents from your data store. This ensures Cedar always has the current state of the entity hierarchy.
| Operation | What Happens |
|---|---|
Document.Read.on(id) | Load document, extract parents |
Folder.View.on(id) | Load folder (no parents for root entities) |
Caching
Enable EntityStore caching to avoid repeated database hits for the same entity:
import cedar4s.caffeine.{CaffeineEntityCache, CaffeineCacheConfig}
val cache = CaffeineEntityCache[Future](CaffeineCacheConfig.default)
val store = EntityStore.builder[Future]()
.register[Entities.Document, DocumentId](new DocumentFetcher(db))
.register[Entities.Folder, FolderId](new FolderFetcher(db))
.withCache(cache)
.build()
With caching, frequently accessed entities are loaded once and reused across authorization checks.