Skip to main content
In this guide, you learn how to use Oso Cloud for the live authorization calls. First, you will remove the old code. After, you can enhance the Polar code for making your authorization even more robust.

Remove the old code

After deleting the inline authorization code from canReadRepo(), the function looks like this:
backend/src/authz.ts
import { resolve } from "path";
import { UserWithRoles } from "../authn";
import { Repository, PrismaClient } from "@prisma/client";
import { Oso } from "oso-cloud";
import * as oso from "oso-cloud";

// Make sure the API key is defined and instantiate the client
if (!process.env.OSO_API_KEY) {
  throw "Missing OSO API key from environment";
}

const oso_url = process.env.OSO_URL
  ? process.env.OSO_URL
  : "https://cloud.osohq.com";
const osoClient = new Oso(oso_url, process.env.OSO_API_KEY, {
  dataBindings: resolve("local_authorization_config.yaml"),
});

// A user can read a repo if they have any role on the repo or its parent organization.
export async function canReadRepo(
  prisma: PrismaClient,
  user: UserWithRoles,
  repo: Repository,
): Promise<boolean> {
  // entities for Oso
  const osoUser = { type: "User", id: user.id.toString() };
  const osoRepo = { type: "Repository", id: repo.id.toString() };

  // Call authorizeLocal() to return a facts query
  // derived from configuration in local_authorization_config.yaml
  const query = await osoClient.authorizeLocal(osoUser, "read", osoRepo);
  // Run the query
  const rows = await prisma.$queryRawUnsafe<oso.AuthorizeResult[]>(query);
  // Save the result to authorizedOso
  const authorized = rows[0].allowed;
  return authorized;
}
The authorization code does four things:
  • Instantiate the Oso Cloud client with a configuration file
  • Create User and Repository resources
  • Call authorizeLocal() to receive a query that will yield the authorization decision
  • Return the query as an authorization decision
You replaced a piece of authorization logic with Oso Cloud without disrupting your existing functionality. The resulting authorization code is explicit, concise, and easy to understand.

Next steps

Add policy tests

With Oso Cloud, you can write tests to validate your policy behaves as you expect. This delivers confidence that your existing functionality remains intact as you extend your policy. Add the following test to validate the “read repository” action:
test "a user with a role on a repository or its parent org can read the repo" {
  setup {
    has_role(User{"alice"}, "admin", Organization{"acme"});
    has_role(User{"bob"}, "reader", Repository{"cool-app"});

    has_relation(Repository{"cool-app"}, "parent", Organization{"acme"});
    has_relation(Repository{"org-configs"}, "parent", Organization{"acme"});
  }

  assert has_permission(User{"alice"}, "read", Repository{"cool-app"});
  assert has_permission(User{"alice"}, "read", Repository{"org-configs"});
  assert_not has_permission(User{"alice"}, "read", Repository{"some-other-repo"});

  assert has_permission(User{"bob"}, "read", Repository{"cool-app"});
  assert_not has_permission(User{"bob"}, "read", Repository{"org-configs"});
  assert_not has_permission(User{"bob"}, "read", Repository{"some-other-repo"});
}

Make use of Polar abstractions and shorthand rules

You do not want to leave your authorization logic as has_permission statements for long. Polar provides some powerful features that will make your authorization logic better encapsulated and more concise. Some of these are: You can incorporate these next. For example, you can modify the existing Polar as follows:
backend/policy.polar
actor User {
}

resource Organization {
  roles = ["admin", "member"];
}

resource Repository {
  permissions = [
    "read",
  ];

  roles = [
    "admin",
    "maintainer",
    "editor",
    "reader"
  ];

  relations = {
    parent: Organization
  };

  "read" if role on "parent";
  "read" if role;
}
This is functionally equivalent to the previous code, but now the available roles and the relationship between repositories and organizations are explicit. If you added a policy test, you can confirm that the logic behaves identically.

Use feature flags

With this approach, you run your existing logic alongside the Oso Cloud logic for a period of time. As a result, you can use feature flags to migrate from one to the other. This provides a phased rollout to monitor app behavior over time. This permits quick restoration of the original logic if something unexpected happens.

Monitor query performance

As your application grows, you may find that database performance degrades. With Local Authorization, the database operation is isolated to a single, distinct line of code. This encapsulation is easy to instrument and monitor performance. If you find that an authorization query starts to become a bottleneck, you can centralize the data for that query in Oso Cloud.
When you centralize data in Oso Cloud, fact queries happen on our servers. This reduces load on your databases, but it requires an initial sync of your data and then updates when it changes. We recommend keeping your data in your application databases so to maintain a single source of truth for authorization data.

Reach out

If you have any questions, we would love to hear from you. Just reach out on Slack and one of our engineers will give you a hand.