1. Define Authorization Schema
Start by defining your authorization model in Cedar, a policy language created by AWS for fine-grained access control.
Cedar separates policy logic from application code, making authorization rules auditable and easy to understand.
Define entities (users, resources), actions, and their relationships. cedar4s generates type-safe Scala code from this schema.
namespace DocShare;
entity User {}
entity Folder {}
entity Document in [Folder] {}
action "Document::Read" appliesTo {
principal: [User],
resource: Document
};
action "Document::Write" appliesTo {
principal: [User],
resource: Document
};2. Implement Entity Fetchers
cedar4s generates Scala traits and case classes from your Cedar schema. Implement EntityFetcher to load entities from your database.
The fetchBatch method enables efficient bulk loading, reducing N+1 queries when authorizing multiple resources.
Built-in caching with Caffeine minimizes database hits for frequently accessed entities.
class DocumentFetcher(db: Database)
extends EntityFetcher[IO, Entities.Document, String] {
def fetch(id: String): IO[Option[Entities.Document]] =
db.findDocument(id).map(_.map { doc =>
Entities.Document(id = doc.id, folderId = doc.folderId)
})
override def fetchBatch(ids: Set[String]) =
db.findDocuments(ids).map(_.map(d => d.id -> toCedar(d)).toMap)
}3. Authorize with Type Safety
Use the generated request types to perform authorization checks. No string-based action names or resource IDs that can drift from your schema.
Batch operations like filterAllowed efficiently authorize multiple resources in a single pass.
Works with any effect type: Future, cats-effect IO,ZIO, or your own.
import myapp.cedar.MyApp
val runtime = CedarRuntime[IO](engine, store, CedarRuntime.resolverFrom(buildPrincipal))
given session: CedarSession[IO] = runtime.session(currentUser)
// Type-safe authorization check
MyApp.Document.Read(folderId, documentId).require
// Check without throwing
val canRead: IO[Boolean] = MyApp.Document.Read(folderId, documentId).isAllowed
// Batch filter - only returns allowed documents
val allowed: IO[Seq[Document]] = session.filterAllowed(documents) { doc =>
MyApp.Document.Read(doc.folderId, doc.id)
}