The Styra-supported driver to connect to Open Policy Agent (OPA) and Enterprise OPA deployments.
The documentation for this SDK lives at https://docs.styra.com/sdk, with reference documentation available at https://styrainc.github.io/opa-typescript
You can use the Styra OPA SDK to connect to Open Policy Agent and Enterprise OPA deployments.
npm add @styra/opa
pnpm add @styra/opa
bun add @styra/opa
yarn add @styra/opa zod
# Note that Yarn does not install peer dependencies automatically. You will need
# to install zod as shown above.
For supported JavaScript runtimes, please consult RUNTIMES.md.
All the code examples that follow assume that the high-level SDK module has been imported, and that an OPA
instance was created:
import { OPAClient } from "@styra/opa";
const serverURL = "http://opa-host:8181";
const path = "authz/allow";
const opa = new OPAClient(serverURL);
For a simple boolean response without input, use the SDK as follows:
const allowed = await opa.evaluate(path);
console.log(allowed ? "allowed!" : "denied!");
Note that allowed
will be of type any
. You can change that by providing type parameters to evaluate
:
const allowed = await opa.evaluate<never, boolean>(path);
The first parameter is the type of input
passed into evaluate
; we don't have any in this example, so you can use anything for it (any
, unknown
, or never
).
HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json
{}
Input is provided as a second (optional) argument to evaluate
:
const input = { user: "alice" };
const allowed = await opa.evaluate(path, input);
console.log(allowed ? "allowed!" : "denied!");
For providing types, use
interface myInput {
user: string;
}
const input: myInput = { user: "alice" };
const allowed = await opa.evaluate<myInput, boolean>(path, input);
console.log(allowed ? "allowed!" : "denied!");
HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json
{ "input": { "user": "alice" } }
When the result of the policy evaluation is more complex, you can pass its type to evaluate
and get a typed result:
interface myInput {
user: string;
}
interface myResult {
authorized: boolean;
details: string[];
}
const input: myInput = { user: "alice" };
const result = await opa.evaluate<myInput, myResult>(path, input);
console.log(result.evaluated ? "allowed!" : "denied!");
If you pass in an arbitrary object as input, it'll be stringified (JSON.stringify
):
class A {
// With these names, JSON.stringify() returns the right thing.
name: string;
list: any[];
constructor(name: string, list: any[]) {
this.name = name;
this.list = list;
}
}
const inp = new A("alice", [1, 2, true]);
const allowed = await opa.evaluate<myInput, boolean>(path, inp);
console.log(allowed ? "allowed!" : "denied!");
You can control the input that's constructed from an object by implementing ToInput
:
class A implements ToInput {
// With these names, JSON.stringify() doesn't return the right thing.
private n: string;
private l: any[];
constructor(name: string, list: any[]) {
this.n = name;
this.l = list;
}
toInput(): Input {
return { name: this.n, list: this.l };
}
}
const inp = new A("alice", [1, 2, true]);
const allowed = await opa.evaluate<myInput, boolean>(path, inp);
console.log(allowed ? "allowed!" : "denied!");
HTTP Request
POST /v1/data/authz/allow
Content-Type: application/json
{ "input": { "name": "alice", "list": [ 1, 2, true ] } }
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": ["property-a is OK", "property-B is OK"]
}
you can turn it into a boolean result like this:
const allowed = await opa.evaluate<any, boolean>(path, undefined, {
fromResult: (r?: Result) => (r as Record<string, any>)["allowed"] ?? false,
});
console.log(allowed ? "allowed!" : "denied!");
In the StyraInc/styra-demo-tickethub repository, you'll find a NodeJS backend service that is using @styra/opa
:
router.get("/tickets/:id", [param("id").isInt().toInt()], async (req, res) => {
const {
params: { id },
} = req;
await authz.evaluated(path, { action: "get", id }, req);
const ticket = await prisma.tickets.findUniqueOrThrow({
where: { id },
...includeCustomers,
});
return res.status(OK).json(toTicket(ticket));
});
In StyraInc/opa-typescript-example-nestjs, we have an decorator-based API authorization example using @styra/opa
:
@Controller("cats")
@AuthzQuery("cats/allow")
@AuthzStatic({ resource: "cat" })
export class CatsController {
constructor(private catsService: CatsService) {}
@Post()
@Authz(({ body: { name } }) => ({ name, action: "create" }))
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get(":name")
@AuthzQuery("cats") // For illustration, we're querying the package extent
@Decision((r) => r.allow)
@Authz(({ params: { name } }) => ({
name,
action: "get",
}))
async findByName(@Param("name") name: string): Promise<Cat> {
return this.catsService.findByName(name);
}
}
Please refer to the repository's README.md for more details.
Note: For low-level SDK usage, see the sections below.
- executeDefaultPolicyWithInput - Execute the default decision given an input
- executePolicy - Execute a policy
- executePolicyWithInput - Execute a policy given an input
- executeBatchPolicyWithInput - Execute a policy given a batch of inputs
- health - Verify the server is operational