Skip to main content
Rather than converting your authorization data to facts and sending it to Oso with every authorization request, you can use Local Authorization. This will instruct Oso to construct a query to fetch that data directly from your database. Setup with two requirements:
  1. A configuration file that maps your facts to SQL queries
  2. Use the Local Authorization API in your application code

Write the configuration file

You configure Local Authorization with a yaml file passed to the Oso Cloud client on instantiation. This configuration file lists the fact signatures used for your authorization queries. It also associates them with the SQL that generates the facts from your application data. Recall that you send up to three context facts to Oso for any “read repository” authorization request:
has_relation(Repository: repoId, "parent", Organization: orgId)
has_role(User: userId, role, Organization: orgId)
has_role(User: userId, role, Repository: repoId)
These are the fact signatures that to include in the config file.
local_authorization_config.yaml
facts:
  "has_relation(Repository:_, String:parent, Organization:_)":
    query: 'SELECT id, "orgId" FROM "Repository"'
  "has_role(User:_, String:_, Organization:_)":
    query: 'SELECT "userId", role, "orgId" FROM "OrgRole"'
  "has_role(User:_, String:_, Repository:_)":
    query: 'SELECT "userId", role, "repoId" FROM "RepositoryRole"'
sql_types:
  Organization: integer
  Repository: integer
  User: integer
Note the following:
  • Any value returned by the query is represented in the fact signature by using the wildcard character (_).
  • Any value not returned from a query must be explicitly specified in the fact signature by its type and value (e.g. String:parent).
  • The sql_types: section is optional, but strongly recommended.

Use the Local Authorization API

You provide this configuration file to the Oso Cloud client when you instantiate it. Then you use the Local Authorization API to make authorization decisions without passing data to Oso Cloud. In the Typescript SDK, you use authorizeLocal in place of authorize.
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> {
  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() };

  // 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 authorizedOso = rows[0].allowed;

  console.log(
    `User:${user.id} read Repository:${repo.id}: inline: ${authorizedInline}; Oso: ${authorizedOso}`,
  );

  return authorizedInline;
}
Major changes are highlighted:
  • The osoClient instantiation is modified to include the local_authorization_config.yaml file.
  • The call to authorize() is replaced with a call to authorizeLocal()
  • The query returned from authorizeLocal() is executed against the database to resolve the authorization request
Notice that the canReadRepo() function is smaller. This is because we do not need code in that function to get role data and convert it to facts. It is handled by the query returned from authorizeLocal() now.
You can write the query that authorizeLocal() returns to a log if you’d like to inspect it.
Confirm that the results from Oso Cloud are still correct.
backend  | User:1 read Repository:1: inline: false; Oso: false
backend  | User:1 read Repository:2: inline: true; Oso: true
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: true
backend  | User:1 read Repository:7: inline: false; Oso: false
backend  | User:1 read Repository:8: inline: false; Oso: false
backend  | User:1 read Repository:9: inline: false; Oso: false
backend  | User:1 read Repository:10: inline: false; Oso: false
The results are identical to our previous authorization code. All that remains is to replace the original authorization logic with the Oso Cloud version.