npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@parsifal-m/backstage-plugin-opa-authz-react

v1.1.1

Published

A Backstage frontend plugin that allows you to use OPA for authorization in the Backstage frontend

Readme

Backstage OPA Authz React Plugin

This Package is still being worked on and could contain breaking changes without notice. Please use with caution!

A React component library for Backstage that enables frontend authorization using Open Policy Agent (OPA).

Overview

This plugin provides React components and hooks to control UI visibility and access based on OPA policy evaluations. Unlike the standard Backstage permissions framework, this library gives you direct control over authorization logic without requiring application rebuilds.

Key Features

  • 🔒 Flexible Authorization - Pass custom policy input data to OPA
  • 🎯 Component-Level Control - Hide/show UI elements based on policies
  • 🔧 Framework Independent - Works alongside or instead of Backstage permissions
  • Performance Optimized - Built with SWR for efficient caching and revalidation

When to Use This Plugin

Use this library when you need:

  • More context in authorization decisions than Backstage permissions provide
  • To decouple authorization logic from your application code
  • Fine-grained control over UI element visibility
  • Custom plugins with flexible authorization patterns

You can wrap your components with the RequireOpaAuthz component to control the visibility of components based on the result of a policy evaluation.

The component uses the useOpaAuthz hook to perform the policy evaluation, and it will render the children only if the policy evaluation allow is true.

Why Choose This Over Backstage Permissions?

While the Backstage Permissions framework works well for many cases, this library provides additional flexibility:

  • Custom Policy Input - Send any data structure to OPA, not just predefined permission types
  • Decoupled Authorization - Change authorization logic without rebuilding your application
  • Fine-Grained Control - Perfect for custom plugins requiring specific authorization patterns
  • Direct OPA Integration - Work directly with OPA policies without framework limitations

Note: This library can work alongside the OPA Permission Wrapper for comprehensive authorization coverage across your Backstage instance.

Prerequisites

Before using this plugin, you need to install and configure the OPA backend plugin. See the OPA Backend Plugin documentation for setup instructions.

Installation

1. Install Required Packages

You'll need both the React plugin (frontend) and the OPA backend plugin:

# Install frontend plugin
yarn add --cwd packages/app @parsifal-m/backstage-plugin-opa-authz-react

# Install backend plugin (if not already installed)
yarn add --cwd packages/backend @parsifal-m/plugin-opa-backend

2. Configure the API

Add the OPA Authz API to your packages/app/src/apis.ts file:

export const apis: AnyApiFactory[] = [
  createApiFactory({
    api: scmIntegrationsApiRef,
    deps: { configApi: configApiRef },
    factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
  }),
  // Add the OPA Authz API
  createApiFactory({
    api: opaAuthzBackendApiRef,
    deps: {
      fetchApi: fetchApiRef,
    },
    factory: ({ fetchApi }) => new OpaAuthzClientReact({ fetchApi }),
  }),
  ScmAuth.createDefaultApiFactory(),
];

Usage

Option 1: RequireOpaAuthz Component (Recommended)

The RequireOpaAuthz component is the easiest way to control component visibility based on OPA policy evaluations.

import { RequireOpaAuthz } from '@parsifal-m/backstage-plugin-opa-authz-react';

function MyProtectedComponent() {
  return (
    <RequireOpaAuthz
      input={{ action: 'read-policy', resource: 'catalog' }}
      entryPoint="authz"
    >
      <div>This content is only visible if the policy allows it!</div>
    </RequireOpaAuthz>
  );
}

Props:

  • input - The data sent to OPA for policy evaluation
  • entryPoint - The OPA policy entrypoint to evaluate
  • children - Components to render when access is allowed

Option 2: useOpaAuthz Hook (Advanced)

For more control over the authorization flow, use the hook directly. You can rename the destructured variables for clarity:

import React from 'react';
import { useOpaAuthz } from '@parsifal-m/backstage-plugin-opa-authz-react';
import { useEntity } from '@backstage/plugin-catalog-react';

const EntityDeleteButton = () => {
  const { entity } = useEntity();

  const {
    loading: policyLoading,
    data: policyResult,
    error: policyError,
  } = useOpaAuthz(
    {
      action: 'delete',
      resource: 'catalog',
      entityRef: entity.metadata.name,
      entityKind: entity.kind,
    },
    'rbac',
  );

  if (policyLoading) {
    return <div>Checking delete permissions...</div>;
  }

  if (policyError) {
    return <div>Permission check failed: {policyError.message}</div>;
  }

  if (!policyResult?.result.allow) {
    return null; // Hide button if not allowed
  }

  return (
    <Button color="error" onClick={() => handleDelete(entity)}>
      Delete Entity
    </Button>
  );
};

Option 3: useOpaAuthzManual Hook (Manual Control)

For scenarios where you need to trigger policy evaluation at specific times (e.g., after fetching user data):

import React, { useEffect, useState } from 'react';
import { useOpaAuthzManual } from '@parsifal-m/backstage-plugin-opa-authz-react';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';

const ConditionalScaffolderAction = ({ templateRef }) => {
  const catalogApi = useApi(catalogApiRef);
  const [userGroups, setUserGroups] = useState([]);

  const {
    loading: authLoading,
    data: authResult,
    error: authError,
    evaluatePolicy,
  } = useOpaAuthzManual(
    {
      action: 'create',
      resource: 'scaffolder',
      template: templateRef,
      userGroups: userGroups, // Use state that gets updated
    },
    'rbac',
  );

  useEffect(() => {
    // Fetch user context first, then trigger policy evaluation
    const fetchUserAndEvaluate = async () => {
      try {
        const userEntity = await catalogApi.getEntityByRef(
          'user:default/current',
        );
        const groups =
          userEntity?.relations?.filter(r => r.type === 'memberOf') || [];
        const groupRefs = groups.map(g => g.targetRef);

        // Update state first - this will cause the hook's input to change
        setUserGroups(groupRefs);

        // Then trigger policy evaluation with the updated input
        await evaluatePolicy();
      } catch (error) {
        console.error('Failed to fetch user context:', error);
      }
    };

    fetchUserAndEvaluate();
  }, [catalogApi, evaluatePolicy, templateRef]);

  if (authLoading) return <div>Checking template permissions...</div>;
  if (authError) return <div>Permission check failed</div>;
  if (!authResult?.result.allow) return null;

  return (
    <Button onClick={() => handleScaffolding()}>Create from Template</Button>
  );
};

Practical Example

Here's a simple example in a metaphorical plugin that allows you to deploy things, showing how to restrict deployment actions during business hours:

import React from 'react';
import { Button } from '@material-ui/core';
import { RequireOpaAuthz } from '@parsifal-m/backstage-plugin-opa-authz-react';

export const DeploymentActions = ({ environment }) => {
  const currentHour = new Date().getHours();
  const isWeekday = new Date().getDay() >= 1 && new Date().getDay() <= 5;

  return (
    <div>
      <Button variant="outlined" color="primary">
        View Logs
      </Button>

      {/* Deploy button only shows if policy allows */}
      <RequireOpaAuthz
        input={{
          action: 'deploy',
          environment,
          hour: currentHour,
          isWeekday,
        }}
        entryPoint="deployment_policy"
      >
        <Button variant="contained" color="primary">
          Deploy to {environment}
        </Button>
      </RequireOpaAuthz>

      {/* Rollback always restricted during business hours */}
      <RequireOpaAuthz
        input={{
          action: 'rollback',
          environment,
          hour: currentHour,
          isWeekday,
        }}
        entryPoint="deployment_policy"
      >
        <Button variant="contained" color="secondary">
          Rollback
        </Button>
      </RequireOpaAuthz>
    </div>
  );
};

OPA Policy Example (deployment_policy.rego):

package deployment_policy

default allow = false

# Allow deployments to dev anytime
allow {
    input.action == "deploy"
    input.environment == "dev"
}

# Allow production deployments only outside business hours (6 PM - 8 AM)
allow {
    input.action == "deploy"
    input.environment == "production"
    not business_hours
}

# Never allow rollbacks during weekday business hours (9 AM - 5 PM)
allow {
    input.action == "rollback"
    not business_hours
}

business_hours {
    input.isWeekday
    input.hour >= 9
    input.hour < 17
}

This example demonstrates blocking dangerous operations during business hours - deployments and rollbacks are hidden when policies don't allow them, preventing accidental production changes during peak usage.

Demo and Examples

For a complete working example, check out our demo frontend plugin that demonstrates practical usage patterns with the RequireOpaAuthz component.

API Reference

RequireOpaAuthz Component

| Prop | Type | Description | | ------------ | ------------- | ------------------------------------------------------------------- | | input | PolicyInput | Data payload sent to OPA for policy evaluation | | entryPoint | string | OPA policy entry point to evaluate against | | children | ReactNode | Content to render when policy allows access | | errorPage? | ReactNode | Optional content to show on loading/error states (defaults to null) |

useOpaAuthz Hook

const { loading, data, error } = useOpaAuthz(input, entryPoint);

Returns:

  • loading - Boolean indicating if evaluation is in progress
  • data - Policy evaluation result from OPA
  • error - Error object if evaluation fails

useOpaAuthzManual Hook

const { loading, data, error, evaluatePolicy } = useOpaAuthzManual(
  input,
  entryPoint,
);

Returns: Same as useOpaAuthz plus:

  • evaluatePolicy - Function to manually trigger policy evaluation using the current hook input

Note: The evaluatePolicy function doesn't accept parameters. It uses the current input object passed to the hook. To evaluate with different data, update your state and the hook input will automatically update.

Troubleshooting

Common Issues

  1. "OPA API not configured" - Ensure the OPA backend plugin is installed and the API is registered in apis.ts
  2. Policy evaluation fails - Check that your OPA server is running and accessible
  3. Component not hiding - Verify your policy returns { "allow": true/false } structure

Related Plugins

This plugin works well with other OPA plugins in the ecosystem:

Contributing

We welcome contributions! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Sign your commits with git commit -s
  5. Open a pull request

For significant changes, please open an issue first to discuss your proposal.

License

This project is released under the Apache 2.0 License.