Skip to content

Commit 9d676e2

Browse files
authored
Update README.md (#26)
Updated quick start to include full implementation details. Signed-off-by: Clay King <kinclay@amazon.com>
1 parent ff62bdc commit 9d676e2

File tree

1 file changed

+118
-41
lines changed

1 file changed

+118
-41
lines changed

README.md

Lines changed: 118 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,77 +20,154 @@ npm install @cedar-policy/authorization-for-expressjs
2020

2121
## Quick Start
2222

23-
To implement this package, do the following:
24-
1. Create your Cedar schema and policies.
25-
2. Set up the authorization middleware.
26-
3. Define your routes.
23+
### Prerequisites
24+
25+
Before you implement the Express integration, ensure you have:
26+
27+
- [Node.js](https://nodejs.org/) and [npm](https://docs.npmjs.com/) installed
28+
- An [Express.js](https://expressjs.com/) application
29+
- An OpenID Connect (OIDC) identity provider (optional for testing)
30+
31+
### Setting up the integration
32+
33+
Let's walk through how to secure your application APIs using Cedar with the new package for Express.
34+
35+
#### Step 1: Add the Cedar Authorization Middleware package
36+
37+
The Cedar Authorization Middleware package will be used to generate a Cedar schema, create sample authorization policies, and perform the authorization in your application.
38+
39+
```bash
40+
npm i --save @cedar-policy/authorization-for-expressjs
41+
```
42+
43+
#### Step 2: Generate Cedar schema from your APIs
44+
45+
A Cedar [schema](../overview/terminology.html#term-schema) defines the authorization model for an application, including the entity types in the application and the actions users are allowed to take. We recommend defining a [namespace](../overview/terminology.html#term-namespaces) for your schema. In this example, we use YourNamespace. Your policies are validated against this schema when you run the application.
46+
47+
The `authorization-for-expressjs` package can analyze the [OpenAPI specification](https://swagger.io/specification/) of your application and generate a Cedar schema. Specifically, the paths object is required in your specification.
48+
49+
If you don't have an OpenAPI specification, you can follow the quick instructions of the [express-openapi-generator](https://github.com/nklisch/express-openapi-generator) package to generate an OpenAPI specification.
50+
51+
You can generate a Cedar schema by running, replacing `openapi.json` with the file of your schema and `YourNamespace` with the namespace of our choice:
52+
53+
```bash
54+
npx @cedar-policy/authorization-for-expressjs generate-schema --api-spec openapi.json --namespace YourNamespace --mapping-type SimpleRest
55+
```
56+
57+
This will generate a schema file named `v4.cedarschema.json` in the package root.
58+
59+
#### Step 3: Define authorization policies
60+
61+
If no policies are configured, Cedar denies all authorization requests. We will add policies that grant access to APIs only for authorized user groups.
62+
63+
Generate sample Cedar policies:
64+
65+
```bash
66+
npx @cedar-policy/authorization-for-expressjs generate-policies --schema v4.cedarschema.json
67+
```
68+
69+
This will generate sample policies in the /policies directory. You can then customize these policies based on your use cases. For example:
70+
71+
```cedar
72+
// Defines permitted administrator user group actions
73+
permit (
74+
principal in YourNamespace::UserGroup::"<userPoolId>|administrator",
75+
action,
76+
resource
77+
);
78+
79+
// Defines permitted employee user group actions
80+
permit (
81+
principal in YourNamespace::UserGroup::"<userPoolId>|employee",
82+
action in
83+
[YourNamespace::Action::"GET /resources",
84+
YourNamespace::Action::"POST /resources",
85+
YourNamespace::Action::"GET /resources/{resourceId}",
86+
YourNamespace::Action::"PUT /resources/{resourceId}"],
87+
resource
88+
);
89+
```
90+
Note: If you specified an `operationId` in the OpenAPI specification, the action names defined in the Cedar Schema will use that `operationId` instead of the default `<HTTP Method> /<PATH>` format. In this case, ensure the naming of your Actions in your Cedar Policies matches the naming of your Actions in your Cedar Schema.
91+
92+
For large applications with complex authorization policies, it can be challenging to analyze and audit the actual permissions provided by the many different policies. Cedar also provides the [Cedar Analysis CLI](https://github.com/cedar-policy/cedar-spec/tree/main/cedar-lean-cli) to help developers perform policy analysis on their policies.
93+
94+
#### Step 4: Update the application code to call Cedar and authorize API access
95+
96+
The application will use the Cedar middleware to authorize every request against the Cedar policies. First, add the package to the project and define the `CedarInlineAuthorizationEngine` and `ExpressAuthorizationMiddleware`. This block of code can be added to the top of the `app.js` file:
2797

2898
```javascript
29-
const express = require('express');
3099
const { ExpressAuthorizationMiddleware, CedarInlineAuthorizationEngine } = require('@cedar-policy/authorization-for-expressjs');
31100

32-
const app = express();
33-
34-
// Load your Cedar policies and schema
35101
const policies = [
36-
fs.readFileSync('policies/policy_1.cedar', 'utf8'),
37-
fs.readFileSync('policies/policy_2.cedar', 'utf8')
102+
fs.readFileSync(path.join(__dirname, 'policies', 'policy_1.cedar'), 'utf8'),
103+
fs.readFileSync(path.join(__dirname, 'policies', 'policy_2.cedar'), 'utf8')
38104
];
39105

40-
// Initialize the Cedar authorization engine
41106
const cedarAuthorizationEngine = new CedarInlineAuthorizationEngine({
42107
staticPolicies: policies.join('\n'),
43108
schema: {
44109
type: 'jsonString',
45-
schema: fs.readFileSync('schema.json', 'utf8'),
110+
schema: fs.readFileSync(path.join(__dirname, 'v4.cedarschema.json'), 'utf8'),
46111
}
47112
});
48113

49-
// Configure the Express authorization middleware
50114
const expressAuthorization = new ExpressAuthorizationMiddleware({
51115
schema: {
52116
type: 'jsonString',
53-
schema: fs.readFileSync('schema.json', 'utf8'),
117+
schema: fs.readFileSync(path.join(__dirname, 'v4.cedarschema.json'), 'utf8'),
54118
},
55119
authorizationEngine: cedarAuthorizationEngine,
56120
principalConfiguration: {
57121
type: 'custom',
58-
getPrincipalEntity: async (req) => {
59-
// Map your authenticated user to a Cedar principal
60-
const user = req.user;
61-
return {
62-
uid: {
63-
type: 'YourApp::User',
64-
id: user.sub
65-
},
66-
attrs: {
67-
...user,
68-
},
69-
parents: user.groups.map(group => ({
70-
type: 'YourApp::UserGroup',
71-
id: group
72-
}))
73-
};
74-
}
122+
getPrincipalEntity: principalEntityFetcher
75123
},
76124
skippedEndpoints: [
77125
{httpVerb: 'get', path: '/login'},
78-
{httpVerb: 'get', path: '/health'},
126+
{httpVerb: 'get', path: '/api-spec/v3'},
79127
],
80128
logger: {
81-
debug: console.log,
82-
log: console.log,
129+
debug: s => console.log(s),
130+
log: s => console.log(s),
83131
}
84132
});
133+
```
134+
135+
Next, add the Express Authorization middleware to the application:
136+
137+
```javascript
138+
const app = express();
139+
140+
app.use(express.json());
141+
app.use(verifyToken()); // validate user token
142+
// ... other pre-authz middlewares
85143

86-
// Use the middleware after your authentication middleware
87-
app.use(authMiddleware); // Your authentication middleware
88144
app.use(expressAuthorization.middleware);
89145

90-
// Define your routes
91-
app.get('/protected-resource', (req, res) => {
92-
res.json({ message: 'Access granted!' });
93-
});
146+
// ... other middlewares
147+
```
148+
149+
#### Step 5: Add application code to configure the user
150+
151+
The Cedar authorizer requires user groups and attributes to authorize requests. The authorization middleware relies on the function passed to `getPrincipalEntity` in the initial configuration to generate the principal entity. You need to implement this function to generate the user entity:
152+
153+
```javascript
154+
async function principalEntityFetcher(req) {
155+
const user = req.user; // it's common practice for the authn middleware to store the user info from the decoded token here
156+
const userGroups = user["groups"].map(userGroupId => ({
157+
type: 'PetStoreApp::UserGroup',
158+
id: userGroupId
159+
}));
160+
return {
161+
uid: {
162+
type: 'PetStoreApp::User',
163+
id: user.sub
164+
},
165+
attrs: {
166+
...user,
167+
},
168+
parents: userGroups
169+
};
170+
}
94171
```
95172

96173
## Configuration Options
@@ -149,4 +226,4 @@ This project is licensed under the Apache-2.0 License.
149226
Publishing to npm is done according to these links:
150227

151228
- https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-nodejs-packages
152-
- https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release
229+
- https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release

0 commit comments

Comments
 (0)