Access denied! Locking down your app
By Dave Berner —
Controlling who can access what in your application isn’t just a nice-to-have - it’s a foundational part of building secure, scalable software.
Like many foundational things, it’s much easier to get right early than to retrofit later. Wait too long, and you’ll be left untangling logic across your UI, backend, billing provider, and feature flag tool, with plenty of opportunity for leaks and mistakes.
In this post, we’ll break down the four common types of access control:
- Entitlements - what a user has paid for
- Permissions - what a user can do
- Roles - how permissions are grouped
- Feature flags - what’s temporarily available
We’ll explain the difference between each type, when to use them, and how they work together. Then we’ll show how Kinde makes implementing them seamless, with real-world examples using the has
helper and ProtectedRoute
component.
Entitlements are tied to billing. They define what a user is allowed to use based on the plan they’re on. This includes which features are available and what limits apply.
Examples:
- Access to the “Projects” feature
- Limit of 3 team members
- Ability to export data
Entitlements are often surfaced on pricing pages and enforced in the product UI and backend. In Kinde, you manage them directly through the platform.
Permissions are about user actions. They define what a user is allowed to do, regardless of plan.
Examples:
create:project
edit:team
delete:account
Permissions are often used in team-based apps or multi-role systems. They’re the foundation of access control logic.
Roles are collections of permissions. Instead of assigning permissions one by one, you assign roles that bundle them.
Examples:
admin
: full accesseditor
: can create and update contentviewer
: read-only access
While roles make permission management easier, they should not be used directly in your app logic. Always check for specific permissions.
Feature flags control whether a feature is available to a given user, environment, or organization. They’re useful for:
- Beta programs
- A/B tests
- Gradual rollouts
- Kill switches
Feature flags are not tied to billing or permissions. They are contextual and temporary.
Now that we’ve covered the concepts, here’s how you can implement them in code using Kinde.
const access = await has({billingEntitlements: ["projects"]});
if (!access) {
return <AccessDenied />;
}
const projects = await fetchProjects();
const maxProjects = getEntitlementValue("projects.limit");
return (
<>
<ul>
{projects.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
{projects.length >= maxProjects ? (
<div>
<p>You’ve reached your project limit. Upgrade for more.</p>
<UpgradeButton />
</div>
) : (
<button onClick={createProject}>Create Project</button>
)}
</>
);
const canCreate = await has({permissions: ["create:project"]});
if (canCreate) {
return <CreateProjectButton />;
}
const isManager = await has({roles: ["manager"]});
if (isManager) {
return <ManagerPanel />;
}
Or declaratively:
<ProtectedRoute has={{roles: ["admin", "manager"]}}>
<AdminDashboard />
</ProtectedRoute>
const access = await has({featureFlags: ["new_projects_ui"]});
return <main>{access ? <NewProjectsUI /> : <LegacyProjectsUI />}</main>;
const access = await has({
permissions: ["view:projects", "create:projects"],
billingEntitlements: ["projects"],
featureFlags: ["new_projects_ui"]
});
if (!access) {
return <AccessDeniedPage />;
}
return (
<div>
<header>
<h1>Projects</h1>
<button>Create Project</button>
</header>
<main>
<NewProjectsUI />
</main>
</div>
);
Whether you’re scaling a new product or wrangling a mature SaaS app, Kinde makes access control simple and powerful:
- Built-in roles and permissions
- Flexible entitlements for plan-based features
- Feature flags that integrate with access logic
- One unified
has
helper to rule them all
Explore the docs or sign up for free and start locking down your app the smart way.