Managing authorization logic can be a daunting task. But what if there was a way to streamline this process, making it both efficient and secure? Enter Cerbos, the solution to complex authorization schemes. ByteGrad’s latest video, “Authorization is easy now (Microservices, Next.js, Cerbos),” showcases how Cerbos Hub effortlessly handles authorization, making it a must-watch for developers and software architects alike.
For those interested in exploring the code used in the demo, it’s available on GitHub.
Authorization doesn't have to be complicated. With Cerbos, you gain a powerful ally in securing your applications, ensuring that your authorization logic is both robust and easy to manage. Whether you're a seasoned developer or just starting out, this video demo offers valuable insights into modern authorization practices.
You're probably familiar with authentication. That's who the user is, but there's something that may even be more important, which is authorization. What permissions does the user have? What level of access do they get to a particular resource? So let's say a company has an expense management app like this.
So currently we are logged in as Lisa. Lisa has a role of a basic standard user, and therefore Lisa can edit this expense, but not approve the expense. But what if we make Lisa a manager? A manager can approve expenses. So now we also wanna show an approve button here in the ui. This is the domain of authorization and this is the front end, but maybe you are already familiar with this.
From the backend side of this, let's say it looks something like this in our app, right? We just have, let's say an A GA API route, and A put API route to. An expense and I'm actually using Next. js in this video, but could be any kind of backend, whether it's an express API or some other API, right? So then if you want to approve an expense, it would look something like this.
You're going to have some kind of update to the expense. So let's say that's a put request. You would get the approved status from whatever the client submitted, and then you would actually update it into your database. And you would say the status of the expense should be whatever is coming here. Now, this is not safe.
Anybody can make a request to our API route, right? So you may say, uh huh, we need to. Make sure the user is logged in. So here's typically where you would use your identity provider, right? Could be next auth, could be kind auth, octa, your, these are your typical identity providers, and they will give you a user object.
If there is no user, the user is not logged in. So you redirect them to log in, let's say, but this is not enough because even if the user is logged in, it doesn't mean that they can just approve expenses, only let's say managers can approve expenses. So here we want to have another step. The Authorization step.
And typically you're going to have a bunch of if checks, if you're going to do it like this, so you could maybe check for their role. So maybe that's part of the user objects. You can check if they are a manager and maybe there are other requirements as well. The user's department needs to be the same as the department of the expense that's going to be updated.
And I'm just writing some pseudocode here. So don't mind the rest quickly lines. And let's say a manager can only approve if the expense is at most, let's say 500. And so it would be something like this. That's so if. Any of that is not true. Then we're going to return something like unauthorized. And only after that is when we would actually update the database.
But you can imagine that we have a lot of rules here, a lot of business logic. There is a maximum amount that a standard manager can approve. And then maybe a finance manager. So if the department of the user is financed, they can approve any amount. And so you can imagine that In the real world, you have a lot of authorization logic.
And right now I'm hard coding this in my app. This is a bad idea because if you have multiple services, multiple apps, you often need the same type of logic. So I would have to duplicate this in other parts of my architecture as well. So then when you make a change, maybe something in the real world in the business has changed.
And now this amount needs to become a thousand dollars. And this is the backend, but we probably want to have a similar logic on the front end. And maybe you have other microservices, maybe you have a mobile app. So you have multiple instances where you have similar logic. So whenever you make a change, you have to update it in all of those instances.
So that is actually very error prone and it can actually become a security issue. So we don't want to hard code this logic. Inside our application or services. We want to have one source of truth where we can put all of our authorization logic. And that's what Cerbos offers us. Cerbos is an open source solution.
You can use it for free and they solve that authorization problem for you. That's what we're going to use in this video. They are of course the sponsor, but even if they weren't sponsoring this video, I would still use them in my own SAS product. So I'm going to show you exactly how to use Cerbos to report.
this spaghetti code. And I will also link to the final code in the description in case you want to take a look at it yourself. Cerbos works on the front end as well, as we'll see in a second. Let's actually start off with the back end though. So let's come back here and let's see how we can replace this spaghetti code here with just one API call to Cerbos.
So you would still do the authentication in the first step, right? So this doesn't change. You can still use your typical identity provider like Kind or Okta or NextAuth. And typically they have some kind of SDK where you can get the user info. They will give you some user information. Object if the user is logged in.
Otherwise we can redirect, let's say. So let's say that that first step actually gives us a user object that looks like this. So Lisa is logged in and Lisa is a manager. And that's the auth and the authentication step. So to make a good authorization decision, we need to know who is making the request.
But we also need something in between here which is actually to get the The resource that the user is trying to perform an action on. So we actually need to get the expense in this case. So you would do something like this, right? I'm using Prisma here, but you would get the actual resource. And in this case, an expense, you would get it from the database.
And let's say that we get something like this. So it has some information here in amount. The status and also who created the expense. The reason that we need to get our resource from the database, by the way, is because we need to make that authorization decision based on the latest data. Since the data in our database is the actual source of truth when it comes to the data, there's no way around it.
We need to actually make that round trip to the database in order to make a safe authorization decision. So now we have user information and the resource information. Now we can get to this authorization step, replace this spaghetti code here with the following. So step three is the actual authorization check.
Here, I'm going to use Cerbos. Most of the time, you only need this one method here, check resource, and you're going to pass all of that information, the user information, the expense information. You're going to pass that to Cerbos. Cerbos has the actual business logic. So the amount up until a manager can approve that type of logic, we're going to give all of that to Cerbos.
Let Cerbos manage that. The only thing we have to do is just pass along all the information to Cerbos and Cerbos will come back with a decision for us. So in this case, in this put API route. Actually, the action that's trying to be performed here would not be view. It would be, let's say approve. And so we have a principle.
This is basically just a user. And we have the resource information. And then we have the action that the principle is trying to perform on the resource. Cerbos will let us know if that's allowed or not. If that's not allowed, if decision, right? So here we get decision if, and then we can check for the action approve.
If the approved action is not allowed, we're going to return a 403 not authorized error. And. Only after all of those steps do we get here user is authorized so we can update the database, right? But in the real world, this third step is actually often the most complicated one, especially in more sophisticated software where you have a lot of access control issues.
So Cerbos actually has a great graphic on their website. So this is the architecture. So here A user is going to make some request to our backend, right? So the dashed lines here, that's our entire backend here. You may have some API gateway, but let's ignore that for now. So here we have some kind of backend application, right?
So I'm using Next. js in this project, but it could be an express web server. Could be any kind of app or service here on your backend. So the user is trying to perform some action on a resource and traditionally you would write spaghetti code directly in your application codes. And there are many problems with that, especially if you have multiple services, you need to duplicate that logic, et cetera.
So instead we're going to use Cerbos here. The only thing that we have to do in our application code is call. Collect the user information, collect the resource information. And that's what we're going to pass over to Cerbos here. And Cerbos will give us an allow or deny decision, but all of the business logic and a manager can only approve up to 500 only approve.
If it's in the same department, that type of logic has to do with actual business requirements. We're going to extract that. Out from our applications, out from our services into Cerbos. And Cerbos is just a microservice running in your environment here. So that's how it all structured. Now to write the business logic, we actually create a so called policy file, right?
So then we give the policy file to that microservice. It will take the policy file that actually holds the actual business logic. It's a YAML file where we actually define all of those rules and that microservice will just Take the incoming information and evaluate those rules with that information.
So if we look at our code right now, we are indeed making that call here, right? So await Cerbos, check resource. We're passing the information. So where is that business logic then? Well, that's what we need to write in a policy file, which we will then give to Cerbos. To write that policy file, we could create some file here in our IDE and write it here, all of those rules.
But the people behind Cerbos now have a new offering called Cerbos Hub. One of the features is that we can log into a dashboard. And so if you make a mistake, you immediately get notified here. You can run tests and they actually have some wonderful examples here as well. So you can find something that's close to your own use case and then just modify it.
So they actually have an expense management example in their dashboard. So here you would then write your actual business logic, right? So here, for example, a user with role admin is allowed to do this. Perform any action. So then here and updating an expense is only allowed by the owner of the resource.
So this is typically the user that created the resource. They can update, they can edit, and you can see in the examples as well, how granular you can get here. And so you can add all sorts of conditions here and really reflect the real world accurately in, in this policy file. So you would write it here.
And then once you're finished, you can essentially just click on it. Copy and paste it here into your IDE. What I like to do is just create a folder here for Cerbos, where I have all of that business logic. So I like to call it policies. And then in there, I'm creating a file called expense. yaml. So here I have the rules for our project here.
So here, let's say a standard user can view and edit under the following condition, the resource. Needs to be created by that user, right? So here we saw already in step two, where we get the actual expense, it has a created by field. So it's created by some user. And if that's the same as the user that's logged in, which is the case here, the user is allowed to view and edit.
And then here a manager can view and edit. Edit and also approve all expenses, even if they didn't create it, but only up to 500. And so here's where we encode all of that business logic. And we can add many more things like a finance manager can approve all expenses regardless of the amount. This is where you have the juicy logic essentially of authorization, right?
So that's the policy file here. Now to actually use it in practice, because it's just a file, that's a some. Server needs to actually use that file to run that logic. So we're going to give that to Cerbos here or what Cerbos calls a policy decision point. So this is the point where the actual authorization decision is made by just a microservice in your environment.
So we need to give the policy file to Cerbos. How do we do that? Well, that's also something we can do with Cerbos Hub now. So besides these playgrounds where you can actually write your policy file, here below you also have a workspace. And this will allow you to easily push your policy. This is the source code and this one is going to be your deposit information should you have zero deposits.
Okay. I'm going to go to deposit dot ETOP Central Bank. I'm gonna have an ATM KM and I need to have a default I would just run this. I will just run no accounts diagram and see, So here's my framework diagram. You can see, what the setup slot is. You have a validation list. So I'm going to add that right here, and then I'm going to just copy and paste this right here.
You can add some Git logic here. We'll just keep it simple. So I'm going to create a new repository here on GitHub so we can push our code there. And then Cerbos Hub can pick that up and it can take the policy file from there. All right. So here we just need to push. I'm just going to add everything and then we can just commit and push to GitHub.
If I refresh now, it should be here. All right. So here I have my repo on GitHub. Now, now I can go back to To my workspace here. And so still in this screen here, I can actually connect it. They call it the policy repository because here is where I will actually have my policies. Right? So I created a Cerbos folder here in my project.
I have one nice centralized place where I have all the business logic for authorization. Then here, I need to connect my GitHub account. So Cerbos hub can actually find this repository and then. In here can find those policy files. We only have one in this case. If you have different kinds of resources, you may have multiple just for organization purposes.
So here I called it Cerbos YouTube. I'm going to continue here. All right. So here I'm using the main branch. Now, which directory are your policies stored? So this is actually in Cerbos slash policies. So make sure you actually add the correct one. Create my workspace. All right. So then you get a secret key.
I'm going to copy that right here and let me actually put that somewhere. I will actually just do that. Paste it here for a second. I'm going to say, yes, I copied this. And now I have my workspace here. I sit in here. We can actually spin up a decision point. Our decision point will be running here and our app can send over the information and get a decision.
So here we can say, deploy a decision points to give you multiple options. We're just going to use a simple Docker container. This is just a command that we can run in our terminal. So I will copy that and I will paste it right here for the moment. So we do need to replace this with the actual secrets here.
So here that's. That's the actual workspace secret that I just had. And then we also need a client ID and a secret. I can go to settings here and here I can generate client credentials. Okay. I can just call that and I get a client ID here. I will copy that and just paste that right there as well as the secret.
Make sure you don't share this to anyone else. All right. So this command is using Docker here. Now I already installed Docker for my desktop. This is the easiest way So on the docker website, you will find in the products docker desktop. So once you download it, you will see something like this. We don't need to know much about it right now.
So don't worry if you're coming from a front end background. So now I have the docker desktop app running in the background. I can just open up my terminal and just paste this right here and actually run the code. That script. And you can see it's starting up that Cerbos’ policy decision point. So this is actually some service that will take the policy file and make that actual decision.
So if I log in, you can see it has a message here, bundle open. And if I go back here to my dashboard here, if I go to those decision points, you can see now there is an instance running here. So what we have now is a policy decision point actually running here. This policy decision point is running that policy.
Policy file that we actually added to our GitHub repo. We connected that to Cerbos Hub here. So Cerbos Hub can automatically take the policy file from there. It will actually run it through their CICD pipeline. So if a test doesn't pass or if there are any syntax issues with the policy file, It will stop right there, but if everything went all right, it will actually give it to the Cerbos policy decision point for us.
Now we actually need to connect our app to that instance so we can actually pass it over, right? So we actually already had the code. I can remove this now. Let me close all of this. So here we were actually trying to make that call, but you can see we still haven't defined Cerbos here and Cerbos doesn't exist right now.
So I'm going to create a separate file here in my library. I like to call it something like Cerbos server, because that's We can also use it on a client as we'll see in a second. And then here I can actually connect with Cerbos. So Cerbos actually has a package that we can install from npm. We can connect through it over gRPC, which is a little bit better in, in certain ways than just plain HTTP.
That's what we export here. So now I can actually import into this file. So now if I save here, and if we run this code, we should be able to see an actual decision. So this is all for the port. Put route. Let's actually quickly do the same for the get route. All right. So I just added all of the logic for the get route as well.
And here we are using Cerbos as well. Right here in this case, it's not approval. Here's just a viewing and expense. And here I'm logging the decision that Cerbos will give us. All right. So let's actually see. All right. So I'm going to open up my terminal here. Here I have my app running. All right. So now let's see what we get.
I need to hit the API route. I'm going to load this page, which will send a request to our app. App here, which in turn passes all of the information to that Cerbos instance, which then gives us the decision. So I just need to refresh here and it will actually hit that API route. So I'm going to refresh here.
Let's see what we get. And indeed, we get a decision here. It says true. So based on this information, Cerbos decided that It is allowed, right? So now we have a nice API here to deal with authorization. We just have to pass along the necessary information and then Cerbos will actually make the decision based on the policy file, right?
So it looks a little bit complicated, but it all makes sense when you think about it. We can now just make a call to that Cerbos instance whenever we need some kind of authorization decision. That Cerbos instance It's just making the decision according to the policy that we wrote. Here is where we actually have all the business logic.
We can include it as part of our git repository. So then whenever we make a change, which is very common actually, and this is usually where you run into an issue, because now let's say we actually do need to make a change, right? So here the business requirements have changed. Now managers can approve up to a thousand dollars, right?
So it's traditionally, if you had this in multiple places in your architecture, you would would have a problem right now. But I have centralized all of this logic in my policy file here. So if I just save here, the only thing I need to do is just push to GitHub. And since I connected Cerbos Hub to my Git repository, Cerbos Hub will automatically pull out the latest policy file, will run it through their CICD pipeline, if everything went all right, they will push it to that pipeline.
Policy decision point. So then we can just keep making calls to that decision point. It will automatically be updated for us. I just changed this. Now I can just push this to get up again here. I'm just going to push to get up, get push origin main. All right. So I just pushed here. Let's actually see what happens here in the terminal.
Let's see if we get, yeah. So here you can see, we get an update here. You can see it says bundle opens again, right? So what happens here is we push to get up, server boss hub picks up on that and then Push that to our policy decision points. And that's why we get another bundle opens here. So now all the requests will be evaluated according to the latest policy.
All right. So that's all backend. Now, what about the front end? Because in the real world, if we go back to our example, we often want to render. A different UI, depending on the permissions that the user has. And so a manager, for example, should be able to approve this. So here we want to render an approve button.
So with Cerbos Hub, we can actually also use the exact same policy file with the same business logic as what we use on the server. On the front end, because serverless hub will take that policy file and also create a web assembly module from that, which we can then include in the browser. So you can get that web assembly module in here as well.
So here you can see it's called embedded. So here you can see, we get a URL for that. And this is what we can use to run authorization logic on the front end, which is helpful for Changing the UI depending on the permissions that the user has. So let's say we have that page here. I'm using Next.js.
So Next.js has both a backend as well as a frontend. So here I have two states here for whether the user can edit or approve. And so here we need to run some logic to see if the user can actually edit or approve it. So by default it's false, but maybe the user is allowed to edit or approve it. So we need to run the logic.
And as you'll see, it's actually the same as on the backend. So the first step is your authentication check. And so typically your identity provider, like Kind or next auth or octa will give you some kind of hook in react that you can use on the clients here to get the actual user information. So let's say we get something like this.
We get Lisa here. Lisa is a manager. All right. So step two, we've seen before, get the resource info. So you need to get the resource info. So here in this case, we would fetch it from our backend, let's say, and that's what we will store in state here as well. So now we have user information and resource information.
That's what we can then pass to Cerbos. Cerbos will make the decision, but on the front end, it's going to be based on the WebAssembly module. So here we have our authorization check. So here, let's see, we are using useEffect here because it's basically like an external system. So here we can do Cerbos.
checkResource, the exact same API call, pass along all the necessary information and Cerbos will take care of the actual decision. Right. So here we get an actual decision. If the user is allowed to edit, we're going to change the state. Now here you can see you get a red squiggly line here. So we do need to connect to Cerbos here on the front end as well.
So we have Cerbos server, but now we also have Cerbos browser, right? So here you can also get this from NPM. Here it's not gRPC. We're just using fetch here and you need to specify the URL here that you get under embedded here in the dashboard. All right. So then we can import the Cerbos here so we can use that.
Here. Oh, we do need to make sure we get the right one. If I now save and we go back here, you can see Lisa as a manager. So Lisa is indeed allowed to both edit as well as approve this expense. So now we have correctly changed the UI. Now, what if I change Lisa instead of a manager? Maybe she is just a standard user.
So here, instead of manager, we're going to make Lisa a basic user. And now you can see when I go back, the approval button. It's gone because a basic user cannot approve expenses. Only managers can, but a basic user can still edit an expense if they created it. So in this case, the expense was created by Lisa.
So we are indeed correctly rendering an edit button here. You can see it's a bit messy here in a component. So we can even create a custom hook here for Cerbos to make all of these authorization decisions on the client. We can call it hook use Cerbos and we just pass in the necessary information. So then here to use it, we could do something like this.
Use, use Cerbos to get the user. You would have your react hook from your identity provider for the expense. You will probably have some custom hook for data fetching as well. And then the action you want to check for here would be approved, let's say, and then here you get the actual decision. So then you wouldn't have to have a separate use state for approve here.
So now we're using Cerbos on both the backend, as well as on the front end. And the real power is that now, whenever you make a change in your business, there's a business requirement change. Managers can now approve up to 5, 000. Let's say we only have to change it in one place, which is in that policy file.
This is our single source of truth for all of our business logic. I only have to make a change here. Now it's 5, 000. I only have to make a change. only have to push it then to GitHub, git push origin. I'm pushing it to GitHub, and since I connected Cerbos Hub to there, Cerbos Hub is connected to my GitHub repo, so whenever I update this, and the new policy file is being pushed here, Cerbos Hub automatically detects that, runs it through their CICD pipeline, runs the test, and if it succeeds, will automatically distribute it to all of my Cerbos instances, whether it's on the backend, frontend.
We don't have to change the URL of the file. of the WebAssembly module, the updated one will automatically be served from the same URL. So my entire portfolio of services and apps is updated with the latest authorization logic, all managed for me by Cerbos Hub. So really a powerful solution for authorization.
I would say, check it out. You can find a link in the description. Thanks for watching this video.
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.