This walkthrough covers Admin Roles and Record Permissions. An application with server-verified user sessions and owner-scoped records. These examples add authorization to an existing authentication layer.
The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.
Before you begin
Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.
1. Write a permission matrix
Start with a small permission matrix based on real actions. Authentication tells you who is making the request; authorization decides whether that person can perform it on a particular record. Avoid broad role labels with unclear meaning. Write down the default denial behaviour, and identify operations that need additional review, such as assigning roles or accessing another person's data.
Action Member Administrator
Read own task allow allow
Edit own task allow allow
Read all tasks deny allow
Change roles deny dedicated admin action2. Keep roles under server control
Read role information from trusted server state. A public registration form must not be able to assign its own administrator role. If permissions are cached inside sessions or tokens, define how quickly role changes take effect. For sensitive applications, check the current role or a revocation version on each protected request. Changing a button's visibility doesn't change the server's decision.
const currentUser = await users.find(session.userId);
// Use currentUser.role from trusted server data.
// Never accept body.role during public registration.
const registration = {email, passwordHash, role: "member"};3. Make the rule explicit
A small policy function makes the intended rule readable and testable. In this example, owners can edit their own records and administrators can edit all records. The function denies missing users and missing records. Real products may need tenant boundaries, record state, or delegated permissions as additional inputs. Keep those conditions explicit rather than scattering loosely related checks across interface components.
export function canEdit(user, task) {
if (!user || !task) return false;
return user.role === "admin" || task.ownerId === user.id;
}4. Enforce permissions on the route
Check authorization before applying the mutation, and maintain the same scope in the persistence operation. Choose whether an unavailable record produces forbidden or not found based on your disclosure policy. Validate identifiers and editable fields independently of permission checks. Protect listing and export endpoints too, because unauthorized read access can expose data even when every edit button is hidden.
app.patch("/api/tasks/:id", requireSession, async (req,res) => {
const task = await store.findTask(req.params.id);
if (!canEdit(req.user, task)) return res.sendStatus(404);
await store.updateScoped(req.user, task.id, validatedPatch);
res.sendStatus(204);
});5. Handle tenant boundaries first
If the application serves several organizations, define what administrator means within a tenant. A company administrator usually shouldn't become a global administrator automatically. Check the tenant boundary before role-specific exceptions and derive tenant identity from the authenticated account. Add compound database indexes and scoped queries to support this boundary. Test identifiers from a different tenant even when the current user holds an administrator role.
function sameTenant(user, record) {
return user.tenantId === record.tenantId;
}
const allowed = sameTenant(user, task) &&
(user.role === "admin" || task.ownerId === user.id);6. Audit role changes
Treat role assignment as a dedicated operation with strong authorization, validated values, and an audit record. Record who made the change, who was affected, and when it happened. Avoid logging credentials or private request bodies. Decide how to prevent accidental removal of the last administrator, and revoke or refresh existing sessions when a change must take effect immediately.
await transaction(async db => {
await db.changeRole(targetUserId, newRole);
await db.addAuditEvent({actorId, targetUserId, newRole});
await db.revokeSessions(targetUserId);
});7. Use the UI as helpful feedback
Reflect the permissions in navigation and controls so users aren't encouraged to attempt unavailable actions. Treat this as a usability feature, not a security control. A user can construct requests without your interface. If permissions change while a page is open, show a clear response when an operation is rejected and refresh the current-user state. Never assume a previously visible control still grants access.
const canManage = me.role === "admin";
return canManage ? <AdminNavigation /> : <MemberNavigation />;
// Every corresponding API route still enforces policy.Integration checks
Test anonymous, owner, other member, tenant administrator and cross-tenant administrator for every protected read and mutation. Reuse an old session after demotion and verify denial.
These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.
Official references
Explore CodeKits development services.

