Gathering detailed insights and metrics for @aserto/aserto-node
Gathering detailed insights and metrics for @aserto/aserto-node
Gathering detailed insights and metrics for @aserto/aserto-node
Gathering detailed insights and metrics for @aserto/aserto-node
npm install @aserto/aserto-node
Typescript
Module System
TypeScript (98.12%)
JavaScript (1.56%)
Shell (0.32%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
4 Stars
429 Commits
2 Forks
4 Watchers
3 Branches
7 Contributors
Updated on Apr 30, 2025
Latest Version
0.33.7
Package Id
@aserto/aserto-node@0.33.7
Unpacked Size
294.40 kB
Size
63.07 kB
File Count
134
Published on
Apr 30, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
7
25
Aserto authorization middleware for the node Express server, based on Auth0's express-jwt-authz package.
This package provides multiple capabilities:
Middleware
- Provides 2 implementations: Authz
and Check
middlewares that sits on a route, and validates a request to authorize access to that route.Authorizer
- Authorizer Client that provides functions to facilitate communication with an Authorizer v2 service.DirectoryServiceV3
- Directory Client that provides functions to facilitate communication with an Directory v3 service.DirectoryServiceV2
- Directory Client that provides functions to facilitate communication with an Directory v2 service.jwtAuthz
(deprecated): middleware that sits on a route, and validates a request to authorize access to that route.displayStateMap
: middleware that adds an endpoint for returning the display state map for a service, based on its authorization policy.is
: a function that can be called to make a decision about a user's access to a resource based on a policy.ds
(deprecated): an object containing the object
and relation
functions, which can be called to retrieve an object or relation, respectively, from the directory.Using npm:
1npm install @aserto/aserto-node
Using yarn:
1yarn add @aserto/aserto-node
express@^4.0.0
is a peer dependency. Make sure it is installed in your project.
If you are migrating from older versions, check out our migration guide;
1interface Authorizer { 2 config: AuthorizerConfig, 3}; 4 5type AuthorizerConfig = { 6 authorizerServiceUrl?: string; 7 tenantId?: string; 8 authorizerApiKey?: string; 9 token?: string; 10 caFile?: string; 11 insecure?: boolean; 12 customHeaders?: { [key: string]: unknown }; 13 14};
1const authClient = new Authorizer({
2 authorizerServiceUrl: "authorizer.prod.aserto.com:8443",
3 authorizerApiKey: "my-authorizer-api-key",
4 tenantId: "my-tenant-id",
5});
authorizerServiceUrl
: hostname:port of authorizer service (required)authorizerApiKey
: API key for authorizer service (required if using hosted authorizer)tenantId
: Aserto tenant ID (required if using hosted authorizer)caFile
: Path to the authorizer CA file. (optional)insecure
: Skip server certificate and domain verification. (NOT SECURE!). Defaults to false
.1const authClient = new Authorizer({
2 authorizerServiceUrl: "localhost:8282",
3 caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
4});
1import {
2 Authorizer,
3 identityContext,
4 policyContext,
5 policyInstance,
6} from "@aserto/aserto-node";
7
8const authClient = new Authorizer(
9 {
10 authorizerServiceUrl: "localhost:8282",
11 caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt`
12 },
13);
14
15authClient
16 .Is({
17 identityContext: identityContext(
18 "rick@the-citadel.com",
19 "SUB"
20 ),
21 policyInstance: policyInstance("rebac", "rebac"),
22 policyContext: policyContext("rebac.check", ["allowed"]),
23 resourceContext: {
24 object_type: "group",
25 object_id: "evil_genius",
26 relation: "member",
27 },
28 })
1// Is 2// (method) Authorizer.Is(params: IsRequest, options?: CallOptions): Promise<boolean> 3await authClient 4 .Is({ 5 identityContext: identityContext( 6 "morty@the-citadel.com", 7 "SUB" 8 ), 9 policyInstance: policyInstance("todo", "todo"), 10 policyContext: policyContext("todoApp.POST.todos", ["allowed"]), 11 resourceContext: { 12 ownerID: "fd1614d3-c39a-4781-b7bd-8b96f5a5100d", 13 }, 14 }) 15 16// Query 17// (method) Authorizer.Query(params: QueryRequest, options?: CallOptions): Promise<JsonObject> 18await authClient 19 .Query({ 20 identityContext: identityContext( 21 "morty@the-citadel.com", 22 "SUB" 23 ), 24 policyInstance: policyInstance("todo", "todo"), 25 policyContext: policyContext("todoApp.POST.todos", ["allowed"]), 26 resourceContext: { 27 ownerID: "fd1614d3-c39a-4781-b7bd-8b96f5a5100d", 28 }, 29 query: "x = data", 30 }) 31 32 33// DecisionTree 34// (method) Authorizer.DecisionTree(params: DecisionTreeRequest, options?: CallOptions): Promise<{ 35// path: Path; 36// pathRoot: string; 37// }> 38await authClient 39 .DecisionTree({ 40 identityContext: identityContext( 41 "morty@the-citadel.com", 42 "SUB" 43 ), 44 policyInstance: policyInstance("todo", "todo"), 45 policyContext: policyContext("todoApp.POST.todos", ["allowed"]), 46 resourceContext: { 47 ownerID: "fd1614d3-c39a-4781-b7bd-8b96f5a5100d", 48 }, 49 }) 50 51 52// ListPolicies 53// (method) Authorizer.ListPolicies(params: PlainMessage<ListPoliciesRequest>, options?: CallOptions): Promise<Module[]> 54await authClient 55 .ListPolicies({ policyInstance: policyInstance("todo", "todo") })
1await authClient.ListPolicies( 2 { policyInstance: policyInstance("todo", "todo") }, 3 { headers: { customKey: "customValue" } } 4);
When authorization middleware is configured and attached to a server, it examines incoming requests, extracts authorization parameters like the caller's identity, calls the Aserto authorizers, and rejects messages if their access is denied.
failWithError
: When set to true
, will forward errors to next
instead of ending the response directly.
callOptions
: Options for a call.(see: https://github.com/connectrpc/connect-es/blob/v1.5.0/packages/connect/src/call-options.ts#L21-L54)
1interface Middleware { 2 client: Authorizer; 3 policy: Policy; 4 resourceMapper?: ResourceMapper; 5 identityMapper?: IdentityMapper; 6 policyMapper?: PolicyMapper; 7 failWithError?: boolean; 8 callOptions?: CallOptions; 9} 10 11type Policy = { 12 root: string; 13 name?: string; 14 decision?: string; 15 path?: string; 16}; 17 18type CheckOptions = { 19 object?: ObjectMapper; 20 objectId?: string | StringMapper; 21 objectType?: string | StringMapper; 22 relation?: string | StringMapper; 23 subjectType?: string; 24}; 25 26type ResourceMapper = 27 | ResourceContext 28 | ((req?: Request) => Promise<ResourceContext>); 29 30type IdentityMapper = (req?: Request) => Promise<IdentityContext>; 31type PolicyMapper = (req?: Request) => Promise<PolicyContext>;
1function Authz()
2function Check(options: CheckOptions)
1const app: express.Application = express();
2
3
4// Standard REST
5const restMw = new Middleware({
6 client: client,
7 policy: {
8 name: 'todo',
9 root: 'todoApp',
10 },
11 resourceMapper: async (req: express.Request) => {
12 if (!req.params?.id) {
13 return {};
14 }
15
16 const todo = await store.get(req.params.id);
17 return { ownerID: todo.OwnerID };
18 },
19})
20
21app.get("/todos", checkJwt, restMw.Authz(), server.list.bind(server));
22app.post("/todos", checkJwt, restMw.Authz(), server.create.bind(server));
23app.put("/todos/:id", checkJwt, restMw.Authz(), server.update.bind(server));
24
25
26// Check
27const rebacMw = new Middleware({
28 client: authClient,
29 policy: {
30 name: 'policy-rebac',
31 root: 'rebac',
32 }
33})
34
35// Only users that are in the `evil_genius` group are allowed to delete todos.
36app.delete("/todos/:id", checkJwt, rebacMw.Check({
37 objectType: "group",
38 objectId: "evil_genius"
39 relation: "member",
40}))
To determine the identity of the user, the middleware can be configured to use a JWT token or a claim using the IdentityMapper
.
1// use the identity type sub
2import { SubIdentityMapper } from "@aserto/aserto-node";
3
4const restMw = new Middleware({
5 client: authClient,
6 policy: policy,
7 identityMapper: SubIdentityMapper,
8})
9
10// use the jwt type sub from a custom header
11import { JWTIdentityMapper } from "@aserto/aserto-node";
12
13const restMw = new Middleware({
14 client: authClient,
15 policy: policy,
16 identityMapper: JWTIdentityMapper("my-header");,
17})
1// use the manual identity type
2import { ManualIdentityMapper } from "@aserto/aserto-node";
3
4const restMw = new Middleware({
5 client: authClient,
6 policy: policy,
7 identityMapper: ManualIdentityMapper("my-identity");,
8})
The whole identity resolution can be overwritten by providing a custom function.
1// needs to return an IdentityContext
2import { identityContext } from "@aserto/aserto-node";
3
4const restMw = new Middleware({
5 client: authClient,
6 policy: policy,
7 identityMapper: async () => {
8 return identityContext('test', 'SUB')
9 },
10})
The authorization policy's ID and the decision to be evaluated are specified when creating authorization Middleware, but the policy path is often derived from the URL or method being called.
By default, the policy path is derived from the URL path.
To provide custom logic, use a PolicyMapper. For example:
1// needs to return an IdentityContext
2import { identityContext } from "@aserto/aserto-node";
3
4const restMw = new Middleware({
5 client: authClient,
6 policy: policy,
7 policyMapper: async () => {
8 return policyContext('path', ['decision'])
9 }
10})
A resource can be any structured data that the authorization policy uses to evaluate decisions. By default, the request params are included in the ResourceContext.
This behavior can be overwritten by providing a custom function:
1const restMw = new Middleware({
2 client: authClient,
3 policy: policy,
4 resourceMapper: async () => {
5 return { customKey: "customValue" };
6 },
7})
1// provides a custom resource context, 2type ResourceMapper = 3 | ResourceContext 4 | ((req?: Request) => Promise<ResourceContext>); 5 6// examples 7async (req: Request) => { return { customKey: req.params.id } }; 8// or just a plain resource context 9{ customKey: "customValue" }
1type IdentityMapper = (req?: Request) => Promise<IdentityContext>; 2 3// You can also use the built-in policyContext function to create a identity context and pass it as the mapper response 4const identityContext = (value: string, type: keyof typeof IdentityType) => { 5 6IdentityType { 7 /** 8 * Unknown, value not set, requests will fail with identity type not set error. 9 * 10 * @generated from enum value: IDENTITY_TYPE_UNKNOWN = 0; 11 */ 12 UNKNOWN = 0, 13 /** 14 * None, no explicit identity context set, equals anonymous. 15 * 16 * @generated from enum value: IDENTITY_TYPE_NONE = 1; 17 */ 18 NONE = 1, 19 /** 20 * Sub(ject), identity field contains an oAUTH subject. 21 * 22 * @generated from enum value: IDENTITY_TYPE_SUB = 2; 23 */ 24 SUB = 2, 25 /** 26 * JWT, identity field contains a JWT access token. 27 * 28 * @generated from enum value: IDENTITY_TYPE_JWT = 3; 29 */ 30 JWT = 3, 31 /** 32 * Manual, propagates thw identity field as-is, without validation, into the input object. 33 * 34 * @generated from enum value: IDENTITY_TYPE_MANUAL = 4; 35 */ 36 MANUAL = 4 37} 38 39// example 40identityContext("morty@the-citadel.com", "SUB")
1type PolicyMapper = (req?: Request) => Promise<PolicyContext>; 2 3 4// You can also use the built-in policyContext function to create a policy context and pass it as the mapper response 5policyContext = (policyPath: string, decisionsList: Array<string> = ["allowed"]) 6 7// Example 8policyContext("todoApp.POST.todos", ["allowed"])
The Directory APIs can be used to get, set or delete object instances, relation instances and manifests. They can also be used to check whether a user has a permission or relation on an object instance.
1type ServiceConfig = { 2 url?: string; 3 tenantId?: string; 4 apiKey?: string; 5 caFile?: string; 6 rejectUnauthorized?: boolean; 7 insecure?: boolean; 8 customHeaders?: { [key: string]: unknown }; 9 10}; 11 12export type DirectoryV3Config = ServiceConfig & { 13 reader?: ServiceConfig; 14 writer?: ServiceConfig; 15 importer?: ServiceConfig; 16 exporter?: ServiceConfig; 17 model?: ServiceConfig; 18};
You can initialize a directory client as follows:
1import { DirectoryServiceV3 } from "@aserto/aserto-node"; 2 3const directoryClient = DirectoryServiceV3({ 4 url: 'localhost:9292', 5 caFile: `${process.env.HOME}/.local/share/topaz/certs/grpc-ca.crt` 6}); 7 8- `url`: hostname:port of directory service (_required_) 9- `apiKey`: API key for directory service (_required_ if using hosted directory) 10- `tenantId`: Aserto tenant ID (_required_ if using hosted directory) 11- `caFile`: Path to the directory CA file. (optional) 12- `rejectUnauthorized`: reject clients with invalid certificates. Defaults to `true`. 13- `insecure`: Skip server certificate and domain verification. (NOT SECURE!). Defaults to `false`. 14- `reader`: ServiceConfig for the reader client(optional) 15- `writer`: ServiceConfig for the writer client(option) 16- `importer`: ServiceConfig for the importer client(option) 17- `exporter`: ServiceConfig for the exporter client(option) 18- `model`: ServiceConfig for the model client(option)
Define a writer client that uses the same credentials but connects to localhost:9393. All other services will have the default configuration
1import { DirectoryServiceV3 } from "@aserto/aserto-node";
2
3const directoryClient = DirectoryServiceV3({
4 url: 'localhost:9292',
5 tenantId: '1234',
6 apiKey: 'my-api-key',
7 writer: {
8 url: 'localhost:9393'
9 }
10});
object({ objectType: "type-name", objectId: "object-id" }, options?: CallOptions)
:
Get an object instance with the type type-name
and the id object-id
. For example:
1const user = await directoryClient.object({ objectType: 'user', objectId: 'euang@acmecorp.com' }); 2 3// Handle a specific Directory Error 4import { NotFoundError } from "@aserto/aserto-node" 5 6try { 7 directoryClient.object({ 8 objectType: "user", 9 objectId: "euang@acmecorp.com", 10 }); 11} catch (error) { 12 if (error instanceof NotFoundError) { 13 // handle the case where the object was not found 14 } 15 throw error; 16}
1 relation({ 2 subjectType: 'subject-type', 3 subjectId: 'subject-id', 4 relation: 'relation-name', 5 objectType: 'object-type', 6 objectId: 'object-id', 7 })
Get an relation of a certain type between as subject and an object. For example:
1const identity = 'euang@acmecorp.com'; 2const relation = await directoryClient.relation({ 3 subjectType: 'user', 4 subjectId: 'euang@acmecorp.com', 5 relation: 'identifier', 6 objectType: 'identity' 7 objectId: identity 8});
1 relations({ 2 subjectType: 'subject-type', 3 relation: 'relation-name', 4 objectType: 'object-type', 5 objectId: 'object-id', 6 })
setObject({ object: $Object }, options?: CallOptions)
:
Create an object instance with the specified fields. For example:
1const user = await directoryClient.setObject( 2 { 3 object: { 4 type: "user", 5 id: "test-object", 6 properties: { 7 displayName: "test object" 8 } 9 } 10 } 11);
setRelation({ relation: Relation }, options?: CallOptions)
:
Create a relation with a specified name between two objects. For example:
1const relation = await directoryClient.setRelation({ 2 subjectId: 'subjectId', 3 subjectType: 'subjectType', 4 relation: 'relationName', 5 objectType: 'objectType', 6 objectId: 'objectId', 7});
deleteObject({ objectType: "type-name", objectId: "object-id", withRelations: false }, options?: CallOptions)
:
Deletes an object instance with the specified type and key. For example:
1await directoryClient.deleteObject({ objectType: 'user', objectId: 'euang@acmecorp.com' });
deleteRelation({ objectType: string, objectId: string, relation: string, subjectType: string, subjectId: string, subjectRelation: string })
:
Delete a relation:
1await directoryClient.deleteRelation({ 2 subjectType: 'subjectType', 3 subjectId: 'subjectId', 4 relation: 'relationName', 5 objectType: 'objectType', 6 objectId: 'objectId', 7});
You can evaluate graph queries over the directory, to determine whether a subject (e.g. user) has a permission or a relation to an object instance.
check({ objectType: string, objectId: string, relation: string, subjectType: string, subjectId: string, trace: boolean }, options?: CallOptions)
:
Check that an user
object with the key euang@acmecorp.com
has the read
permission in the admin
group:
1const check = await directoryClient.check({ 2 subjectId: 'euang@acmecorp.com', 3 subjectType: 'user', 4 relation: 'read', 5 objectType: 'group', 6 objectId: 'admin', 7});
Check that euang@acmecorp.com
has an identifier
relation to an object with key euang@acmecorp.com
and type identity
:
1const check = directoryClient.check({ 2 subjectId: 'euang@acmecorp.com', 3 subjectType: 'user', 4 relation: 'identifier', 5 objectType: 'identity', 6 objectId: 'euang@acmecorp.com', 7});
1const identity = 'euang@acmecorp.com'; 2const relation = await directoryClient.relation( 3 { 4 subjectType: 'user', 5 objectType: 'identity', 6 objectId: identity, 7 relation: 'identifier', 8 subjectId: 'euang@acmecorp.com' 9 } 10); 11 12if (!relation) { 13 throw new Error(`No relations found for identity ${identity}`) 14}; 15 16const user = await directoryClient.object( 17 { objectId: relation.subjectId, objectType: relation.subjectType } 18);
You can get, set, or delete the manifest
1await directoryClient.getManifest();
1await directoryClient.setManifest(` 2# yaml-language-server: $schema=https://www.topaz.sh/schema/manifest.json 3--- 4### model ### 5model: 6 version: 3 7 8### object type definitions ### 9types: 10 ### display_name: User ### 11 user: 12 relations: 13 ### display_name: user#manager ### 14 manager: user 15 16 ### display_name: Identity ### 17 identity: 18 relations: 19 ### display_name: identity#identifier ### 20 identifier: user 21 22 ### display_name: Group ### 23 group: 24 relations: 25 ### display_name: group#member ### 26 member: user 27 permissions: 28 read: member 29`);
1await directoryClient.deleteManifest();
createAsyncIterable
has been deprecated, please use createImportRequest
1import { ImportMsgCase, ImportOpCode, createImportRequest } from "@aserto/aserto-node" 2const importRequest = createImportRequest([ 3 { 4 opCode: ImportOpCode.SET, 5 msg: { 6 case: ImportMsgCase.OBJECT, 7 value: { 8 id: "import-user", 9 type: "user", 10 properties: { foo: "bar" }, 11 displayName: "name1", 12 }, 13 }, 14 }, 15 { 16 opCode: ImportOpCode.SET, 17 msg: { 18 case: ImportMsgCase.OBJECT, 19 value: { 20 id: "import-group", 21 type: "group", 22 properties: {}, 23 displayName: "name2", 24 }, 25 }, 26 }, 27 { 28 opCode: ImportOpCode.SET, 29 msg: { 30 case: ImportMsgCase.RELATION, 31 value: { 32 subjectId: "import-user", 33 subjectType: "user", 34 objectId: "import-group", 35 objectType: "group", 36 relation: "member", 37 }, 38 }, 39 }, 40]); 41 42const resp = await directoryClient.import(importRequest); 43await (readAsyncIterable(resp))
1const response = await readAsyncIterable(
2 await directoryClient.export({ options: "DATA" })
3)
4
1// passing custom headers to a request 2const user = await directoryClient.object( 3 { 4 objectType: "user", 5 objectId: "euang@acmecorp.com", 6 }, 7 { 8 headers: { 9 customKey: "customValue", 10 }, 11 } 12);
Use Protocol Buffers to serialize data.
1import { GetObjectsResponseSchema, toJson } from "@aserto/aserto-node"; 2 3const objects = await directoryClient.objects({objectType: "user"}); 4const json = toJson(GetObjectsResponseSchema, objects)
aserto-node publishes log events using the Node.js Event emitter. The events for each log level are defined as:
1export enum LOG_EVENT { 2 DEBUG = "aserto-node-debug", 3 ERROR = "aserto-node-error", 4 INFO = "aserto-node-info", 5 TRACE = "aserto-node-trace", 6 WARN = "aserto-node-warn", 7}
Consumers can register a function when any of these events are triggered and handle the logging.
1import { LOG_EVENT, setLogEventEmitter } from '@aserto/aserto-node' 2 3// create a new Event emitter 4const emitter = new EventEmitter() 5 6// configure aserto-node to use the emitter 7setLogEventEmitter(emitter) 8 9// handle aserto-node log events 10emitter.on(LOG_EVENT.TRACE, (message) => { 11 log.trace(message) 12}) 13emitter.on(LOG_EVENT.DEBUG, (message) => { 14 log.debug(message) 15}) 16 17emitter.on(LOG_EVENT.INFO, (message) => { 18 log.info(message) 19}) 20emitter.on(LOG_EVENT.WARN, (message) => { 21 log.warn(message) 22}) 23emitter.on(LOG_EVENT.ERROR, (message) => { 24 log.error(message) 25})
Note: the
authorizerServiceUrl
option that is used throughout is no longer a URL, but the option name is retained for backward-compatibility. It is now expected to be a hostname that exposes a gRPC binding. Any "https://" prefix is stripped out of the value provided.
jwtAuthz
is an Express-compatible middleware that you can place in the dispatch pipeline of a route.
You can use the jwtAuthz function together with express-jwt to both validate a JWT and make sure it has the correct permissions to call an endpoint.
1const jwt = require('express-jwt'); 2const { jwtAuthz } = require('@aserto/aserto-node'); 3 4const options = { 5 authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port 6 policyRoot: 'mycars', // required - must be a string representing the policy root (the first component of the policy module name) 7 instanceName: 'instance-name', // optional (required only for a hosted authorizer) 8}; 9 10app.get('/users/:id', 11 jwt({ secret: 'shared_secret' }), 12 jwtAuthz(options), 13 function(req, res) { ... });
By default, jwtAuthz
derives the policy file name and resource key from the Express route path. To override this behavior, two optional parameters are available.
jwtAuthz(options[, packageName[, resourceMap]])
:
options
: a javascript map containing at least { authorizerServiceUrl, policyName, policyRoot }
as well as authorizerApiKey
and tenantId
for the hosted authorizerpackageName
: a string representing the policy package name (optional)resourceMap
: an optional resource context to send the authorizer. This can be either an object or a function that
takes an HTTP request and returns an object.authorizerServiceUrl
: hostname:port of authorizer service (required)policyRoot
: Policy root (required)instanceName
: instance name (required if using hosted authorizer)authorizerApiKey
: API key for authorizer service (required if using hosted authorizer)tenantId
: Aserto tenant ID (required if using hosted authorizer)caFile
: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.disableTlsValidation
: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.failWithError
: When set to true
, will forward errors to next
instead of ending the response directly.useAuthorizationHeader
: When set to true
, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to true
.identityHeader
: the name of the header from which to extract the identity
field to pass into the authorize call. This only happens if useAuthorizationHeader
is false. Defaults to 'identity'.customUserKey
: The property name to check for the subject key. By default, permissions are checked against req.user
, but you can change it to be req.myCustomUserKey
with this option. Defaults to user
.customSubjectKey
: The property name to check for the subject. By default, permissions are checked against user.sub
, but you can change it to be user.myCustomSubjectKey
with this option. Defaults to sub
.By convention, Aserto policy package names are of the form policyRoot.METHOD.path
. By default, the package name will be inferred from the policy name, HTTP method, and route path:
GET /api/users
--> policyRoot.GET.api.users
POST /api/users/:id
--> policyRoot.POST.api.users.__id
Passing in the packageName
parameter into the jwtAuthz()
function will override this behavior.
By default, the resource map will be req.params. For example, if the route path is /api/users/:id
, the resource will be { 'id': 'value-of-id' }
.
Passing in the resourceMap
parameter into the jwtAuthz()
function will override this behavior.
Use the displayStateMap middleware to set up an endpoint that returns the display state map to a caller. The endpoint is named __displaystatemap
by default, but can be overridden in options
.
1const { displayStateMap } = require('@aserto/aserto-node'); 2 3const options = { 4 authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port 5 policyRoot: 'policy' // required - must be a string representing the policy root (the first component of the policy module name) 6}; 7app.use(displayStateMap(options));
displayStateMap(options)
authorizerServiceUrl
: hostname:port of authorizer service (required)policyRoot
: Policy root (required)instanceName
: instance name (required if using hosted authorizer)authorizerApiKey
: API key for authorizer service (required if using hosted authorizer)tenantId
: Aserto tenant ID (required if using hosted authorizer)caFile
: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.disableTlsValidation
: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.endpointPath
: display state map endpoint path, defaults to /__displaystatemap
.failWithError
: When set to true
, will forward errors to next
instead of ending the response directly. Defaults to false
.useAuthorizationHeader
: When set to true
, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to true
.identityHeader
: the name of the header from which to extract the identity
field to pass into the displayStateMap call. This only happens if useAuthorizationHeader
is false. Defaults to 'identity'.customUserKey
: The property name to check for the subject key. By default, permissions are checked against req.user
, but you can change it to be req.myCustomUserKey
with this option. Defaults to user
.customSubjectKey
: The property name to check for the subject. By default, permissions are checked against user.sub
, but you can change it to be user.myCustomSubjectKey
with this option. Defaults to sub
.While jwtAuthz
is meant to be used as dispatch middleware for a route, is
provides an explicit mechanism for calling the Aserto authorizer.
Use the is
function to call the authorizer with a decision
, policy, and resource, and get a boolean true
or false
response. The decision
is a named value in the policy: the string allowed
is used by convention. Examples: is('allowed')
, is('enabled')
, is('visible')
, etc.
1const { is } = require('@aserto/aserto-node'); 2 3const options = { 4 authorizerServiceUrl: 'localhost:8282', // required - must pass a valid host:port 5 policyRoot: 'policy' // required - must be a string representing the policy root (the first component of the policy module name) 6}; 7 8app.get('/users/:id', async function(req, res) { 9 try { 10 const allowed = await is('allowed', req, options); 11 if (allowed) { 12 ... 13 } else { 14 res.status(403).send("Unauthorized"); 15 } 16 } catch (e) { 17 res.status(500).send(e.message); 18 } 19});
is(decision, req, options[, packageName[, resourceMap]])
:
decision
: a string representing the name of the decision - typically allowed
(required)req
: Express request object (required)options
: a javascript map containing at least { authorizerServiceUrl, policyRoot }
as well as authorizerApiKey
and tenantId
for the hosted authorizer (required)packageName
: a string representing the package name for the the policy (optional)resourceMap
: a map of key/value pairs to use as the resource context for evaluation (optional)This is simply a string that is correlates to a decision referenced in the policy: for example, allowed
, enabled
, etc.
The Express request object.
authorizerServiceUrl
: hostname:port of authorizer service (required)policyRoot
: Policy root (required)instanceName
: instance name (required if using hosted authorizer)authorizerApiKey
: API key for authorizer service (required if using hosted authorizer)tenantId
: Aserto tenant ID (required if using hosted authorizer)caFile
: location on the filesystem of the CA certificate that signed the Aserto authorizer self-signed certificate. See the "Certificates" section for more information.disableTlsValidation
: ignore TLS certificate validation when creating a TLS connection to the authorizer. Defaults to false.useAuthorizationHeader
: When set to true
, will forward the Authorization header to the authorizer. The authorizer will crack open the JWT and use that as the identity context. Defaults to true
.identityHeader
: the name of the header from which to extract the identity
field to pass into the authorize
call. This only happens if useAuthorizationHeader
is false. Defaults to 'identity'.customUserKey
: The property name to check for the subject key. By default, permissions are checked against req.user
, but you can change it to be req.myCustomUserKey
with this option. Defaults to user
.customSubjectKey
: The property name to check for the subject. By default, permissions are checked against user.sub
, but you can change it to be user.myCustomSubjectKey
with this option. Defaults to sub
.By default, is
will follow the same heuristic behavior as jwtAuthz
- it will infer the package name from the policy name, HTTP method, and route path. If provided, the packageName
argument will override this and specify a policy package to use.
By convention, Aserto Rego policies are named in the form policyRoot.METHOD.path
. Following the node.js idiom, you can also pass it in as policyRoot/METHOD/path
, and the path can contain the Express parameter syntax.
For example, passing in policyRoot/GET/api/users/:id
will resolve to a policy called policyRoot.GET.api.users.__id
.
By default, is
follows the same behavior as jwtAuthz
in that resource map will be req.params
. For example, if the route path is /api/users/:id
, the resource will be { 'id': 'value-of-id' }
.
Passing in the resourceMap
parameter into the Authz()
function will override this behavior.
The provided value can be either an object or a function that takes an http request and returns an object.
The Topaz / Aserto authorizers exposes SSL-only endpoints. In order for a node.js policy to properly communicate with the authorizer, TLS certificates must be verified.
For a hosted authorizer that has a TLS certificate that is signed by a trusted Certificate Authority, this section isn't relevant because that TLS certificate will be successfully validated.
In a development environment, topaz automatically creates a set of self-signed certificates and certificates of the CA (certificate authority) that signed them. It places them in a well-known location on the filesystem, defaulting to $HOME/.local/share/topaz/certs/
(or $HOMEPATH\AppData\Local\topaz\certs\
on Windows).
In order for the aserto-node
package to perform the TLS handshake, it needs to verify the TLS certificate of Topaz using the certificate of the CA that signed it - which was placed in $HOME/.local/share/topaz/certs/grpc-ca.crt
. Therefore, in order for this middleware to work successfully, either the caFile
must be set to the correct path for the CA cert file, or the disableTlsValidation
flag must be set to true
. The same is true for the caFile
argument of the DirectoryClient
.
Furthermore, when packaging a policy for deployment (e.g. in a Docker container) which uses aserto-node
to communicate with an authorizer that has a self-signed TLS certificate, you must copy this CA certificate into the container as part of the Docker build (typically performed in the Dockerfile). When you do that, you'll need to override the caFile
option that is passed into any of the API calls defined above with the location of this cert file.
Alternately, to ignore TLS certificate validation when creating a TLS connection to the authorizer, you can set the disableTlsValidation
option to true
and avoid TLS certificate validation. This option is not recommended for production.
aserto-node provides a couple of environment variables that can be used to print debug information:
NODE_TRACE=true
- enables trace logging for the requests.
NODE_TRACE_MESSAGE=true
- logs the request payload for gRPC requests.
If you have found a bug or if you have a feature request, please report them at this repository issues section. Please do not report security vulnerabilities on the public GitHub issue tracker.
Aserto based on the original work by Auth0.
This project is licensed under the MIT license. See the LICENSE file for more info.
No vulnerabilities found.
No security vulnerabilities found.