RBAC that knows where it applies
Role-based access control, with the parts multi-tenant SaaS actually needs: roles bundle scopes, and grants apply them to one resource at a time
Roles and permissions
Roles bundle scopes, and a grant applies one inside a context.
Scopes granted by admin
await nulvless.auth.verify({
resource: "proj_16tbwq34",
roles: ["admin"],
})
The model
Four pieces that compose, instead of a permissions table you maintain by hand
Access token claims
What your services read straight out of the token.
{
"sub": "usr_4c81f0a2",
"iss": "https://acme.nulvless.co",
"email": "[email protected]",
"roles": ["owner"],
"scopes": ["projects:write", "members:invite"],
"org": "org_2f19ac",
"plan": "premium"
}Claims are computed per token, so a role change lands on the next token minted — not on your next deploy
Scopes
The smallest unit: a single thing that may be done
Roles
A named bundle of scopes you can change without touching users
Contexts
Organization, project or any resource of yours — the "where" a role applies
Grants
A user holding a role in a context, with an audit trail behind it
Scopes
The capability your code checks, so a renamed role breaks nothing
Checking it
One call, at the edge, with the answer already next to the session that asked
verify()
Resource plus roles in, a typed decision out — no policy engine to run
Access-token claims
Put roles, scopes and your own claims into the token your services already read
Server-side checks
A service key and forwarded user headers for work your backend does
Deny by default
Nothing is reachable until a grant says it is
Explorer
One trail across auth, functions, notifications and system events.
select ts, email, reason, ip from auth_events where event = "signin" and ok = false
| ts | reason | ip | |
|---|---|---|---|
| 12:04:11 | [email protected] | bad_password | 31.22.8.4 |
| 12:03:52 | [email protected] | user_banned | 77.14.2.90 |
| 12:02:19 | [email protected] | mfa_required | 81.3.44.7 |
| 11:59:03 | [email protected] | bad_password | 31.22.8.4 |
Grant changes are queryable like anything else, which is usually what an auditor is actually asking for
Handing it over
The invitation and membership flows every B2B product ends up building twice
Invitation lifecycle
- created
- email sent, no account required yet
- accepted
- grant issued inside the target context
- expired
- after the window you set
- revoked
- tokens carrying it stop passing
Invitations
Invite by email into a context and a role, before an account exists
Ownership transfer
Move the owner grant without a support ticket
Delegation
Let an admin grant only what they themselves hold
Revocation
Pull a grant and the tokens carrying it stop passing
Invite into a context, not into the app
Grants are issued against an organization, a project or a resource — so the same person can be an owner in one place and a reader in another.
import { createServerClient, forwardUserHeaders } from "@nulvless/nulvless-js";
const nulvless = createServerClient(url, "sk-…");
const { data, error } = await nulvless.auth.grants.invite(
{ email: "[email protected]", context: "org", roles: ["admin"] },
{ headers: forwardUserHeaders(req) },
);One platform, from your first user
Focus on your product. We handle identity infrastructure
What you would have built
Six areas, every one of them a project of its own.
- Authenticationpasswords, providers, MFA, sessions
- AuthorizationRBAC, scopes, per-resource grants
- OAuth serverOIDC discovery, JWKS, PKCE
- Edge functionsDeno runtime, deploys, rollback
- Notificationstemplates, providers, delivery logs
- Observabilitymonitoring, audit trail, query