Authentication
Ramose verifies identities; it does not manage passwords or issue user sessions. Bring an OAuth/OIDC provider or another signer that publishes a JSON Web Key Set.
Browser credential provider
Section titled “Browser credential provider”Use the browser entry with your authenticated Better Auth user ID:
import { createAuthProvider } from "ramose/better-auth/client"
const credentials = createAuthProvider({ userId: session.user.id })const client = createClient({ url: location.origin, database: "app", schema: AppSchema, auth: credentials,})The provider renews expiring tokens, shares concurrent renewals, and can reuse
its saved bearer during a network failure. HTTP denials never trigger that
fallback. Call credentials.clear() and close the client on sign-out.
Configure the server
Section titled “Configure the server”const server = Ramose.Server("App", { main: import.meta.resolve("./peer.ts"), storage: Store, auth: { jwksUrl: "https://auth.example.com/.well-known/jwks.json", issuers: ["https://auth.example.com/"], aud: "https://api.example.com", maxTtl: 900, allowedOrigins: ["https://app.example.com"], },})The Worker entry installs the schema separately:
import * as Effect from "effect/Effect"import { QueryReplicaDO, createServer, createTransactorDO, deployOperationCatalogs,} from "ramose/worker"import { deployment } from "./domain"
const operationCatalogs = await Effect.runPromise( deployOperationCatalogs(deployment),)
export default createServer({ operationCatalogs })export { QueryReplicaDO }export const TransactorDO = createTransactorDO(operationCatalogs)Ramose verifies signature, issuer, audience, expiry, and configured maximum lifetime. The subject identifies the principal deployment-wide. Authentication alone grants access to nothing.
Use jwksJson for a pinned local or isolated environment. Use jwksService when the issuer is another Worker reached through a service binding. Never place private signing keys in the Ramose Worker.
Supply tokens to the browser
Section titled “Supply tokens to the browser”const client = createClient({ url: "https://api.example.com", database: ROOT_DATABASE, schema: AppSchema, auth: async () => { const session = await auth.getSession() return { token: await auth.getAccessToken({ audience: "https://api.example.com" }), cacheKey: session.user.id, } },})The callback may refresh a short-lived token. When the principal changes, Ramose closes the previous synchronization scope and purges or quarantines its browser partition before rendering the next principal’s data.
Map the principal into schema data
Section titled “Map the principal into schema data”Policies can use subject directly or resolve me through a unique field:
const User = Ramose.Entity("user", { authId: Ramose.Field.unique(Ramose.string(), "strict"), name: Ramose.string({ optional: true }),})
const AppSchema = Ramose.Schema("app-v1", { user: User, task: Task,})const ROOT_DATABASE = "app"
AppSchema.applyPolicy( { principal: User.authId }, ({ policy, actor }) => { policy.task.read.where((task) => task.owner.eq(actor)) },)
export const deployment = { root: AppSchema, deployments: [{ database: ROOT_DATABASE }],}Create or update principal rows through explicit authorized operations. Do not depend on token payload fields being silently copied into application data.
Use claims sparingly
Section titled “Use claims sparingly”Declare every claim a policy may read. Claims are best for stable identity context such as organization issuer or service class. Put revocable membership and resource ownership in the database so policy changes take effect without waiting for token expiry.
MCP OAuth discovery
Section titled “MCP OAuth discovery”With MCP enabled, POST /mcp publishes protected-resource metadata and standards-correct bearer challenges. A capable MCP client can discover the authorization server, request the correct audience, and return with a token. The MCP session is transport state, not authority; every tool call verifies the bearer token again.
Service and agent principals
Section titled “Service and agent principals”Represent agents and backend jobs as ordinary signed principals with narrow roles and operation rules. Prefer short-lived delegated tokens over deployment-wide shared secrets. Give the agent only the root and operations its job requires; discovery automatically reflects that view.
Local development
Section titled “Local development”Use a local JWKS signer with the same issuer and audience checks as production. Keep development keys outside the repository and rotate them freely. Do not add an anonymous or open-mode bypass: testing the real authentication boundary is part of the application contract.