CodeKits.devNew developer offer

Admin Roles and Record Permissions

A practical walkthrough of admin roles and record permissions, with code excerpts, failure checks and integration guidance.

By Jonathan · 5 min read

Published

Admin Roles and Record Permissions tutorial cover
In this article

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.

Write a permission matrix
Action          Member        Administrator
Read own task   allow         allow
Edit own task   allow         allow
Read all tasks  deny          allow
Change roles    deny          dedicated admin action

2. 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.

Keep roles under server control
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.

Make the rule explicit
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.

Enforce permissions on the route
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.

Handle tenant boundaries first
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.

Audit role changes
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.

Use the UI as helpful feedback
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.