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. But authentication has also many advanced features. For developers is simple because there are many, many software as a service providers. But authorization also require some features, right?
  • 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 permissions 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 principles. The way that we are exposing to the user the application logic and the data itself. Second point is the level of authorization applications stack built.
  • Let's talk about four common permission model. Why and when and where use each. First is the access control list. In this kind of permission model it's hard to scale. ABAc is the most complex way to model policies yet.
  • 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. There are agents. Another option is to have your authorization service with some UI.
  • Opal is a Docker compose that has Opal and also some infrastructure that we create running it locally. The policies sit in a git repository that I'm launching in another docker container. In production you can scale this opal client again and again for every application that you need and you will always get the same decision.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hello everyone and welcome to Con 42 JavaScript. 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. The 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, 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 telling hey, one a is my seat, right? So there are many differences 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 J's 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 permissions to know if a particular user that already authenticated themselves is authorized to do an action in our software. It is something that happened again and again require permission 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 permissions. 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 multi factor authentication. We want to make sure that the user that just put a password is the real user. So we are trying maybe sms or authentication app. We want also to let user use their own identity. So we give them like sociology login 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 signup flow. We need to verify account and there are many, many features in authentication. So authentication is not that simple, right? But for developers is 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 node J's 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. Batting authorization, which is not simple than authentication. And we will 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, but 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 mother 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 permit IO. Permit IO is a startup that do authorization as 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 applications. 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 Ozero. Two innovations happened a couple of years ago. The first is Oauth. 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 need to do authentication can call the OAuth 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 especially 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 are just develop a platform, you can do it yourself. You can create all of 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 always go with 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 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 permission models than just asking for a user role. We can see here in example that we need more granular level of permissions to make sure a user is allowed to do some action. This level of granularity this level of granularity, granularity can change. It could be like role, as we saw in the middle of all 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 sometime is 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 permission 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 permissions, right? We do continuous integration, we want to create tests 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 cutting authorization decision. It's something that could cost performance. And when we need to debug those performances, when we need to get the audit of what the authorization decision does, it's something that can't get a hell of decision in case we are doing it in just coding the decision logic as part of our applications, right? So the problem of creating permissions 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 permission models that we want. Permission decision could be simple as if his admin, but could be complex as if his 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 decouple from the code. So every time we need to change, every time a PM comment 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 then 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 needs. What is the easiest way to model is think about three principles. 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 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 is of course, if an administrator 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. So 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 applications stack built usually from let's say UI or gateway, right. The way that we are exposing 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 application itself only enforce the permission. We also sometimes need to do feature toggling in the UI or the gateway to make sure the user 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 permission 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 usually used in system and firewalls and switches, etcetera. It's maintain a list, long list of users. And in 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 explain hey, if you want your users to be able to do a particular action on a resource, just assign them the role. While RBAC is easy to define and also to use and audit because every decision we make we can easily get see hey this got this decision because they enroll. It's hard to inspect into resource, right? If for example we want to allow users to do something only on their owned 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 there 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 is getting harder. ABAc in 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 let give 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 has 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 is belong to a user, not user, not because an attribute of 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 applications. So to solve this level of granularity in resource instances and derivation of permissions, there is a model called relationship. By accessing some based access control rebuild where we can create a graph database of connections between thing 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 we can the idea of create contracts for creating policies, what if for creating, let's say RBAC policies or RBAC 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 usually 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 don't 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 allowed or not. Open policy agent has a language called Rigo 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 Cder. Cder 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 CDer 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. 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 9th thing in CDER 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 policy rule. Let's see on the left hand side which is a most sophisticated ABAC role. And we say if a resource as owner and the owner is a principal, then we allowed something in the last side, in the left hand side did a small letters is a 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 CDR 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 permit IO 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 this policy is, 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 invested 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 the result. True or false allowed or not allowed, right? So once we have an agent, we starting to form a complete authorization service. We have a policy repository where we over 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 in a resource from our request to the seeder 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 permission. 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 a 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 SIDAR agent and then SiDAR agent and then we can in the front end feature toggle the everything we need when we need to audit. It's easy to see because all the seeder 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 looks 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 described 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 can 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 there everything related to Opal 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. Opal 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 applications. 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 couple of four multiple applications. And those applications can get decisions that based on the logic that exists in the central part. If you see it hopal, this is actually giving you the same as JWT and OAuth server bring to authentication. You have one server to configure all the policy needed like oo server. And you have instead of making the calls to the configuration themselves, you are calling the sidecar like verifying JWT that 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 to discuss about. First let's talk about the Opal docker compose. This repository is exist 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 opal 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 resources. This external resource can be let's say your CRM system or CRM service. Also here I'm just mocking it from a Nginx server but I'm configuring the policy configuration and then I spin up the opal client, the decision agent. I actually wrap the see their agent with the opal client. The nice thing is that in production you can scale this opal 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 looks like. So I have your simple node 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 different than a middleware that we saw in the beginning that can dart 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 these three principles. Right? So we can see, 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 dirtying 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 git, 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 more actions than that. For example, a writer can do post and put. Of course it will also derive the user attributes and writer can also post and put permissions. 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 still doing the 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, the policy seeder of the cedar policy. The nice thing is that if I want to change my policy, so for example, I want now to do a bank, okay, I want to the writer, I want them to be allowed to write article and publish it immediately. Only let's say they are senior in the system. If they have like lot of karma. If they don't have this karma, I don't want them to do the opera 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 a limit the permission. I limit the permission to do post and put of writers into writers with more than thousand 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 this karma getting from? So if you remember we have here this data JSON, this data JSON. Think about it as a 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 m a back policy. Now I committed the file and as you can see here the soon the opal will get an update that a new change is happened to the policy as 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 for 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 permit IO. 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 so 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 maintaining it, to continue the way for a better access control for all the applications 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