Skip to main content

Quickstart

This page gets you running quickly with minimal explanation. See Installation for detailed setup options.

Prerequisites

  • Scala 2.13 or 3.x
  • sbt 1.9+

1. Add the Plugin

project/plugins.sbt
addSbtPlugin("io.github.devnico" % "sbt-cedar4s" % "0.0.0-SNAPSHOT")

2. Configure Build

build.sbt
enablePlugins(Cedar4sPlugin)

cedarSchemaFile := baseDirectory.value / "src/main/resources/schema.cedarschema"
cedarScalaPackage := "com.example.cedar"

libraryDependencies ++= Seq(
"io.github.devnico" %% "cedar4s-core" % "0.0.0-SNAPSHOT",
"io.github.devnico" %% "cedar4s-client" % "0.0.0-SNAPSHOT"
)

3. Define Schema

src/main/resources/schema.cedarschema
namespace DocShare {
entity User = {};

entity Folder = {};

entity Document in [Folder] = {};

action "Document::view" appliesTo {
principal: [User],
resource: [Document]
};
}

4. Create Policy

src/main/resources/policies/main.cedar
permit (
principal,
action == DocShare::Action::"Document::view",
resource
) when {
resource in principal.folders
};

5. Implement Entity Fetchers

EntityFetchers.scala
import cedar4s.entities.EntityFetcher
import com.example.cedar.DocShare
import com.example.cedar.EntityIds.{DocumentId, FolderId, UserId}
import scala.concurrent.Future

class DocumentFetcher(db: Database)(using ExecutionContext)
extends EntityFetcher[Future, DocShare.Entity.Document, DocumentId] {

def fetch(id: DocumentId): Future[Option[DocShare.Entity.Document]] =
db.findDocument(id.value).map(_.map { doc =>
DocShare.Entity.Document(
id = DocumentId(doc.id),
folderId = FolderId(doc.folderId)
)
})
}

class UserFetcher(db: Database)(using ExecutionContext)
extends EntityFetcher[Future, DocShare.Entity.User, UserId] {

def fetch(id: UserId): Future[Option[DocShare.Entity.User]] =
db.findUser(id.value).map(_.map { user =>
DocShare.Entity.User(id = UserId(user.id))
})
}

6. Set Up Authorization

Main.scala
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import cedar4s.capability.instances.{futureSync, futureMonadError}
import cedar4s.auth.{CedarSession, FlatMap, Principal}
import cedar4s.entities.EntityStore
import cedar4s.client.{CedarEngine, CedarRuntime}
import com.example.cedar.DocShare
import com.example.cedar.EntityIds.*

given FlatMap[Future] = FlatMap.futureInstance

// Register all entity fetchers
val store = EntityStore.builder[Future]()
.register[DocShare.Entity.Document, DocumentId](DocumentFetcher(db))
.register[DocShare.Entity.User, UserId](UserFetcher(db))
.build()

val engine = CedarEngine.fromResources[Future]("policies", Seq("main.cedar"))

// Principal resolver - returns the principal entity, not CedarPrincipal
def resolvePrincipal(p: Principal): Future[Option[DocShare.Entity.User]] = {
p match {
case DocShare.Principal.User(userId) =>
// Load user from database
db.findUser(userId.value).map(_.map { user =>
DocShare.Entity.User(id = userId)
})
case _ => Future.successful(None)
}
}

// Create runtime with explicit principal type
val runtime = CedarRuntime[Future, DocShare.Entity.User](
engine,
store,
CedarRuntime.resolverFrom(resolvePrincipal)
)

// Create a session for the current user
given session: CedarSession[Future] =
runtime.session(DocShare.Principal.User(UserId(currentUserId)))

7. Use the DSL

import com.example.cedar.DocShare
import com.example.cedar.EntityIds.*

// Check authorization - throws on denial
DocShare.Document.View.on(DocumentId("doc-123")).require

// Check without throwing
DocShare.Document.View.on(DocumentId("doc-123")).isAllowed // Future[Boolean]

// Get result with error details
DocShare.Document.View.on(DocumentId("doc-123")).run // Future[Either[CedarAuthError, Unit]]

Run sbt compile to generate code, then build your application.