Skip to main content

Implement the Logic in Oso Cloud

In this guide, you will implement the authorization logic in Oso Cloud. You will run the Oso Cloud logic alongside your existing logic to confirm that the Oso Cloud implementation is correct. After, you will replace the authorization logic with Oso Cloud.

Convert the logic to Polar

In Oso Cloud, you express your authorization logic in Polar. Polar is a concise and flexible language that provides powerful abstractions for both authorization models and application entities. For now, you will implement the logic with simple permission checks in order to mirror the existing application logic. Once that works, you can improve the logic further with more abstractions available in Polar. Recall the authorization logic from the previous step:
src/authz.ts
// A user can read a repo if they have any role on the repo or its parent organization.
function canReadRepo(user: UserWithRoles, repo: Repository): boolean {
  const orgRole = user.orgRoles.some((orgRole) => orgRole.orgId == repo.orgId);
  const repoRole = user.repoRoles.some(
    (repoRole) => repoRole.repoId == repo.id,
  );

  return orgRole || repoRole;
}
This says that a user has the read permission on a repository if:
  • they have a role on the repository or
  • they have a role on the repository’s parent organization
Write this logic in Polar:
policy.polar
actor User {}

resource Organization {}

resource Repository {}

# A user has the read permission on a repository if ...
has_permission(user: User, "read", repository: Repository) if
  # ... they have a role on the repository
  has_role(user, _, repository);

# A user has the read permission on a repository if ...
has_permission(user: User, "read", repository: Repository) if
  # ... they have a role on the repository's parent organization
  organization matches Organization and
  has_relation(repository, "parent", organization) and
  has_role(user, _, organization);
This Polar code does the following:
  • Entities in authorization logic are actors or resources.
    • An actor is the entity that is requesting a permission (e.g. User)
    • A resource is the entity upon which the actor is requesting permission (e.g. Organization, Repository)
  • Two has_permission statements that mirror the logic from our application.
Copy the Polar code and save it to your Oso Cloud Workspace. For a more basic introduction, you can review our quickstart guide.

Use the Oso Cloud SDK alongside existing authorization code

Next, you will use the Oso Cloud SDK to evaluate authorization requests in your application. Do not replace your existing logic immediately. Instead, add the check API call for your language alongside your existing logic. In TypeScript, this is authorize() Import and instantiate the Oso Cloud client.
src/authz.ts
import { 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 osoUrl = process.env.OSO_URL
  ? process.env.OSO_URL
  : "https://cloud.osohq.com";
const osoClient = new Oso(osoUrl, process.env.OSO_API_KEY);
Then, add the authorize() call to the canReadRepo() function, alongside the existing logic.
The call to osoClient.authorize() in the following sample will not return the correct results until you send data to Oso Cloud in the next step.Do not use in production without providing data as facts.
src/auth.tz
// A user can read a repo if they have any role on the repo or its parent organization.
export async function canReadRepo(
  user: UserWithRoles,
  repo: Repository,
): Promise<boolean> {
  const orgRole = user.orgRoles.some((orgRole) => orgRole.orgId == repo.orgId);
  const repoRole = user.repoRoles.some(
    (repoRole) => repoRole.repoId == repo.id,
  );

  const authorizedInline = orgRole || repoRole;

  // entities for Oso
  const osoUser = { type: "User", id: user.id.toString() };
  const osoRepo = { type: "Repository", id: repo.id.toString() };

  const authorizedOso = await osoClient.authorize(osoUser, "read", osoRepo);
  console.log(
    `User:${user.id} read Repository:${repo.id}: inline: ${authorizedInline}; Oso: ${authorizedOso}`,
  );

  throw new Error(
    "This is sample code that fails without sending the necessary data to Oso Cloud.",
  );
  return authorizedInline;
}
The authorize() function accepts three arguments: an actor, an action, and a resource. It returns true if the actor has permission to perform the action on the resource. If the actor does not have permission, authorize() returns false.
  • The actor and resource arguments are represented by a type and an id.
    • This is how Oso represents entities in your application.
  • In the Node SDK, these entities are expressed as an object with type and id properties.
    • User object: {type: "User", id: user.id.toString() }
    • Repository object: {type: "Repository", id: repo.id.toString() }
  • The permission argument is a string that is the name of the permission.
    • "read"
In the sample above, the User and Repository objects are assigned to the osoUser and osoRepository variables. This simplifies the resulting osoClient.authorize() call.
If you’re using a different client library, the form of the Actor and Resource arguments will be different. Check the documentation for your language’s client for details.

Compare the results from Oso Cloud to the original code

By logging the authorization results of the original code and the Oso Cloud SDK, you can compare them ensure the results are identicial. After that, you can switch to using Oso for live authorization with confidence. If you compare those results now, you’ll see something like this:
backend  | User:1 read Repository:1: inline: false; Oso: false
backend  | User:1 read Repository:2: inline: true; Oso: false
backend  | User:1 read Repository:3: inline: false; Oso: false
backend  | User:1 read Repository:4: inline: false; Oso: false
backend  | User:1 read Repository:5: inline: false; Oso: false
backend  | User:1 read Repository:6: inline: true; Oso: false
backend  | User:1 read Repository:7: inline: false; Oso: false
The calls to Oso always return false, even when the original code returns true. This results because we have not provided Oso the authorization data to determine what permissions the User possesses for the Repository. We will provide these data initially as context facts.