Skip to main content

OPA TypeScript SDK Usage

Installation

npm:

npm add @styra/opa

Yarn:

yarn add @styra/opa zod
note

Yarn does not install peer dependencies automatically. You will need to install zod as shown above.

Code Examples

The following examples assume an OPA server equipped with the following Rego policy:

package authz
import rego.v1

default allow := false
allow if input.subject == "alice"

and this data:

{
"roles": {
"admin": ["read", "write"]
}
}

Simple Query

For a simple boolean response without input, use the SDK as follows:

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

const allowed = await opa.evaluate(path);
console.log(allowed ? "allowed!" : "denied!");

Default Rule

For evaluating the default rule (configured with your OPA service), use evaluateDefault. input is optional, and left out in this example:

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);

const allowed = await opa.evaluateDefault();
console.log(allowed ? "allowed!" : "denied!");

Input

Input is provided as a second (optional) argument to evaluate:

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

const input = { subject: "alice" };
const allowed = await opa.evaluate(path, input);
console.log(allowed ? "allowed!" : "denied!");

Default Rule with Input

Input is provided as an (optional) argument to evaluateDefault:

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);

const input = { subject: "alice" };
const allowed = await opa.evaluateDefault(input);
console.log(allowed ? "allowed!" : "denied!");
note

Everything that follows applies in the same way to evaluateDefault and evaluate.

Input and Result Types

It's possible to provide your own types for input and results. The evaluate function will then return a typed result, and TypeScript will ensure that you pass the proper types (as declared) to evaluated.

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz";

interface myInput {
subject: string;
}
interface myResult {
allow: boolean;
}
const input: myInput = { subject: "alice" };
const result = await opa.evaluate<myInput, myResult>(path, input);
console.log(result);

If you pass in an arbitrary object as input, it'll be stringified (JSON.stringify):

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

class User {
subject: string;
constructor(name: string) {
this.subject = name;
}
}

const inp = new User("alice");
const allowed = await opa.evaluate<User, boolean>(path, inp);
console.log(allowed);

You can control the input that's constructed from an object by implementing ToInput:

import { OPAClient, ToInput } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/allow";

class User implements ToInput {
private n: string;
constructor(name: string) {
this.n = name;
}
toInput(): Input {
return { subject: this.n };
}
}

const inp = new User("alice");
const allowed = await opa.evaluate<User, boolean>(path, inp);
console.log(allowed);

Result Transformations

If the result format of the policy evaluation does not match what you want it to be, you can provide a third argument, a function that transforms the API result.

Assuming that the policy evaluates to

{
"allowed": true,
"details": ["input.a is OK", "input.b is OK"]
}

like this (contrived) example:

package authz
import rego.v1
good_a := ["a", "A", "A!"]
good_b := ["b"]
response.allowed if input.subject == "alice"
response.details contains "input.a is OK" if input.a in good_a
response.details contains "input.b is OK" if input.b in good_b

you can turn it into a boolean result like this:

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL);
const path = "authz/response";
const input = { subject: "alice", a: "A", b: "b" };

const allowed = await opa.evaluate<any, boolean>(
path,
input,
{
fromResult: (r?: Result) => (r as Record<string, any>)["allowed"] ?? false,
},
);
console.log(allowed);

Batched Queries

import { OPAClient } from "@styra/opa";

const serverURL = "http://localhost:8181";
const path = "authz/allow";
const opa = new OPAClient(serverURL);

const alice = { subject: "alice" };
const bob = { subject: "bob" };
const inputs = { alice: alice, bob: bob };
const responses = await opa.evaluateBatch(path, inputs);

for (const key in responses) {
console.log(key + ": " + (responses[key] ? "allowed!" : "denied!")); // Logic here
}
Result
alice: allowed!
bob: denied!

Advanced options

Request Headers

You can provide your custom headers -- for example for bearer authorization -- via an option argument to the OPAClient constructor.

import { OPAClient } from "@styra/opa";
const serverURL = "http://localhost:8181";
const opa = new OPAClient(serverURL, { headers: { authorization: "Bearer opensesame" } });
const path = "authz/allow";
const allowed = await opa.evaluate(path);
console.log(allowed);

HTTPClient

You can supply an instance of HTTPClient to supply your own hooks, for example to examine the request sent to OPA:

import { OPAClient } from "@styra/opa";
import { HTTPClient } from "@styra/opa/lib/http";
const httpClient = new HTTPClient({});
httpClient.addHook("response", (response, request) => {
console.group("Request Debugging");
console.log(request.headers);
console.log(`${request.method} ${request.url} => ${response.status} ${response.statusText}`);
console.groupEnd();
});
const serverURL = "http://localhost:8181";
const headers = { authorization: "Bearer opensesame" };
const opa = new OPAClient(serverURL, { sdk: { httpClient }, headers });
const path = "authz/allow";

const allowed = await opa.evaluate(path);
console.log(allowed);