Skip to content

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.

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.

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:

peer.ts
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.

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.

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.

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.

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.

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.

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.