This package contains helpers for using @open-policy-agent/opa from React.
@tanstack/react-query
.npm install @open-policy-agent/opa-react
yarn add @open-policy-agent/opa-react
<AuthzProvider>
To be able to use the <Authz>
component and useAuthz
hook, the application needs to be able to access the AuthzContext
.
The simplest way to make that happen is to wrap your <App>
into <AuthzProvider>
.
Add these imports to the file that defines your App
function:
import { AuthzProvider } from "@open-policy-agent/opa-react";
import { OPAClient } from "@open-policy-agent/opa";
Then instantiate an OPAClient
that is able to reach your OPA server, and pass that along to <AuthzProvider>
:
const serverURL = "https://opa.internal";
export default function App() {
const [opaClient] = useState(() => new OPAClient(serverURL));
// other initialization logic
return (
<AuthzProvider opaClient={opaClient}>
{ /* your application JSX elements */ }
</AuthzProvider>
);
See the API docs for all supported properties of AuthzProvider
. Only opaClient
is mandatory.
If your OPA instance is reverse-proxied with a prefix of /opa/
instead, you can use window.location
to configure the OPAClient
:
const [opaClient] = useState(() => {
const href = window.location.toString();
const u = new URL(href);
u.pathname = "opa"; // if /opa/* is reverse-proxied to your OPA service
u.search = "";
return new OPAClient(u.toString())
});
To provide a user-specific header, let's say from your frontend's authentication machinery, you could do this:
const { user, tenant } = useAuthn(); // assuming there's some hook for authentication
const [opaClient] = useState(() => {
return new OPAClient(serverURL, {
headers: {
"X-Tenant": tenant,
"X-User": user,
},
});
}, [user, tenant]);
The <Authz>
component provides a high-level approach to letting your UI react to policy evaluation results.
For example, to disable a button based on the outcome of a policy evaluation of data.things.allow
with input {"action": "delete", "resource": "thing"}
, you would add this to your JSX:
<Authz
path="things/allow"
input={{ action: "delete", resource: "thing" }}
fallback={
<button disabled={true}>Delete Thing</button>
}
>
<button>Delete Thing</button>
</Authz>
See the API docs for all supported properties of Authz
:
loading
allows you to control what's rendered while still waiting for a result.path
and fromResult
can fall back to defaultPath
and defaultFromResult
of AuthzProvider
respectively, andinput
can be merged with the defaultInput
of AuthzProvider
.useAuthz
hook<Authz>
is a convenience-wrapper around the useAuthz
hook.
If it is insufficient for your use case, you can reach to useAuthz
for more control.
In the example above, we had to define <button>
twice: once for when the user is authorized, and as fallback
when they are not.
We can avoid this by using useAuthz
:
export default function MyComponent() {
const { result: allowed, isLoading } = useAuthz("things/allow", {
action: "delete",
resource: "thing",
});
if (isLoading) return <div>Loading...</div>;
return <button disabled={!allowed}>Delete Thing</button>;
}
For questions, discussions and announcements related to Styra products, services and open source projects, please join the Styra community on Slack!