Conf42 JavaScript 2023 - Online

Building Authorization with Node.js: Dos and Don’ts

Video size:

Abstract

This talk will provide best practices for building authorization with Node.js. To do so, he will show how to use existing tools in the ecosystem: OPA, OSO, OPAL, Zanzibar, and others.

Summary

  • There are two terms in access control that usually confuse developer authentication and authorization. The difference is similar to passport and flight ticket. But authentication has also many advanced features. For developers it's simple because there are many software providers that let you feel all that advanced feature as a service.
  • Gabriel: Today I'm going to talk about how can we do better authorization in Javascript application. He says a more clean way to code authorization is using middleware. Gabriel: We need more granular level of permitio to make sure a user is allowed to do some action.
  • Before we are doing authorization, we need to model the permission model. What is the easiest way to model is think about three principle. Second point is the level of authorization application stack built. Why it is important to design a permitio model.
  • Policy languages is a trend that coming by for the last years. Every couple of months you see a new policy language release. Today we are going to talk about three policy language. Open policy agent, Cedar and permitio. Easy to config and use the code for the most invest use cases.
  • Opal is a Docker compose that has Opal and also some infrastructure that we create running it locally. All the decision happened not in the application, happened as a result of the policy. In production you can scale this OPA client again and again for every application that you need.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
My name is Gabriel, and although this session recording from home remotely, I like to travel. This is one of my hobbies. And usually when I travel I'm using two documents. First one is on the left side, my passport, and the right one is the flight ticket. And let's talk about the difference between those documents because they are probably both allow me to get on a plane and travel. But I have different roles. The passport is a form of my identity. I declare, I verify my identity with a passport, but I cannot go on a flight, only with a passport. I need to authorize myself to a flight. A passport also, for example, is something that I'm renewing once for ten years. But flight ticket is something that I get again and again for every flight that I'm going to. The details on both documents are different. One is the details that verify my identity, like a biometric image picture or my birth date. The other is the particular details that allow me to go on the flight, like the seat number and the passport is something I'm using. Once in the airport, the flight ticket is something that I'm using again and again. I'm showing it in the check in, and then I'm showing when I check the luggage, then in the security, then in the gate. And if someone start fighting me on my seat, I'm taking my flight ticket out and tell him hey, one a is my seat, right? So others are many difference between passport and flight ticket. But what is matter for Javascript or software? It's matter because there are two terms in access control that usually confuse developer authentication and authorization. Access control is a term, is a general term to describe who allowed access to our application. Every application has some level of access. It could be node JS applications. And this is what we are going to talk about today. Authentication is the step where our users verify their identity, right? Same as they do in password, in passport, yeah, they do it with password, but password is not that safe. So there are features like biometrics, multifactor authentication. But authentication happened once and then we maintained session, but authorization, the determination of the user permitio to know if a particular user that already authenticate themselves is authorized to do an action in our software. It is something that happened again and again require permitio model, right. We need to understand what a user can or cannot do. It has evolved much more data than authentication session include. It's not something to revoke because it exists or valid only for one time that we are checking for permitio. So the difference between authentication and authorization are similar to passport and flight ticket. But authentication has also many advanced features. It's not as simple as it seems like multifactor authentication. We want to make sure that the user that just put a password is the real user. So we are trying like maybe sms or authentication app. We want also to let user use their own identity. So we give them like social, they log in with their Google account or GitHub. We might want to let them use biometric or passwordless mechanism to authenticate. We also need to manage all our users, right? We need some identity provider to manage identities. We also need to manage the sessions, manage the registration of the user, all the flows, the login flows, the sign up flow. We need to verify account. And there are many, many features in authentication. So authentication is not that simple, right? But for developers it's simple because there are many, many software as a service providers that let you feel all that advanced feature as a service. To implement, for example clerk into your Nodejs or JavaScript app, you need only three lines of code, four lines of code, and then all the advanced authentication feature is implemented into your app. But in authorization, which is not simple than authentication, and we'll go through it and you'll see that authorization also require some features, right? We still work really hard. It's still the old way of trying to code everything in authorization ourselves. Burn a lot of time my name is Gabriel and today I'm going to talk about how can we do better authorization in Javascript application short about intro about myself I am in software development for many years, JavaScript considered as my others language. Every time that I just need to sketch something on a paper, I'm doing it in JavaScript. But I'm also a big fan of front end and security and this is how I got to everything related to access control in applications. And now I'm leading the developer relation in permitio. Permit IO is a startup that do authorization as a service and today I'm going to share with you a lot of learns that I learned along my career about having better authorization on application. But before we dive into authorization, let's try to understand what happened maybe started ten years ago that let developers create simplest authentication using providers like we saw like OSO or clerk. Two innovations happened a couple of years ago. The first is auth. OAuth is a protocol that allowed to decouple the authentication server from any kind of application. Here we can see in the center API, gateway and also API services. Could be any kind of language or technology for any of them. We can once connect the authentication server which in their turn return a token. The base of OAuth is creating token based authentication instead of managing sessions. And every time that we need to know if a user authenticated, we are calling a session directory and check if session exists. We are using token token that we can validate and we can do it in a decentralized way. So every application that needs to do authentication can call the auth server to make authentication flow with the user and then by use token verify the identity of the user. Another thing that came to public lately, or maybe it's not lately anymore, it's like I think already ten years, is JWT Json web token. This is a format of token that specially work well in web that can be verified without calling the authentication server. So in the architecture we see here, the user is made the authentication against the OAuth server and then each API, microservice or application can probably verify their authentication with this architecture service provider like cloud service provider. Or if you just develop a platform, you can do it yourself. You can create auth server that creating centralized authentication and decentralized authentication in one and a secure way using JWt. That's in general the way that all the authentication service provider works. But how is go is authorization. So the most simple form of authorization is the if statement that we see here, right? We can ask if a user is admin and only then allow them to delete something. It's a fan code because the user delete themselves. We don't want to allow admin delete themselves, but that's a demonstration. How can we code authorization? Probably this way is not the smartest way to do. First, every time we are changing something, we need to change the code and we involve the code in the logic that we need to do so. It's also hard to read. So a more clean way to code authorization is using middleware. Here we can see an example of express middleware. It's a piece of JavaScript code for delete endpoint of a user. And instead of mixing the business logic of checking if a user is admin with the function itself, we are taking it out into a middleware. And then we check if a user can remove something. This is better than just ask if a user is admin. But it's not easy because for example we need more permitio models than just asking for a user role. We can see here in example that we need more granular level of permitio to make sure a user is allowed to do some action. This level of granularity, this level of granularity can change. It could be like role, as we saw in the middle of role required, but can also be based on attributes of a user or maybe data from a different service. Well, data from a different service is also not that good, because sometimes it's not part of the request itself. So middlewares, as we can see in the code here in the right hand side, we are trying to know what is the tier of the user. This tier of the user is not something which is part of the request itself. It's something that we need to go to ask somewhere in the middle of the business logic. So no matter how we will make our middleware granular, sometimes we will have a need where we need to do authorization decision in the middle of the code, right? I think we already saw enough code to understand that the way we are coding authorization, it may be work, but it's not that clean. Because the requirement of authorization with permissions model with roles, or maybe more granular permitio models and endpoint, and maybe more granular level of permission enforcement on endpoint. It's getting complex and complex. For example, we can see that users getting decisions that need only for partial authorization and not for the whole scope of the function. And this code is getting dirtier and dirtier. But clean code is not the only problem of the way we are doing authorization today. Sometimes we need a different environment with different permitio, right? We do continuous integration, we want to create, test, and we want maybe the admin in the staging environment to be allowed to do what super admin allowed in production. How can we separate these policies, these permissions between environments? It's not only staging in production, it could be different tenant environment, it could be different customer environment. So as you can see, having policy or having enforcement or having decision, mostly, most accurately having decision of authorization in the code. It's something that can be complex when your application getting complex. And also the real fact is we love javascript, but sometimes we need to do a different app, we need to do a data application. So we are using Python and we want to keep the same logic across our applications, across our stack. And also let's go a bit back to the clean code thing. Authorization decision is something that could cost performance. And when we need to debug those performance, when we need to get the audit of what the authorization decision does, it's something that can get a hell of decision in case we are doing it in just coding the decision logic as part of our application. Right? Oso, the problem of creating permitio and authorization as part of application is clear. Let's see from the examples we saw what we can do to make sure that authorization service. Let's say that we are now creating an authorization microservice, a dedicated one. What it has to be first, and for the last thing, we want it to be declarative. The way that we are coding complex imperative code to get authorization decision. It's something that hard to read and also hard to manage and audit. We want it also to be generic, right? We don't want it to be in a level of roles or attributes. We want it to have to support any kind of permitio models that we want. Permitio decision could be simple as if is admin, but could be complex as if is admin and a paid user and own the document. We want it also to be unified in one place, so we can run it in multiple application, we can manage it for multiple environments. We want it to be agnostic to the language that we are using. And because we want it to be agnostic, we want it to decoupled from the code. So every time we need to change, every time a PM come and wants us to change authorization decision, we don't want to change the application code, we want to decouple it. And we also want it to be easy to audit, because access control is something that can create very high level security vulnerabilities. So we really want it to be easy to audit when we are getting maybe wrong decision. So what is the way to build this authorization microservice? So let's start with a simple five steps. Maybe not simple, but I'll try to make it simple. The first is model. Before we are doing authorization, we need to model the permission model. What do you mean? What does that mean? Authentication is something that can create out there. We decide who are giving us identities, we decide how do we authenticate users. But authorization is something that driven by our application needs, and hence we need to model what our application need. What is the easiest way to model is think about three principle. In our application, we'll always have three principles when we want to get a decision, a user or other principle, maybe a service, an action, what they want to do, and a resource, the resource that they want to do on. So for example, a permission decision is. Is a monkey allowed to eat a banana? A monkey is the user, eat is the action and banana is the resource. Yeah, but in a real world it's. Of course if an admin allowed to delete a document, or if an admin allowed to delete a document created today or if an admin allowed to delete a document that they are not owning. Right? Oso every authenticate authorization decision is happening with these three principles so that help us to model all our authorization rules. Second point is the level of authorization application stack built usually from let's say UI or gateway, right the way that we are existing to the user, the application logic and the data itself, the database in application logic, which is the main part of our code. We want to do enforcement. We want to have a simple function that get these three principles, user action and resource and return. True or false. We don't want to do the decision in the application logic. We want us to do the logic of the decision in a different service and in the application itself only enforce the permission. We also sometimes need to do feature toggling in the UI or the gateway to make sure that users are not getting the resources or not allowing to do or to see what they need to see. And we also sometimes want on the data itself do data filtering. So to not return the user something that they are not supposed to get. So we know what is the principles, how this enforcement function signature should look like accepting these three principles and returning either data filtering or true or false result. And then we need to design a permission model. Why it is important to design a permitio model because that help us to create a good picture of how the authorization policies looks in our application. Let's talk about four common permission model, why and when and where use each. First is the access control list. Access control list is a very old one. I consider it as an end of life model. It's usually used in system and firewalls and switches, et cetera. It's maintain a list, a long list of users and the list, let's say title is an action and a resource or only a resource. And the users that appear in the list are actually the one that allowed to do something. In this kind of permission model it's hard to scale because we need to maintain all those lists and we need to use data that is saved in the list. If for example, we want to get the role of a user, or we want to get a decision based on a user that assigning to some list, but it's not appearing the list is in a different system. It's really hard. So ACL is not fit for modern application. RbaC role based access control is like the generic name for authorization. Why? Because it's very simple to assign users roles. We can take a user and tell this user is admin, this user is a moderator and then we can easily assign permissions for particular resources and actions of these roles, right? So if we want to give a nice user experience for our application admin, we will use RBaC because then we can extend hey, if you want your users to be able to do a particular action on a resource, just assign them the role. While arbac is easy to define and also to use and audit, because every decision we make we can easily get here, hey this got this decision because they are role. It's hard to inspect into resource, right? If for example, we want to allow users to do something only on their own resources, we don't have the granular level to do that. Because as we can see here, the granularity of RBAC is only in the resource level, not in the resource instance level. Also, others is no resource. Also there is scalability is limited because if we need, for example to have more roles or more granular way on looking on users, it's getting hard. IBAc on the other hand, is the most complex way to model policies yet. It's important to understand that it might be complex, but it still gives our users a very easy way to define policy rules. If you have a way to define policy rules, then you can take attribute of the users and the resource, right? In software, everything probably has attributes, user as attributes like name, role, job description, address, city, whatever it is, resources as attributes. And then we can create policy roles that consider set of attributes together with an action and decide if a user is allowed or not allowed to do something. While ABAC is the most granular policy level, it's hard when we, for example, need relationships. So for example, if we think about Google Drive, Google Drive has account in account, there are folders, in folders, there are documents. A document belong to a user's not user, not because an attribute, a document because it's a part of a folder. So we need to find a way to propagate or derive policy rules based on relationships in application. So to solve this level of granularity in resource instances and derivation of permissions, there is a model called relationship back substance based access control reback where we can create a graph database of connections between things and make our policy rules based on this graph and say if there is a tuple a relationship between resources, then we can take the granular level of resource instances and create policy rule based on them. So now we understand the first point model, our application permission. We have in mind the model that we need. Maybe it's RBaC maybe RBaC with RebaC, maybe RBaC with ABAC. We understand who are our users, what are our resources and what are the policy rules that we need to create. We draw it nicely on a whiteboard and we want to author it, right? So we want to have like authorization microservice with all these policy rules. So one way is of course do it imperative as we did before, like create code that do it. But then it means we are coupling ourselves again to application code. So how about the idea of create contracts for creating policies? What if for creating, let's say Arbac policies or arbac rules. We have a new language that dedicated for that. And this is one of the things that I want to introduce you today. Policy languages. Policy languages is a trend that coming by for the last years. Every couple of months you see a new policy language release. This language is not a real programming language, it's more a configuration language. It's not a language usual. It's not a language that you need to learn from scratch, but to know how to use that. There are also policy languages that based on Yaml or Json. So you donts need to learn a new syntax. Today we are going to talk about, I'm going to mention three policy language. The first one is the most famous one, open policy agent. Open policy agent is a decision engine. Like think about a compiler that we can run a policy program and get a decision if it's allowed or not. Open policy agent has a language called Oprigo where you can declare your own policies and then enforce them by the decision that happened in the open policy agent. There is also a language called Cedar. Cedar is a language released by AWS as an open source. This is the language that we are going to demonstrate today. And there are type of languages for Openfga, for Google Zanzibar. Google Zanzibar will not expand on it too much, but we mentioned relationship based access control. And sometimes relationship based access control require much more sophisticated policy engines for that. So if you need a specific case of relationship based access control, let's say that you have tons of resources and instances and millions of users, you might need this language. But let's dive for the cedar language. Here we can see, and sorry it looks small in the left side here we can see three examples of rules that declared in cedar language. Let's focus on the one in the middle so we can see that we are declaring a rule of permit. Permit means a user is allowed to do something. And as you can see, one of the things in cedar is that we are declaring policy with the three principles of authorization, the user, the action, and the resource. So we say here like an ABAC policy, that said, a user is permitted to do update, list, create task, update task, or delete task on a resource, any resource or any users only when this user is one of the resource editors, right? So see how in what a clean way we can describe a policy rule. Let's see on the left hand side, which is a most sophisticated ABAC rule. And we say if a resource as owner and the owner is a principal, then we allowed something in the left hand side. In the left hand side in the small letters is an RBAC policy. Like we say, only a user with the role admin is allowed to do something. Now we can have one policy repository where we create all our authorization rules, okay? And all our application use the same authorization rule. All what we need to do is call the cedar agent that loaded with these rules and make an authorization decisions or authorization enforcement based on it. Another option of course is to have your authorization service with some UI. Here is what we built in permitio to manage policies and then you can just let your product manager or whatever it is to edit the policies without even knows the code. We are using our UI to create and configure policies and then all these policies is created or generated as policy as code. And then you can use the UI for what? Easy to config and use the code for the most invest use cases. So we understand how we author the policies, but how we check that these policies is what we mean. So there are agents. Agent is like a decision maker where we are passing three principles, a particular user, an action description and a resource, or maybe attributes of a resource, and this agent returning a result. True or false allowed or not allowed. Right? So once we have an agent, we're starting to form a complete authorization service. We have a policy repository where we author all the policy. Then we have the agent where we can get the queries. And then what we left is to enforce, right? So we can enforce it by calling the policy agent. Here we can see that we are calling the Cedar agent in an HTTP interface. As you can see, we are passing the principal action and a resource from our request to the cedar agent. And the context is also something that we can use to expand the data that we are passing with the policy enforcement request. And in this case or with this example, we are not writing the decision logic in our application anymore. We are keeping our application logic only in enforcing permitio and think about this is great. We actually decouple the policy from the code. We have a unified place for our policy. We can make it generic to the language because we can do it in an HTTP API or some other RPC forms. It's easy to audit because all the decision happen in this agent. So if we need to audit something we're just going and see what happened in the decision and it's declarative. So if someone want to understand what happened or how our policy configured, it's easy to see just by looking at the code. Great right? So we are enforcing policy. But not only that, there is a nice framework called Castle for Front end where we can load all our authorization, all our policy decision that we need for our front end from our cedar agent and then cedar agent and then we can in the front end feature toggle everything we need when we need to audit. It's easy to see because all the cedar agents and all the policy engine are probably auditing what happened when we got the authorization decision. So we understand the framework and let's see in practical how is that tools like. So let's say that we want to have now authorization microservice that we can easily use. So authorization include control plane as we describe where we author the policy and a data plane where we get the decision and the application when we do the enforcement. And on the other side the data sources. We need to enrich our policy with data to make better decision. Right? Policy decision maybe need data from external sources. How we do all that not from scratch. We can use OPal. Opal is an open source tool that lets you with one docker compose file spin up in minutes everything we saw before. Create a complete microservice that can help you with do authorization in all your application using one interface with audit. Here you can see the QR code. The QR code will lead you to a GitHub repository of Opal that actually you can see others. Everything related to OPA and how to spin it up. We'll also do a demo soon. And Opal, as you can see here is a complete authorization service that you need for all your application. OPA include Opal client and Opal server. The server is probably the policy store. It's actually connected to a git repository where you store all your policy. You can also separate it to environment and maybe even application. Also you configure. You can configure in Opal what is the sources that your data need. And then the Opal client is running as a sidecar in your application or for multiple applications. And those applications can get decisions that based on the logic that exists in the central part. If you see at Hopeal, this is actually giving you the same as JWT and OOH server bring to authentication. You have one server to configure all the policy needed like OSO server. And you have instead of making the calls to the configuration themselves, you are calling the sidecar like verifying JWT that's sitting close to the application, maybe even as a binary and making very fast decision. Now it's a good time for a demo. So here is the code that I want to discuss about. First let's talk about the Opal Docker compose. This repository exists on GitHub. I'll share a link later. And actually the first part of this repository is Docker compose that has Opal and also some infrastructure that we create running it locally. Let's start from the bottom where we are loading opal. So we are loading the OPA server image. Remember opal server is actually where we are storing the policy and configuring the data. So as you can see here, I'm saying the policies sit in this git repository. This git repository is actually a mock repository that I'm launching in another docker container here. And I also say I need data from external resource. This external resource can be let's say your CRM system or CRM service. Also here I'm just mocking it from an NginX server. But I'm configuring the policy configuration and then I spin up the Opal client, the decision agent. I actually wrap decider agent with the Opal client. The nice thing is that in production you can scale this OPA client again and again for every application that you need and you will always get the same decision. Let's see now how our applications look like. So I have here a simple nodejs application. This application has route for like let's say a blog. There is article we can create, article we can update, we can get it, we can delete it. And we have a middleware. But a difference than a middleware that we saw in the beginning that can dirt our code. This middleware actually always do the same logic. No matter how complex is the decision, we need to do it only enforce it by this tree principle, right? So we can see here we call the opal client that actually is the cedar agent. And even if we for example need to do this authorization in the middle of the code, we are not dirting the business logic, we are just doing the same authorization function again and again. So let's see how it is work. So I already run opal here. Opal is getting the git. As you can see here is pulling the policy git from our repository and let's see how our rules looks like. So here is the folder of the policy. First we have an admin policy. The admin policy said that every user with the role admin actually can do anything on the system. The user policy said that a user can do only get right? Think about it like anonymous user. So a user with no role can do only the get action writer can do more actions than that. For example, a writer can do post and put. Of course it will also derive the user attribute and writer can also post and put permitio. Now I already ran this node JS application. Let's try to do something in a user. So I'm trying here to post an article as a user. And as you can imagine, I'll not be able to do that. Access denied. But what happened if I'll try to do the same one as a writer? Okay, so here I'm doing as a writer. I can see that the article created, right? So all the decision happened not in the application, happened as a result of the policy. Cedar of the cedar policy. The nice thing is that if I want to change my policy, so for example, I want now to do ABAC, okay, I want the writer, I want them to be allowed to write article and publish it immediately. Only if, let's say they are senior in the system. If they have lot of karma. If they don't have this karma, I don't want them to be allowed to publish published articles. So in a traditional way, I should go to my application and write this policy logic in a policy as code way. I'm just having to change the policy here. So as you can see, I limit the permission. I limit the permission to do post and put of writers into writers with more than 1000 karma. But that's valid only if the article is published. If the article is not published, I'm still allowed them to do it. Okay, where is karma getting from? So if you remember, we have here this data json. This data json. Think about it as an identity provider, which is something also that we are connecting to the authorization microservice. So the admin will be allowed to do everything. It's not part of the ABAC, but the writer, the senior writer will be able to do the publish. True, because he had more karma than the writer that has only 800 karma now we change the writer cedar file and what we need to do is just commit this file into the git repository, right? Let's do this way git commit minus a minus m abac policy. Now I committed the file and as you can see here, as soon the opal will get an update that a new change is happened to the policies code soon. We can see that it takes a bit of time to update sometimes, but it's still really fast because you don't need all the software development CI CD to make it. And as you can see here it got a notification of a new policy that updated. If I'll try now to do this call in the same of writer, what will happen? The access will be denied because this writer has not enough karma. But if I'll do the same but with the senior writer user the article will create it. It's amazing. I haven't changed any kind of user data or any kind of application code and the authorization microservice did everything for me. And also remember there are not much than just implement like the Opal SDK application. And one of the most amazing thing I also have here a python application that actually consume the exact same authorization service. So we can take one authorization microservice and ship everything into all of our application. Isn't that amazing? So Opal probably can help you reach in open source everything you have with authentication provider for authorization using policy as code and in the most advanced way you can see permitio. In the other end is the commercial tool on top of Opal. I'll not stick on it too because as I say it's a commercial tool but I'm really inviting you to try it. It has also the option to edit all the policies in the UI and scale it and store it and much more feature than Opal offer. But to start Opal is definitely enough and I really want to ask you to give a star for Opal. OPal is an open source OSO you can support also in contribution, but the first basic support for open source is of course giving it a start, giving our more power to continue maintain it to continue the way for a better access control for all the application out there. Thank you very much.
...

Gabriel Liechtman Manor

Director, Growth & DevRel @ Permit.io

Gabriel Liechtman Manor's LinkedIn account Gabriel Liechtman Manor's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways