Mobile app: the role system breaks as the team grows
The first roles in a mobile app are described by a couple of conditions: the administrator sees everything, everyone else sees only their own. While there are dozens of employees, the role system lives in one place and does not get in the way.
When the team grows and partner scenarios are added, permission checks spread out: some conditions remain on the client, some in server handlers, and some are duplicated in the admin panel.
Then the cost appears. The overgrown access logic stops being predictable: a screen closed in one place turns out to be open in another, and a role granted manually is not revoked when an employee is transferred.
Any edit to the permission list turns into a new app release, and while it is in progress, manual permission granting remains the only way to close the task.
A single decision point saves the day. Roles and permissions live on the server side, the app receives a ready-made set together with the token and does not store a copy locally — this is how access works in Keycloak.
Permission checks inside the app turn into a single call, and revocation takes effect immediately, not after an update.
Let's break down how this is addressed: which roles to distinguish, where checks happen, how to change the access matrix without a new release, and what happens to already granted permissions.
What managed access delivers: measurable effects
Permissions stop being part of screens and become a separate service that responds "allowed / not allowed" and why to every request. The decision is made by a role matrix, not by a condition spread across interface modules.
We measure the effects below using the check log: how many requests passed normally, how many were rejected, and how many rules were changed manually.
One check instead of copies of rules in each module
All data accesses go through a single permission check point, so the rule is described once.
The share of manual edits in requests falls from a third to single-digit percentages: typical scenarios are covered by the role matrix, and manual setup is used only for new roles, not for discrepancies between screens.
Along with copies of logic, an entire class of failures disappears — when a role is interpreted differently on one screen than on another, and a request passes where it should not.
There is no longer anything to reproduce such a discrepancy with: the interface is built from the response of the permission service.
Access changes without a new app release
Changing permissions is a configuration edit, not a code change, so no rebuild and release in app stores are needed.
A role is granted or revoked, the client sees the result from the very first request, and reverting to the previous matrix restores previous behavior without waiting for a new version.
Release speed increases exactly by the volume of access tasks that no longer sit in the release queue and no longer drag along a repeat build check.
Access predictability also holds at the boundary with external systems: the layer is compatible with identity providers such as Keycloak, so changing such a provider does not rewrite the role matrix, and the check remains in one place.
Approaches to role separation: where to store permissions
Access separation begins with the question of where the source of truth about permissions lives: this determines how quickly access changes after an employee transfer and what a person sees without a network.
The four schemes that are actually chosen between when designing a role system differ not in ideology, but in the cost of a mistake.
| Option | When it fits | Limitations |
|---|---|---|
| Permissions on the device | Autonomous work: warehouse, field service, offline rounds | Changing permissions requires a new build; access revocation for a dismissed employee is delayed |
| Server-side permission granting | Sensitive data, frequent role changes | Every screen waits for a response; if the connection drops, access disappears |
| Hybrid access scheme | Both instant response and manageability are needed | A conflict resolution rule is required for the local and server versions |
| External identity provider | There is already a corporate employee directory | Dependence on third-party availability; your own permission logic remains |
A role cache is needed in any of the schemes.
Permissions are stored in secure storage on the device together with the policy version and timestamp: at startup the interface is built from the local copy, and in the background it is reconciled with the server — new permissions are applied without restarting the app.
So that access revocation does not stretch over the entire lifetime of the copy, the cache lifetime is kept short, and forced invalidation by a signal from the server is added on top. Next, we'll break down how this is arranged at the level of routes and checks.
Implementation stages: from scenario analysis to handover of regulations
A role system is assembled not by intuition: first, what can generally be done in the app is recorded, and only then is it decided who is allowed to do it.
Each stage relies on the artifact of the previous one, so the final scheme does not diverge from the code a month after handover.
-
Inventory of actions in the app. We go through screens and network calls and write out every action — from viewing an order card to changing a tariff.
The result is an "action → resource → owner" table; it becomes the single source of truth, and an action that did not make it into it does not leak into roles.
-
Designing the role and inheritance scheme. We assemble roles from permission sets, not individual toggles: a base set is inherited, and a child role adds or revokes pointwise. We keep inheritance to one or two levels — deeper, the scheme cannot be read or verified.
-
Setting up access checks. Permissions are checked on the server side; on the client, only the visibility of interface elements. A single check layer intercepts all incoming requests, so a new screen cannot be released bypassing permissions.
-
Testing on real scenarios. We test not roles individually, but user paths: changing a role in an active session, revoking permissions, attempting to reach someone else's data. We look separately at access denial — the screen should explain the reason, not crash.
-
Handover of regulations. We provide the permission matrix, role descriptions, and instructions for adding a new one, and train the customer's team. The administrator then grants and removes access independently, without contacting development.
What remains in the project: access matrix, schemes, and regulations
After launch, the role system lives longer than the team that assembled it: employees, tariffs, and external partners change. That is why we close the project not with a screen demo, but with a set of artifacts that new engineers and support use to make decisions without us.
- "Role — action" matrix — for each action, it is marked who performs it and which objects it applies to. A single source of truth in disputed requests: it is clear that a restriction was intentional, not lost during modification.
- Role inheritance description — which base roles make up composite ones (for example, regional manager = manager + reports for their region). If you edit a base one, you understand whose permissions will also change.
- Check settings — where exactly access is decided: on the server, in the app, or in both places, and why the interface cannot be considered protection.
- Automated access tests — a set of checks like "role X cannot perform action Y" that runs on every build. If you expand a role, the test will show whether extra access has opened up.
- Regulations for granting and revoking access — who approves, within what time permissions appear upon employee transfer and are closed upon dismissal, and where a trace of the change remains.
- Instructions for the support service — typical requests ("I can't see the section", "data disappeared") and ready-made answers, so that not every one is escalated to development.
Case: granting permissions stopped being a manual operation
The partner perimeter of the Garant-Med clinic network relied on manual access granting: 9 roles and 60 scenarios in the app, but permissions for new sections were written directly into the "user — screen" table.
The role for a partner laboratory was assembled by copying a neighboring entry, so a doctor from one network saw another's schedule. Support requests about access came in at 8–10 per week, and any change to permissions waited for a new build release.
What we changed
Roles were described declaratively — as a set of permissions on objects and operations, not on screens. The check was moved to the server layer, and we send to the app a short permission snapshot with a limited lifetime: new grants are picked up without updating the build.
For partners, a separate role was created with binding to the organization and type of services — it is configured by the network administrator without affecting others.
Measurements before and after
In this access separation case, the share of manual operations during granting fell from 60% to 5%: typical cases were covered by role templates.
Support requests decreased from 8–10 to 1–2 per week, and the release time for permission changes — from two days of approvals to half an hour and without a new app version.
The role change log remained mandatory: disputed situations are sorted out from it in minutes, not by correspondence.
How does a role differ from an access permission?
A role is a named set of permissions assigned to an employee function: warehouse worker, shift supervisor, regional manager. An access permission is an atomic operation on a specific object: open a request, confirm a payment, export a client list.
The separation is fundamental: a role answers the question "who is this person in the process", a permission — "what exactly can they do". When a role changes, it is enough to adjust one set — the interface and checks will pull the changes automatically.
The scheme breaks when permissions begin to be granted directly, bypassing the role.
At first this looks like a convenient small thing for one employee, but a couple of releases later, two managers with the same position have different access sets, no one remembers the reason for the differences, and any permission check turns into archaeology through the change history.
Composite roles and permission inheritance restore manageability: a base role describes the common minimum, an application role adds industry-specific permissions rather than rewriting the base entirely.
Inheritance gives a single source of truth, but requires explicit conflict resolution rules — otherwise it is unclear what wins if one role allows an operation and another forbids it.
Access scheme constraints should be laid down in advance: nesting depth, the number of roles per user, and the list of operations that are not delegated at all.
And the check is always performed on the service side — the app only renders the interface based on the received permission set.
A role and a permission are different entities with different lifecycles. Keep them separate, and the access scheme remains readable: it is clear who can do what, why, and how to revoke it.
Technical doubts: integrations, cache, offline mode
Permissions in the app live in two places: on the server, where the source of truth is stored, and in the local cache on the device — so that the interface does not wait for a network response.
The discrepancy between them causes most disputed situations: an employee sees a button that is no longer available to them, or does not receive one that was just granted. Below are answers to what engineers and tech leads ask before starting work.
What does an employee see if the network is lost?
The app works on the last confirmed permission set from the local cache, and operations requiring the network are queued and go out when connectivity is restored. The server still rechecks permissions upon receipt — the queue does not become a workaround.
How does the app learn about a permission change if the cache is stale?
The record has a short lifetime plus a role change signal: the app re-requests the permission set without waiting for expiration. If the signal is lost, updating when returning to the app helps, and critical actions are checked on the server.
Will the app work with our existing identity system?
We bind to your user directory rather than creating a second one. Roles and groups are provided by the current identity system, and the app keeps on top of it a layer of rules for "what is allowed on this screen". No duplication of accounts or manual synchronization appears.
How quickly is access closed for a dismissed employee?
Revocation comes from the server: the session is marked invalid, and the permission update is sent to the device.
While the device is offline, it works on the old set, but the server does not accept operations requiring fresh permissions until that moment — the window closes on the first network connection.
Let's discuss the task: send an access description — we'll come back with a plan
To come back with a work plan for roles, rather than general wording, we need a minimum of inputs.
Send the current access description: the list of roles, the "role → object → action" matrix, and how permissions are granted and revoked today — manually, through the admin panel, or by synchronization with a corporate directory. That is enough to estimate the scope of work and name the order of stages.
What helps speed up project start:
- list of roles and groups with an explanation of what each can do in the app;
- permission matrix in any form — a table, screenshots of screens, an export from an internal system;
- how sign-in works: login and password, external provider, corporate account;
- where and how long sessions live, how sign-out is performed on all of the user's devices;
- what is already implemented: checks on screens, hiding sections, server-side restrictions;
- log requirements: whether it is necessary to record who and when granted or revoked a permission;
- upcoming releases and deadlines by which a working contour is needed.
In the first conversation, we'll analyze the disputed points: what is acceptable to decide on the client, what must be checked on the server, how to migrate existing users without blockages and downtime.
Describe the access task — we'll come back with a scope estimate, a stage-by-stage plan, and acceptance criteria, after which we fix the start date.







