Oso in 5 minutes
Oso helps developers build authorization into their applications. If you’ve never used Oso before and want to see it in action, this guide is for you. We’re going to walk through how to use Oso to add authorization to a simple web server.
To follow along, clone the Rust sample app:
git clone https://github.com/osohq/oso-rust-quickstart.git
Run the server
Our sample application serves data about expenses submitted by users. The sample application has three important files.
One file defines a simple Expense
class and some sample data stored in a map.
A second file has our HTTP server code, where we have defined a route handler
for GET
requests to the path /expenses/:id
. We’ve already added an
authorization check using the
Oso library to control access to
expense resources.
The third file is the Oso policy file, expenses.polar
, and is currently
empty.
Run the server with Cargo, which will also install project dependencies:
$ cargo run
With the server running, open a second terminal and make a request using cURL:
$ curl localhost:5050/expenses/1
Not Authorized!
You’ll get a “Not Authorized!” response because we haven’t added any rules to
our Oso policy (in expenses.polar
), and Oso is deny-by-default.
Let’s start implementing our access control scheme by adding some rules to the Oso policy.
Adding our first rule
Oso rules are written in a declarative policy language called Polar. You can include any kind of rule in a policy, but the Oso library is designed to evaluate allow rules, which specify the conditions that allow an actor to perform an action on a resource.
In our policy file (expenses.polar
), let’s add a rule that allows anyone
with an email ending in "@example.com"
to view all expenses:
allow(actor: String, "GET", _expense: Expense) if
actor.ends_with("@example.com");
Note that the call to ends_with is actually calling
out to
the String::ends_with
method
. The actor value passed to Oso is a
string, and Oso allows us to call methods on it.
The Expense
and String
terms following the colons in the head of the rule
are
specializers, patterns that control rule
execution based on whether they match the supplied argument. This syntax
ensures that the rule will only be evaluated when the actor is a string and the
resource is an instance of the Expense
class.
Once we’ve added our new rule and restarted the web server, every user with
an @example.com
email should be allowed to view any expense:
$ curl -H "user: alice@example.com" localhost:5050/expenses/1
Expense(...)
Okay, so what just happened?
When we ask Oso for a policy decision via Oso.is_allowed()
, the Oso engine
searches through its knowledge base to determine whether the provided
actor, action, and resource satisfy any allow rules. In the
above case, we passed in "alice@example.com"
as the actor, "GET"
as the
action, and the Expense
object with id=1
as the resource. Since
"alice@example.com"
ends with @example.com
, our rule is satisfied, and
Alice is allowed to view the requested expense.
If a user’s email doesn’t end in "@example.com"
, the rule fails, and they
are denied access:
$ curl -H "user: alice@foo.com" localhost:5050/expenses/1
Not Authorized!
If you aren’t seeing the same thing, make sure you created your policy
correctly in expenses.polar
.
Using application data
We now have some basic access control in place, but we can do better.
Currently, anyone with an email ending in @example.com
can see all expenses —
including expenses submitted by others.
Let’s modify our existing rule such that users can only see their own expenses:
allow(actor: String, "GET", expense: Expense) if
expense.submitted_by = actor;
Behind the scenes, Oso looks up the submitted_by
field on the provided
Expense
instance and compares that value against the provided actor. And
just like that, an actor can only see an expense if they submitted it!
Alice can see her own expenses but not Bhavik’s:
$ curl -H "user: alice@example.com" localhost:5050/expenses/1
Expense(...)
$ curl -H "user: alice@example.com" localhost:5050/expenses/3
Not Authorized!
$ curl -H "user: alice@example.com" localhost:5050/expenses/3
Not Authorized!
Feel free to play around with the current policy and experiment with adding your own rules!
For example, if you have Expense
and User
classes defined in your
application, you could write a policy rule in Oso that says a User
may
"approve"
an Expense
if they manage the User
who submitted the expense
and the expense’s amount is less than $100.00:
allow(approver: User, "approve", expense: Expense) if
approver = expense.submitted_by.manager
and expense.amount < 10000;
In the process of evaluating that rule, the Oso engine would call back into the application in order to make determinations that rely on application data, such as:
- Which user submitted the expense in question?
- Who is their manager?
- Is their manager the user who’s attempting to approve the expense?
- Does the expense’s
amount
field contain a value less than $100.00?
For more on leveraging application data in an Oso policy, check out Application Types.