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!