Integrations
Integrate Role-Based Access Control
RBAC gives you one place to say what a role can do and one way to check it. Permissions ride along inside the session, so an authorisation check is a local array lookup rather than another network call — which is what makes it cheap enough to put on every route.
Before you begin
- An organization model. Roles are scoped to an organization, so the same person can be an admin in one tenant and a viewer in another.
- A list of the actions in your product that are worth protecting. Permissions describe actions, not screens.
- A default role for new members. Most products want the least privileged one.
- A decision about session lifetime, because a permission change takes effect when the session next refreshes.
Install
Every example on this page uses the Node.js client. The same calls exist in the Python, Go, Ruby, PHP and Java SDKs, following the naming conventions of each language.
1npm i @paycux/node
Configure
RBAC needs no additional credentials. What it does need is a naming convention for permission slugs, chosen before you have fifty of them.
1PAYCUX_API_KEY=sk_example_7f4c1b9ad2e84c6f2PAYCUX_CLIENT_ID=client_01HQZX8N4T3PAYCUX_COOKIE_PASSWORD=a-32-character-or-longer-secret
Then, in the dashboard:
- 1Open Roles and define your permission slugs first. A resource:action shape such as invoices:read reads well in code and sorts sensibly in a list.
- 2Create the roles your product actually has. Three is usually enough to start; a role per customer request is how this becomes unmanageable.
- 3Assign permissions to each role, and mark one role as the default for new members.
- 4Decide which roles an organization admin may hand out themselves, and which stay yours.
- 5Check the session settings, since a shorter access token lifetime means a revoked permission takes effect sooner.
Implement
There are two places to enforce a permission: in the session, for anything the user does in your product, and against the API, for background work where no session exists. Do both, and never rely on hiding a button.
Read permissions from the session
The authenticated session carries the role and the resolved permission list for the active organization. This is the check that belongs on every protected route.
1import { Paycux } from "@paycux/node";2import { cookies } from "next/headers";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function requirePermission(permission: string) {7 const jar = await cookies();8 const sealed = jar.get("px_session")?.value;9 if (!sealed) throw new Error("unauthenticated");1011 const session = paycux.userManagement.loadSealedSession({12 sessionData: sealed,13 cookiePassword: process.env.PAYCUX_COOKIE_PASSWORD,14 });1516 const result = await session.authenticate();17 if (!result.authenticated) throw new Error("unauthenticated");1819 if (!result.permissions.includes(permission)) {20 throw new Error("forbidden");21 }2223 return { user: result.user, organizationId: result.organizationId, role: result.role };24}
Guard a route
Fail closed. An unknown permission slug, an expired session and a missing cookie should all end in the same refusal rather than in an unhandled exception.
1import { requirePermission } from "@/lib/authz";23export async function GET() {4 try {5 const { organizationId } = await requirePermission("invoices:read");6 const invoices = await listInvoices(organizationId);7 return Response.json(invoices);8 } catch (error) {9 const status = error.message === "forbidden" ? 403 : 401;10 return Response.json({ error: error.message }, { status });11 }12}
Change someone's role
Role changes belong to your admin surface. Update the membership, then revoke the affected sessions so the new role applies immediately rather than at the next refresh.
1import { Paycux } from "@paycux/node";2import { requirePermission } from "@/lib/authz";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function POST(request: Request) {7 await requirePermission("members:manage");8 const { membershipId, roleSlug } = await request.json();910 const membership = await paycux.userManagement.updateOrganizationMembership({11 organizationMembershipId: membershipId,12 roleSlug,13 });1415 await paycux.userManagement.revokeSessions({ userId: membership.userId });1617 return Response.json({ ok: true, role: membership.role.slug });18}
Verify
A permissions test is only meaningful if it asserts a refusal. Verify what a viewer cannot do before you celebrate what an admin can.
- 1Sign in as a member with the default role and confirm a protected route returns 403 rather than an error page.
- 2Promote that member to admin, sign in again and confirm the same route now succeeds.
- 3Call the protected API directly with the member's session, bypassing your UI. It must still refuse.
- 4Remove a permission from a role while a session is open and confirm the change applies once the session refreshes.
- 5Open Roles in the dashboard and confirm the permission list matches the slugs your code checks — a typo in a slug fails open in most hand-rolled systems, and this is where you catch it.
Reference
Request and response shapes for each of these live in the API reference. Paths are relative to https://api.paycux.com.
| Method | Path | What it does |
|---|---|---|
| GET | /roles | List the roles defined in the environment. |
| POST | /roles | Create a role with a slug, name and permission set. |
| PUT | /roles/:id | Update a role's name or its permissions. |
| DELETE | /roles/:id | Delete a role that no membership is using. |
| GET | /permissions | List permission slugs and their descriptions. |
| PUT | /user_management/organization_memberships/:id | Change the role attached to a membership. |
| GET | /user_management/organization_memberships | List memberships with their roles for one organization. |