In my previous article, I introduced NPL as a language where your “Hello World” isn’t throwaway code—it’s a production-ready foundation with built-in authorization, persistence, and API generation. Today, I want to zoom in on one of NPL’s most powerful features: authorization that you don’t write.
If you’ve ever built a backend service, you know the drill. You define your endpoints, then you layer on authentication middleware, role checks, permission guards, and before you know it, authorization logic is scattered across your codebase like confetti.
NPL adopts a radically different approach: authorization is a first-class language construct, and the NPL Runtime enforces it automatically.
Why does it matter? Because authorization won’t be added as a brittle afterthought to your code. Because you won’t forget about it in the first place. Because the compactness of how you write authorization for the full stack in NPL will allow developers to understand and maintain robust authorization over time and as the system scales. Because security breaches are expensive.
Now let’s dive in and see how authorization works in NPL in practice.
Try It Yourself: The Live Demo
Before we dive into code, experience NPL authorization firsthand. We’ve deployed a Hello World application that demonstrates the complete flow:
This demo runs the exact same NPL code we’ll explore below, hosted on NOUMENA Cloud. Walk through it and notice what happens when you log in as different users—the UI reflects what each user can see and do, but you didn’t write any of that logic.
Want to run it locally? Clone the repository and you’re up in minutes:
git clone https://github.com/NoumenaDigital/npl-demo.git
cd npl-demo
docker compose up -d --build --wait
npl deploy
The frontend is now running at http://localhost:5173, and the NPL Runtime API is at http://localhost:12000/.
The Protocol: 21 Lines That Do Everything
Here’s the complete NPL code powering the demo backend:
package demo;
@api
protocol[greeter] HelloWorld() {
initial state greeting;
final state greeted;
@api
permission[greeter] sayHello() returns Text | greeting {
become greeted;
return "Hello " + getUsername(greeter) + "!";
};
};
function getUsername(party: Party) returns Text ->
party.claims()
.getOrNone("preferred_username")
.getOrFail()
.toList()
.get(0);
Notice what’s not here: no JWT validation, no role checks, no middleware configuration, no database queries for permissions. Yet this protocol has complete access control built in.
The magic is in the [greeter] party declaration. This syntax tells the NPL Runtime: “Only users whose JWT claims match the greeter party can interact with this protocol.”
The Access Experiment: Alice Gets In, Bob Doesn’t
Let’s prove that authorization actually works. We’ll create a protocol instance tied to Alice, then try to access it as both Alice and Bob.
Step 1: Get Tokens for Both Users
The demo includes a local OIDC server with pre-configured users. Grab tokens for Alice and Bob:
export ALICE_TOKEN=$(curl -s -X POST http://localhost:11000/token \
-d "grant_type=password" \
-d "username=alice" \
-d "password=password12345678" | jq -r .access_token)
export BOB_TOKEN=$(curl -s -X POST http://localhost:11000/token \
-d "grant_type=password" \
-d "username=bob" \
-d "password=password12345678" | jq -r .access_token)
To inspect the claims it contains, type echo $ALICE_TOKEN to view the token and then paste it on, e.g. jwt.io.
Step 2: Create a Protocol Instance as Alice
When we create a HelloWorld instance, we bind the greeter party to Alice’s claims:
INSTANCE_ID=$(curl -s -X POST http://localhost:12000/npl/demo/HelloWorld/ \
-H "Authorization: Bearer $ALICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"@parties": {
"greeter": {
"claims": {
"preferred_username": ["alice"]
}
}
}
}' | jq -r '.["@id"]')
echo "Created instance: $INSTANCE_ID"
The @parties object is where authorization begins. We’re declaring that the greeter role on this specific instance requires preferred_username: alice. This claim will be matched against JWT tokens claims in subsequent interactions with the protocol instance.
Step 3: Alice Calls the Permission
Alice’s JWT contains preferred_username: alice, which matches the party claims bound to greeter on that instance. She can call sayHello:
curl -X POST "http://localhost:12000/npl/demo/HelloWorld/$INSTANCE_ID/sayHello" \
-H "Authorization: Bearer $ALICE_TOKEN" \
-H "Accept: application/json"
Response:
"Hello alice!"
The protocol transitioned from greeting to greeted, and Alice got her personalized greeting. The getUsername function extracted her username directly from the party’s bound claims—no database lookup needed.
Step 4: Bob Tries the Same Thing
Now let’s see what happens when Bob tries to access Alice’s instance:
curl -X POST "http://localhost:12000/npl/demo/HelloWorld/$INSTANCE_ID/sayHello" \
-H "Authorization: Bearer $BOB_TOKEN" \
-H "Accept: application/json"
Response:
{
"errorType": "noSuchItem",
"message": "No such StateId '5a7071ff-294b-4349-b4d4-1b10e0b3ac40'",
"idType": "StateId",
"id": "5a7071ff-294b-4349-b4d4-1b10e0b3ac40"
}
Bob gets a 404 Not Found—not a 403 Forbidden. This is intentional: the NPL Runtime doesn’t just block access, it hides protocol instances from users who don’t match any party. From Bob’s perspective, this instance doesn’t exist.
Step 5: Bob Can’t Even List Alice’s Instances
curl -X GET "http://localhost:12000/npl/demo/HelloWorld/" \
-H "Authorization: Bearer $BOB_TOKEN" \
-H "Accept: application/json"
Response:
{
"items": [],
"page": 1
}
Empty. The NPL Runtime filters all query results based on party matching. Bob only sees instances where his JWT claims match at least one party.
Where Did the Auth Code Go?
In a traditional stack, you’d implement this access control pattern with:
- JWT validation middleware to verify tokens
- A permissions database mapping users to resources
- Query filters to scope database results by user
- Route guards checking permissions before each action
- Error handling to return appropriate status codes
In NPL, all of this is handled by the Runtime. Here’s what’s happening under the hood:
Party Binding at Instantiation
When you create a protocol instance with @parties, you’re binding claims to protocol-level roles:
{
"@parties": {
"greeter": {
"claims": {
"preferred_username": ["alice"]
}
}
}
}
The claims define who can match this party. The NPL Runtime stores these alongside the protocol instance.
Claims Matching at Runtime
Every API request includes a JWT. The Runtime extracts claims from the token and compares them against the bound party claims. For a user to match a party:
- Every claim key in the party binding must exist in the JWT
- At least one value for each claim must match
This is Attribute-Based Access Control (ABAC) baked into the language.
Automatic Filtering
The Runtime applies party matching to:
- GET requests: Only instances where the caller matches at least one party are returned
- POST requests (permissions): The caller must match the permission’s declared party
- Protocol references: When one protocol calls another, the calling party is “represented” on the called protocol
You write zero filtering logic. The engine handles it all.
What This Means for Your Architecture
NPL’s approach to authorization has profound implications:
Testing becomes trivial. You don’t mock authorization—you test with different party configurations. The behavior is deterministic and declared in your protocol definition.
Auditing is automatic. Every permission call is logged with the calling party. You get a complete audit trail without instrumenting your code.
Compliance is built-in. ABAC, role separation, and access logging aren’t features you add—they’re how the system works.
Less code means fewer bugs. The authorization logic you don’t write can’t have security vulnerabilities.
Get Started
Ready to experience authorization that you don’t write? Here are your options:
- Try the live demo: public-npldemo.noumena.cloud
- Run locally with Docker:
git clone https://github.com/NoumenaDigital/npl-demo.git
cd npl-demo
docker compose up -d --build --wait
npl deploy
- Start from scratch:
brew install NoumenaDigital/tools/npl
npl init --project-dir my-project
cd my-project && docker compose up -d --wait
npl deploy
- Read the documentation: documentation.noumenadigital.com
The npl-demo repository includes a complete React frontend in the webapp folder, showing how to build UIs that respond to NPL’s authorization model. The WALKTHROUGH.md provides a shell-based guide if you prefer working from the command line.
NPL is developed by NOUMENA Digital. We believe authorization shouldn’t be an afterthought—it should be a language primitive. If you’re tired of writing the same access control boilerplate for every project, give NPL a try.