623af82e
extracted
5. Yatish Mehta - No 'Pundit' Intended - wroc_love.rb 2025.txte6121f6f7ed7| Status | Model | Tokens (in/out) | Duration | Cost | Nodes/edges | Read set (nodes/edges) | Time |
|---|---|---|---|---|---|---|---|
| completed | claude-opus-4-7 |
197,088
/
9,848
59,034 cached ยท 6,900 write
|
138.7s | - | 22 / 40 | 83 / 1 | 2026-04-18 07:44 |
| failed | claude-opus-4-7 |
RubyLLM::BadRequestError: You have reached your specified API usage limits. You will regain access on 2... | 2026-04-17 16:18 | ||||
Our next
guest came to us from San Francisco
yesterday and he's still a little bit
jetlagged but he's ready to talk about
authorization in Rails. Ladies and
gentlemen, please welcome Yatish Meta.
Cool. Um, thank you everyone and yeah,
welcome to my talk, no pun intended,
duple the power of rails authorization
and yes, the pun is
intended. Cool. Uh, before we start, I
have a trivia simple trivia
question. Uh, what should we put in the
place of the question mark so that it
prints both the
statements? And I have a book giveaway.
You could uh fork.
Uh, no. Okay.
It's It's way more simpler than you guys
think.
No, you want both the sentences to be
printed. So the output should be both. I
like Sernik, I like Paknik.
Oh,
I think you could do if puts I like
Cernic and false.
Yep, that is correct. But we had two
people who answered that question. So
I'll find another book for
you. Uh,
cool. Um, yeah, my name is Yatish Mata.
Uh, I live in San Francisco, California
in the States. Uh, I've been a Rubist
for more than 10 years. I'm active on
Twitter, GitHub.
Um I'm the tech lead at Asana for
authorization. Uh if you don't know what
Asana is, Asana is a work collaboration
platform to manage your projects, your
tasks. Um
uh we are based out of San Francisco as
the HQ, but we have an engineering
office in Warsaw and we are hiring. So
feel free to talk to me uh after the
session if you're
interested. Cool. Uh let's start with
authentication and authorization. Like
these are the terms that people often
get mixed
up. So authentication deals with who you
are. It makes sure the user is what they
claim they
are. Username, password, SSO, SAML.
These are the terms that are related
with
authentication. In Rails, we'll use gems
like device ra to manage
authentication.
Authorization on the other hand uh deals
with what the user can do, what actions,
permissions are allowed for this user.
Now since I'm traveling a good example
is passport is for
authentication but the visa stamp is for
authorization. So let's talk about
authorization rails and like the
different approaches uh with which we do
authorization. Uh let's start with the
basic implicit
authorization. Um this is an example of
an implicit authorization.
We fetch the
project. Okay. We fetch the project that
is uh scoped to the current
user and if the project exist we allow
the user otherwise we render
unauthorized.
Technically there's no explicit
authorization check but our query scopes
make sure that the only the user who has
access to the projects or projects that
the current user owns are shown to that
user. So there's no authorization as a
framework or explicit check but a
queries and scopes kind of take care of
that. Again, it's simple to implement,
but now we have a business logic and
authorization rules combined
together. As our app grows, it's
difficult to manage your authorization
logic this
way. So, what's the next
solution? You have the can can
gem. Um, it allows you to create an
ability.rb file where you define all the
rules and permissions for the user.
So in this example, we've defined the
read permission for the post resource
for the
user and then you can check the
permission in the controller
um using like the authorize method and
also
uh check it on your
views. Simple, right?
But as your app grows, the ability rb
file also grows and becomes totally
unmanageable. This is a 60line snippet
of code from an open-source ability.
file. Now just imagine what would the
whole file look like.
This even if you can't read it is an
example of a real production app ability
file which can grow even from thousands
of
lines. Now your permissions you could
also have permissions which depend on
each other and that makes it really
difficult to refactor them because you
have to make sure that refactoring one
permission doesn't affect the other
unknowingly.
So what do we get with can can we get
separation of
concerns all our authorization logic is
in a single place but again as the app
grows ability file keeps growing that's
not something that's
scalable so what's
next pundit is the most popular
authorization gem in Rails there's also
action policy
which is like a revised version of
Pundit with additional features but it
has the same structure the same
philosophy. So uh Pundit provides the
concept of policies. You can define a
simple poro class with your resource to
define the
permission. Each instance method is a
permission
check. Since it's plain old Ruby, you
can define any logic within the
permission
method. So this is an example of like
the update uh permission and some some
logic inside
that again similar to can can you can
use it in the controller and in views.
So kind of let's build a policy uh for
an actual kind of a real use case.
Uh so
uh let's build an authorization policy
for a project management
app. So the the features are you have to
organize like work with projects and
tasks. Tasks can be part of multiple
projects. Um you can have users that get
added to the projects with admin,
editor, commenter roles.
You can even add the whole teams to
projects with the same rules. So either
you add the users directly or you add
like the whole team engineering or
marketing to the project and then
associate them with the same uh admin
editor commenter
roles and again users can be part of
multiple teams. Just a
[Music]
Cool. Um, so this is kind of the visual
representation of our use case. You have
users, projects, task,
teams. Task can belong to multiple
projects. Your projects can have users,
can have teams, and then your users
belong to a team.
Um let's define the permission for who
can edit a task. The rules uh for this
are if you're the creator of the
task or if you're an admin or editor of
the project, you can edit the task uh
and you can be an admin or edit editor
either directly or through a
team. So how do you go about writing a
task policy for this? Um and we want to
write it for like who can edit the task,
right? So you'll define an update uh
permission. The first rule was to check
if the task is created by the user. So
you're checking for the
creator. Uh then you'll check if you
have admin or editor access
um
directly or then you have admin or
editor access through a team. I mean the
end of the code is cut off but that kind
of the top uh uh method the public
method gives you an idea that if any of
these three conditions are true you are
allowed to edit the
task. Soon your PM comes in and decides
to add a new use case. If the task is
sensitive then only admins uh of the
project can edit the task.
So you go back to your code and be like
okay I need to like modify my
policy. Um you're going to like check
first whether the task is sensitive.
Then only allow for
admin access either directly or through
a team. If it's not you just use the old
path. Um as you can see it's it started
to increase complexity. And let's see
what are the limitations of of pundit as
the app
grows. The first is refactoring. It's
really difficult to refactor a
permission since changing one permission
can impact upstream and downstream
permissions. You have to be very
carefully navigate the code through the
policies to make sure there's no
unintended impact. Like for example, if
you change the definition of editor
access, uh there could be permissions
upstream that are using it uh and you
want to make sure like you don't have
any unintentional
impact. There's no automated way to find
these dependencies. So you have to
navigate through the code to see what it
depends
on. Next is performance.
when calculating the permission you'll
have to add our load like intermediate
objects
um you can optimize the code but it's
easy to fall for N plus1 issue here I
know we spoke about N plus1 as a feature
but normally it's not
um so uh you can still optimize the code
but it's easy to fall for that trap
um you cannot cache the result of the
evaluation between requests. Since the
permission logic is a black box, you
have to evaluate evaluate the permission
logic for every
request. Fun fact, Figma recently
mentioned in their blog that around 23%
of their SQL queries are related to
access
control. So, how do you check why a user
was not allowed? Normally you'll get
into the Rails console, load the user
object, load the task or the record and
like run that permission to see which
component was
false. In the same situation, you need
to know which is more dangerous need to
know why the user was allowed if it's a
bad actor or unintentionally if they got
access. There's no easy way to audit or
debug these permissions. There's no easy
way to find out why the user got or did
not get
access. And finally, since Panda doesn't
store any data,
uh there's no way to identify
uh what users have access to. So there's
no way to request hey give me the list
of users that have this permission to a
resource or hey give me the list of
users that can edit this task. I it's
always the opposite where given a user
given a task it's going to return
whether I can edit or
not. So what do we do? Is there a better
way to model our authorization
logic? And yes, there is. There's fine
grain authorization
FGA. It's also called as relationship
based access
control. This is based on Google's
Zanzibar project. Um Google put out a
paper about the Zanzibar project and how
they manage authorization
uh a distributed authorization across
apps uh for all their
products. Um so the way FGA or
relationship based access control works
is that you're storing relationship
between entities as these
pupils. Um they're also called as access
control lists. So what you're storing is
user A where A is the identifier has a
relationship of editor with project
X. So you're going to store these
relationships in in like a subject
relationship object
format. Um and this is what your
authorization data would look like,
right? you would have a collection of
these uh
pupils and um and with the relationship
with
it. The other way to look at this data
is it actually ends up being a graph
because a subject in one pupil could be
a resource or an object in another one.
So it's a graph of entities connected to
each other by
relationship and the nodes being the
entities and the relation being the
edge. So this is your authorization data
which is static which is
stored. Then on top of this
authorization data you define rules for
your permissions.
Um u again it doesn't matter if you can
read this but this is like the rules
which are defined for this graph uh on
how to navigate this graph what parts
are allowed for this permission. Uh
there are multiple FGA providers and
each one of them have their own DSL uh
but the idea is the same where you're
kind of defining a model defining a
schema and defining rules uh for your
permissions.
So in this world uh checking a
permission of whether user two has edit
access to task
one rather than checking hey does it
have access what you're saying is is
there a path from user two to task one
in this graph based on the rules that we
defined in the previous slide and if
that path
exists then uh user two is allowed to
edit Task
one. Uh, so how do we implement FGA or
relationship based access control in
Ruby or
Rails? Um, I created a gem called
granity to support fine grained
authorization in Rails
apps. So let's look at how it
works. Um, you add the gem, you install
the artifacts. Uh, it's going to create
a migration and a model. Uh this is
where you're going to store the
pupils. Uh once you add the gem, we need
to define the authorization model or the
authorization
schema. Um so you define this. It's
going to be in your
initializer. Um but this is how you
would define. First you'll go through
the entities of your app. So we're
defining like resource type user which
is one entity.
Then you have another one which is team.
So resource type
team. And then you define a relationship
between them. So for for this one I'm
defining a member relationship. So what
this says is the team object or the team
entity and the user entity are related
with the relationship name called as
member. Similarly we define the project
entity. uh it it's going to have a
relationship with the user, a
relationship with the team and like
multiple types of relationship admin
editor and again if you have more access
controls you'll add them
here. Um you define the task entity now
it's a relation between project and a
task and for the creator it's between
the task and the user. So again you're
taking those graphs and edges and you're
defining that u in in the schema which
is again Ruby code. So it generates a
graph object out of
it. Uh once you've defined the
relationships you can define a
permission. Um it it has like a DSL of
include any and include all. So you can
have like nested and and or logic to
combine uh your rules. So what in this
case it's saying that hey if any of
these conditions or any of this
relationships
exist then you're allowed to edit the
tasks in that
project. In the same way you're defining
one on the task object saying that hey
if you're the creator or if you can edit
the project tasks then you can edit this
task. Again you can use either a
relationship or you can use another
permission. So in this case we're using
a permission which was defined on
projects. So once you've defined the
schema it's time to create those
relationships. It's time to create those
tpples.
Um, again, you're going to create those
relationships based on your flow. So,
every time a new user is added as an
editor, you'll kind of call this in your
code path. Again, if you add or remove
or modify, you would do the
corresponding changes. So in this case
in my code path wherever I'm adding a
user to a project as an editor I'm going
to include this so that it creates that
tpple for my authorization
data. And finally how do I check my
permission? Again it's a simple check
permission where I pass in the user pass
in the task pass in the permission name.
It's going to use my rules, go through
the authorization data, and if the path
exists, it means yes, I'm allowed to do
it. And uh if if no, then it's going to
return
false. Uh again like can can and ponder
the helper methods so that you can kind
of use it in a concise way uh rather
than passing all of those attributes.
So how does this win over the previous
approach? Right? What are the advantages
of FGA and granity over a previous
approach? Um since the authorization
model is defined as a schema, you can
easily check for dependencies. You
literally have a graph object. So you
can query it, question it saying like,
hey, give me the permissions that they
are dependent on. So when you're
refactoring, you're really confident
about what other permissions would get
impacted by
it. Um
performance there's no intermediate
object loading. The tupils navigate
through ids. So it's just when it's
navigating, it's not loading the team
object. It's not loading the user
object. Uh it's just going through those
tpples. Granity also has smart caching.
So you can tell it to cache results
between request since it knows the
dependent
nodes if uh it can cache the result and
if there are no changes it's just going
to return the result from the cache and
if you add a new node add a new edge it
knows it knows its dependencies and it
can invalidate those
results. So technically you don't need
to evaluate the permission for every
request.
auditability. Now you can ask it along
with the permission result to also
return you the path. This makes debug
debugging easier and you know why a
particular user got access. So in this
example uh you could get access to the
task maybe through a team or maybe
through direct access since you have the
whole path you know why the user got
access.
um reverse lookups because you're
storing these relationships. Uh you can
actually ask the opposite question of
like hey give me the subjects for
permission edit for the uh this resource
and it'll give you the list of users
back. Again this is I I've put a star on
this because there's a caveat. It
doesn't have pageionation. So it depends
on what your authorization model is. It
could load all the users. So you want to
be careful with
this. Yeah. Finally, um we we spoke
about uh FJ in Rails, but there are many
external open-source authorization as a
service providers for fine grain
authorization. So you have uh open FGA,
OZ, OSO, permit.io uh all of them are
based on Google's Zanziba project.
and they are pretty advanced. They have
like a playground. You can set up the
model uh test data. So I would highly
recommend to like kind of go through
these just to understand FGA uh
understand authorization as a service um
like just the concept of uh relationship
based access
control. If you have uh used or if you
use an external service for this uh this
is how the flow would look like. Your
authorization data and model would live
in the o as a service. So every request
uh you would have to call that service.
So let's say I call the update endpoint.
It's going to first check hey this user
this resource does it have access?
returns me back the answer and then
depending on that I'll either return the
result or say it's you're unauthorized.
Uh again these authorization as service
projects are like really optimized
cached they they kind of promise like
singledigit millisecond latency but
still it's it's an external service.
This is very useful if you have like a
distributed system microservices
architecture.
So yeah just to summarize you start
simple with pundit you add something
like granity for fine grained
authorization and then once you reach
like like a distributed a truly
distributed system um you would consider
something like uh the options that I
gave before authorization as a service.
Yeah that is
it.
Any questions?
Yeah, thanks for the talk. Is it
working? I just Yeah, thanks for the
talk. I just didn't realize uh how
exactly would you model the sensitive
task case? Yeah. So you would create a
new entity called as sensitive task and
it would have a relationship with task.
So if it's a sensitive task, you would
have a relationship with the same
ids. So on the left hand side you would
have an entity called a sensitive task.
Right hand side you would have the
actual task and the relationship would
be something like is
sensitive. Even if it's not a model
within your authorization model you can
create a new entity out of it. Okay.
Understood. Thanks. And the other one is
um how actually difficult is it to
uh to keep all this u relationships in
sync. So that's a challenge um because
technically what you're doing is you're
taking your belongs to many to many
foreign key relationships and you're
moving it out or you're duplicating it
from your model to this normalized entry
attribute value kind of a table, right?
Like imagine if all your foreign keys uh
or all your many to many were in a
single table. That's what you're doing.
So either so that's you have to manage
that explicitly where after create
commit you have to make sure you're
making an entry in in the authorization
model as well. So yeah but at least if
it's within the monolith it's within the
single transaction when you use o as a
service it has to be synced between your
rails app and the external
service. So yeah you have to manually
manage those relationships. So you kind
of have to have a logics that checks
that yeah sorry you have to have a
logics that duplicated logics kind of
that checks if you do have this uh kind
of yeah but you would do it only for
relationships that you kind of care
about for authorization.
Yeah, great talk. I um I've seen some of
these authorization as a service um
providers and it's cool to see a a gem
to do it locally. I was curious though
in pundit you have the ability to define
scopes in your policies as well when
doing this relationship based access
control. What is the pattern that you
reach for to sort of define the the
scopes for you know the different parts
of the app right? Um that's something
that's I mean when you have relationship
based access control you don't have that
but
you're you have to like explicitly query
it like that's something that's missing.
Um in pundit again you could have like
just a black box of logic that you can
add where here you have to be like it's
very constrained you have to define that
relation. I can't just have any Ruby
code to be checked. So those those are
like constraints with this for sure.
Any other questions?
Okay, thank you very much. Awesome.
Thank you.