Use signed-in user information
Keelson handles login and checks who can open your app. Your app can use the resulting user information to record who created a record or show users only their own data.
Implement business rules, such as who can approve a request, in your app. Choose among these three approaches:
| What you want to do | What to use | Additional setup |
|---|---|---|
| Identify the person accessing the app, record authors, or show personal data | User information headers such as X-Keelson-User-Id | None |
| Show a screen only to app operators who can deploy or change settings | X-Keelson-User-App-Perms header | None; assign permissions in the console |
| Vary behavior by department or job function, or identify approvers | attributes.groups from the Identity SDK | keelson apps directory enable |
Use group memberships or your app’s own data, such as an assignee table, for business permissions like “only a manager can approve.” The manage permission is for deploying and configuring an app; its holders are not necessarily business approvers.
1. Identify the current user
Section titled “1. Identify the current user”For requests from signed-in users, Keelson provides user information in headers (metadata attached to the request). Read these headers in your server-side code.
| Header | Content |
|---|---|
X-Keelson-User-Id | User ID: a stable identifier to store in your database |
X-Keelson-User-Email | Email address, or an empty string if Keelson has no value |
X-Keelson-User-Name | Display name, or an empty string if Keelson has no value |
To record the author, save the user ID alongside the data. To show only the user’s own data, include that ID in your database query. Reading the header alone does not filter data automatically.
# FastAPI / Flaskuser_id = request.headers.get("X-Keelson-User-Id")email = request.headers.get("X-Keelson-User-Email")// Expressconst userId = req.headers["x-keelson-user-id"];const email = req.headers["x-keelson-user-email"];Keelson adds this information after verifying the user’s login. If a caller sends headers with the same names, Keelson replaces them with the verified user’s information. Your app does not need to manage the Keelson login session.
Read user information with the SDK
Section titled “Read user information with the SDK”The Identity SDK returns user information as id, email, and name. See Keelson SDK installation. Run the following examples on the server.
getCurrentUser only reads headers. It makes no external request and does not require the Directory API to be enabled. In JavaScript, call it with await. A missing or empty user ID header raises IdentityError.
import { getCurrentUser } from "@keelsonhq/identity";
app.get("/", async (req, res) => { const user = await getCurrentUser({ headers: req.headers }); // { id, email, name } res.type("text").send(`Hello, ${user.name ?? "Guest"}`);});To also get the user’s permissions for this app (X-Keelson-User-App-Perms), use getRequestUser (get_request_user in Python, GetRequestUser in Go). It returns the same values as getCurrentUser plus perms (usage).
2. Operator-only screens
Section titled “2. Operator-only screens”Apps have two permissions, view and manage, assigned to groups through the console or CLI (Groups and app access). manage allows operators to deploy, change configuration, and edit secrets. Owner, Admin, and Developer have it by default. Keelson passes the resulting permissions in a header.
X-Keelson-User-App-Perms | Meaning |
|---|---|
view | Can view the app |
view,manage | Can view and manage the app |
Your app only needs to check whether manage is present. Do not hard-code group names for this check.
// Express: read inside the handler because headers vary by request.function canManage(req) { const perms = (req.headers["x-keelson-user-app-perms"] ?? "").split(","); return perms.includes("manage");}
app.get("/admin", (req, res) => { if (!canManage(req)) return res.status(403).end(); res.send("App operator screen");});# FastAPI / Flask: also read per request.def can_manage(request) -> bool: perms = request.headers.get("X-Keelson-User-App-Perms", "").split(",") return "manage" in permsTo make an app available only to the accounting team, assign permissions; no app code is needed (Share an app with your team). To let only operators open a settings screen, check manage. You can change who has manage in the console without changing code.
3. Group-based behavior
Section titled “3. Group-based behavior”Use the Identity SDK to get group memberships when you need department-specific behavior or business permissions such as allowing only approvers to approve.
Hiding controls in the UI is not enough. When implementing “users can see only their own requests,” filter lists and also enforce on the server that users cannot retrieve or update someone else’s request by specifying its ID in the API. Base decisions on Keelson-provided headers or SDK results, not values supplied by the browser.
To retrieve group or member information, enable read access to the Directory API, then redeploy. Enabling it alone does not make it available to the running app.
keelson apps directory enablekeelson deployAfter redeployment, the app receives KEELSON_DIRECTORY_TOKEN, which the SDK uses automatically. Keep this token on the server; do not send it to the browser.
import { getCurrentIdentity } from "@keelsonhq/identity";
// Read inside a request handler.const me = await getCurrentIdentity({ headers: req.headers });const groups = me.attributes?.groups ?? []; // Example: ["everyone", "developers", "accounting"]const isAccountingMember = groups.includes("accounting");// Use this result to decide whether to allow accounting operations.from keelson import identity
me = identity.get_current_identity(headers=request.headers)groups = (me.attributes.groups or []) if me.attributes else []is_accounting_member = "accounting" in groups# Use this result to decide whether to allow accounting operations.attributes.groups contains group keys:
- Role-based system groups: Owner receives
owners,developers, andeveryone; Admin receivesadmins,developers, andeveryone; Developer receivesdevelopersandeveryone; App User receiveseveryone. - Custom groups the user belongs to that have permissions assigned for this app. Groups not assigned to the app are omitted.
- Keys are stable and can be used in code. Japanese keys, such as
経理(accounting), are also supported.
getCurrentIdentity queries the Directory API. Its result also includes user information (user.id, user.email, user.name), the workspace role (workspace.role), and app permissions (app.permissions). User information is nested differently from the result of getCurrentUser.
Use the member list to choose assignees
Section titled “Use the member list to choose assignees”When you need information about other members, such as options in an assignee picker, use listMembers / list_members (with search and role or group filters), getUser, or listGroups.
Each member has id, email, name, role, and image_url. image_url is the profile image URL (served from img.clerk.com), or null if the member has no image. Append width / height query parameters to get a smaller image.
On the server, return only what the picker needs:
import { listMembers } from "@keelsonhq/identity";
// Inside a request handler. Returns up to 100 members per call.const page = await listMembers({ group_key: "developers", limit: 100 });const options = page.items.map((m) => ({ id: m.id, label: m.name || m.email, image_url: m.image_url, // string or null}));from keelson import identity
page = identity.list_members(group_key="developers", limit=100)options = [ {"id": m.id, "label": m.name or m.email, "image_url": m.image_url} for m in page.items]In the browser, show initials when there is no image or the image fails to load:
function avatar(option) { const initials = document.createElement("span"); initials.className = "avatar-initials"; initials.textContent = option.label.slice(0, 1).toUpperCase(); if (!option.image_url) return initials;
const img = document.createElement("img"); img.className = "avatar"; img.alt = ""; const src = new URL(option.image_url); src.searchParams.set("width", "64"); src.searchParams.set("height", "64"); img.src = src.toString(); img.addEventListener("error", () => img.replaceWith(initials), { once: true }); return img;}Store only the member id in your app’s database, for example in a task’s assignee column. Do not store image_url, name, or email. Look up the latest values with listMembers or getUser each time you display them; a member’s image can change at any time, and a stored copy becomes stale.
Local development
Section titled “Local development”When you run your app on your machine, there is no Keelson login, so requests carry no user information headers. Start it with keelson dev serve to receive the same headers as in production and switch between a view-only user and a user with manage permission. When you start it with KEELSON_LOCAL_MODE=1 and no CLI, the Identity SDK returns one fixed user. See Run your app locally.
Custom groups cannot be reproduced locally. Also test group-based behavior in a test app with real permissions configured.
Requests without a signed-in user
Section titled “Requests without a signed-in user”Requests to /api/external/ or /api/webhooks/ authenticated with an app token do not carry a signed-in user’s ID. Handle them separately from code that calls getCurrentUser or getCurrentIdentity. See External integrations for authentication details.
Related pages
Section titled “Related pages”- Groups and app access — Who receives view and manage permissions
- Authentication and login