Authentication
Identify the caller of your function. When a signed-in user invokes a function from the Koolbase SDK, the runtime injects their identity into ctx.auth.
The ctx.auth object
Every function receives an auth field on its context:
{
"user_id": "bf061caa-b43e-4e18-975a-a4b61f1d030e",
"is_authenticated": true,
"email": "[email protected]",
"email_verified": true
}When no signed-in user is invoking — or when the function is fired by a DB trigger — user_id and email are null, and is_authenticated / email_verified are false.
email and email_verified come straight from the caller's auth identity — they are the authoritative values, resolved fresh on every invocation.
Read email from ctx.auth — don't copy it into your data
ctx.auth.email is read from the auth identity on every call, it never goes stale. If instead you copy the email into your own records at sign-up, the two drift apart the moment anything changes — an email update, a self-heal path that runs without the email in scope, an alias mismatch — and you end up with records showing a null or wrong email for a perfectly valid user. Read identity from ctx.auth at request time, and key your own records on the stable user_id rather than email.Reading the caller
Inside the function, read ctx.auth.user_id to identify who's calling. Reject if absent.
export async function handler(ctx) {
const userId = ctx.auth?.user_id;
if (!userId) {
return {
error: { code: 'AUTH_REQUIRED', message: 'Sign in required.' },
status: 401,
};
}
const result = await ctx.db.find('profiles', { user_id: userId }, 1);
return { profile: result.records?.[0] ?? null };
}Future<Map<String, dynamic>> handler(Map<String, dynamic> ctx) async {
final auth = ctx['auth'] as Map<String, dynamic>?;
final userId = auth?['user_id'] as String?;
if (userId == null) {
return {
'error': {'code': 'AUTH_REQUIRED', 'message': 'Sign in required.'},
'status': 401,
};
}
final db = ctx['db'] as Map<String, dynamic>;
final result = await (db['find'] as Function)('profiles', {'user_id': userId}, 1);
final records = (result['records'] as List?) ?? [];
return {'profile': records.isEmpty ? null : records.first};
}Public functions
Some functions are designed to be called without a signed-in user — sign-up flows, password reset, anonymous reads. Skip the auth check and proceed:
export async function handler(ctx) {
// No auth check — anyone can call this.
await ctx.db.insert('events', {
name: ctx.request.body.event,
timestamp: new Date().toISOString(),
});
return { ok: true };
}A function can also do both — branch on ctx.auth.is_authenticated when authenticated callers get a richer response than anonymous ones.
Server-side enforcement
Checking ctx.auth.user_id inside every function is reliable but easy to forget. When a function should always require a signed-in caller, set requires_auth: true at deploy time and Koolbase rejects anonymous invocations with a 401 before your code runs.
koolbase deploy get-profile \
--file ./functions/get_profile.dart \
--runtime dart \
--requires-authAnonymous callers receive:
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{"error":"authentication required"}Inside the function, ctx.auth.user_id is now guaranteed to be present — no defensive check needed.
Default is open
--requires-auth=false to explicitly disable. Without it, a call carrying an expired or unresolvable token still runs your function — with ctx.auth.user_id null — and whatever error you return comes back as a 200 carrying a failure. Clients cannot tell that apart from an ordinary error, so an SDK will not detect that the session died. With the flag, the rejection is a 401 and the SDK signs the user out and routes to login.What's exempt
The gate only fires for SDK and direct HTTP invocations. These paths are exempt by design:
- DB triggers — run as a system-elevated caller, not on behalf of an end-user.
- Cron functions — same; scheduled work runs without an end-user identity.
- Dashboard test-invoke — trusted developer action from the dashboard's "Test" button.
Invoking from the SDK
When a user is signed in via Koolbase.auth, the SDK forwards their access token on every Koolbase.functions.invoke() call. No extra arguments needed.
await Koolbase.auth.signInWithEmail(email, password);
final result = await Koolbase.functions.invoke('get-profile');
// ctx.auth.user_id is populated inside the function.await Koolbase.auth.signIn(email, password);
const result = await Koolbase.functions.invoke('get-profile');
// ctx.auth.user_id is populated inside the function.Token refresh is transparent
Direct REST invoke
When calling the REST endpoint directly, attach the user's access token as a Bearer header alongside the project's API key:
curl -X POST https://api.koolbase.com/v1/sdk/functions/get-profile \
-H "Content-Type: application/json" \
-H "x-api-key: pk_live_your_key" \
-H "Authorization: Bearer <user_access_token>" \
-d '{"body": {}}'Without the Authorization header, the request is treated as unauthenticated and ctx.auth.is_authenticated resolves to false.
SDK compatibility
ctx.auth is populated automatically when calling from these SDK versions or later:
koolbase_flutter5.1.0+@koolbase/react-native4.1.0+Older SDK versions
ctx.auth.is_authenticated as false even when the user is signed in. Upgrade the SDK to enable authenticated invocations.