Enforce Authorization in your Application
Install the client SDK
Follow the installation instruction (opens in a new tab) to install the Oso Cloud client SDK for your language.
Add enforcement
Most authorization comes down to "can this user perform this action on this resource?" We call authorization checks like these enforcement.
The Oso API for enforcement is authorize(user, action, resource)
-- authorize whether this user can perform this action on this resource,
and return true/false. This is what you call in your application to do enforcement.
The user
and resource
arguments are represented using a generic object with a type and an ID. This way, Oso can
reference these objects against the data that you've stored.
For example, suppose we have a controller method to read a repository. We'll typically build the user object from authentication information, and extract the repository ID from the request path parameters.
And finally, we can check the user is authorized to perform the read
action on the repository.
// get global oso instanceimport { oso } from "../app";router.get("/repos/:repoId", async (req, res) => { const user = { type: "User", id: req.user.id }; const repo = { type: "Repository", id: req.params.repoId }; if (!(await oso.authorize(user, "read", repo))) { return res.status(403).send("Unauthorized"); } // fetch repository from database, etc.});
Add enforcement using local data
Oso Cloud's authorize_local
API can resolve authorization requests by combining data in Oso Cloud with data from your application database.
If your users have access to millions of resources, it may be cumbersome to store all of those IDs directly in Oso Cloud. Consider building an "Issues" microservice for GitCloud. The policy says that users can access issues that belong to repositories they have access to. If repositories have lots of issues, it could be slow or brittle to store all repository-to-issue relationships in Oso Cloud. Instead, you can tell Oso to look in your own database for information about which issue belongs to which repository. This way, Oso Cloud can determine whether the current user has access to a given issue without having to replicate all of the issue-repository relationships to Oso Cloud.
Configuration
You'll use a YAML file to configure how Oso Distributed Authorization looks for authorization data in your database:
facts: has_relation(Issue:_, String:parent, Repository:_): query: SELECT id, repository FROM issuessql_types: Issue: UUID Repository: UUID
This example tells Oso that has_relation
facts that associate issues with their parent repositories
are resolved by running a query (SELECT id, repository FROM issues
) against your application's
database, rather than by looking them up directly in Oso Cloud.
The sql_types
section is optional, but strongly recommended. It maps resources in Oso Cloud (e.g. Issue, Repository) to their data types in your application database. This allows Oso List Filtering to more effectively use indexes, improving query performance.
Usage
When you initialize the Oso Cloud client, provide the YAML configuration:
oso = OsoCloud::new( ... data_bindings: "path/to/data_bindings.yaml")
Returning to the repository example, first use authorize_local
to tell Oso Cloud to partially evaluate whether the user is authorized to perform the read
action on the repository.
Then, use the resulting query to finish the evaluation using local data.
# get global oso instancerequire 'app/oso'get '/repos/:repoId' do user = { "type" => "User", "id" => request.user.id } repo = { "type" => "Repository", "id" => params[:repoId] } query = Oso.authorize_local(user, "read", repo) if !Issue.connection.select_value(query) raise Sinatra::PermissionDenied end # fetch repository from database, etc.end
Policy Tests & Enforcement
The authorize
API is intentionally designed to mirror the allow
API for policy tests.
When writing a policy test, the test assertions are exactly what you would be passing in via the authorize
API.
test "repo members can read their repositories" { setup { has_role(User{"alice"}, "member", Repository{"repo-1"}); } assert allow(User{"alice"}, "read", Repository{"repo-1"});}
@app.route("/repos/<str:repoId>")def get_repo(repoId): user = { "type": "User", "id": "alice"} repo = { "type": "Repository", "id": "repo-1" } if not oso.authorize(user, "read", repo): raise PermissionDenied
Talk to an Oso Engineer
If you'd like to learm more about how to use local data in enforcement or have any questions about this guide, schedule a 1x1 with an Oso engineer. We're happy to help.