Role-Based Access Control Using Dependency Injection (Add User Roles) - FastAPI Beyond CRUD Part 13

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi everyone welcome back in this video we're going to be looking at world based access control and how we can implement it within a very simple first API application that we have here so world based access control is simply a mechanism that is implemented in a way that we allow access to specific endpoints or specific functionalities within our application or within our API in this case depending on the role that the user has within our application so this is a very simple example that I have right here in our application we going to have very simple role based Access Control by having only two roles but you can have as many roles within your application and then you can figure out the different kinds of permission you allow for those specific RS within your application so for my example I have only admins and users so users are going to be the normal people who sign up to use the application well the admins will be pretty much the people who have all the admin privileges is to perform almost everything within the application so these are the two roles we have the admin role and we also have the users role now the users role is going to have the following permissions they'll the users will be able to perform CED on whatever book they submit so by doing the crow all we're doing is to pretty much handle everything that's related to a book submission when a user submits a book that book will be made public by admins and then those admins will be able to go ahead and make it public when they make it public then other users will add reviews on that specific book so we're going to look into how to do almost everything but the point is with those rules comes permission so everything listed here is a permission what you are allowed to do within the application is your permission and now we see that admins can pretty much add users change roles perform CR on users basically do their own book submissions they can pretty much do everything within a app so we're going to be looking at a very simple example and how we can implement this in the video so to begin we going to build an API endpoint that's going to allow us to get a single user within our app to do that we're going to head over to our dependencies where we pretty much did everything that's related to validating our tokens now the be about fast API dependency injection mechanism is the fact that we can create dependencies which also take in other dependen so for example if you want to create the dependency for getting a currently logged in user within application all you have to do is to create a function and we going to call this get current user and this is going to take in the token that is provided via the authorization headers as we looked at in the previous videos so to do that we can just simply say that our token details are going to be got or this will be actually a dictionary but they'll be got from using any of the objects created from these dependency classes that we created so we can do such by using the depend function so I'm going to go at the top right here and import it I'll say from first FTI we shall also import the depends function and once we've imported the depend function we simply going to go ahead and make use of it by saying depends and then we shall inject in the dependency which is going to be an object of our access token so the access token is where we going to get the information about the currently logged in user and therefore we shall create an object of type access token bear so once that is done then you're going to Simply go ahead and code now that our token details actually decode the token and return the details we can simply get the user's email and then use one of the utility functions we created for the previous videos to get the current logged in user and return the object so the way we going to do that is by simply accessing the user email so we may say that our user email is going to be equal to so in this case we're going to get token data so our token detail is going to contain the user dictionary and inside this user dictionary we encoded the email now if you do not look at this please go ahead and look at the previous videos where we looked at JWT authentication so when you get our email then we need to also make use of our session dependency now we can inject into a dependency as many dependencies as we want but the problem is you need to do this for only dependencies that you're going to use within routes if you do inject a dependency into a class or let's say a method or a function that you're using outside routes this is not going to work but since this is dependency that we're going to use within our routes then it is going to make sense for us to do the extra dependency injection so I'm going to begin by importing the get session function so I'll do that by basically importing it from source. db. Main and then we can go ahead and import the get session function which you call our session dependency so I'll just come right here and then what I'll do is to Simply go ahead and I think I'll have to format this to make it look a little bit organized and then we shall have our sessions so this session is going to be an object of type as sync session I think I have to import it since I haven't imported it right here so I'll do that by saying from SQL model do EXT do sync iio do session we shall have to go ahead and import the asnc session class once we imported the async session class we're going to Simply go ahead and inject it right here and once we've injected it we now going to specify that it's going to have a default value of our dependency which shall come from the get session function so another thing that's important that I wanted to that I felt like I did not explain right in the previous videos is the Dand call method so every time you want to create a python object from a class but you want that python object to act as something you could call as a function then that is the method that you're always going to work with so the D call method is one that allows us to create these dependencies and then call them as functions and that's why you see that we create objects for these classes and then provide them as dependencies within our route handlers or within any other dependencies so if you do not understand that you'll get to understand it as we go on in the series so what I'm going to do is to go ahead and query for the user using this session object that we've imported and injected here so I'll begin by importing our user service class I'll say from do service since we in the O directory then we're going to go ahead and import the user service class once we have access to the user service class we're now going to create the user service instance so I'll just come right here and say that our user service it's going to be an instance of a user service class and once we've been able to do that then we going to go down here and we're going to get the user object so our user is going to be equal to now I want you to remember that this is an awaitable or this is a COR routine and therefore we have to use await so we're going to redefine this dependency by saying a sync and then inside here we shall just simply go ahead and say await then we shall use our user service object on it we shall call the method get user by email and this case we shall provide our user email once we've got our user email the next thing is going to be to Simply return the user object so in this case she just come and say return user and that will pretty much do it for us another thing that I've forgotten is that this takes in the session object so I have to go ahead and provide it by saying comma session so this is going to be just enough for us to get our currently logged in user so any checks for the token will be done by this dependency so let's go ahead and Define the route or the end point or the path on which we shall get the currently logged in user I'm going to go ahead and access the routes which are our o routes and inside here I'm going to begin by defining so let me get to do this before the log out in print so I'll simply say at o router dot in this case want to get the current logged in user so I'll say slash getet and then again I'll provide the endpoint name as slash or the path name as slash me so you'll go to API SL verion run/ o and then SL me and in this case we go ahead and say async Def and we shall just simply call this a get current user so in this case we shall just say get current user and then this will take in our dependency so I can simply return the user but for us to Define this user then we have to go ahead and first import our dependency by going right here and seeing from dependencies shall also import get current user once that is done then we are simply going to go down here and then say the our user is going to be an object of the user class but in case we don't do that we shall just call our depends and this will take in our get current user function once that is done then simply returning the user will go ahead and return our user so by doing that we shall we shall be in position to just get the currently user object so look at how simple it is to use dependen injection to R less code all we have to do is to define the dependency that pretty much does everything we need and once we do that we go ahead and inject it within our routes making them have less Cod to proceed we're going to test this end point in rest Fox so I'm going to head over to rest Fox and what I'm going to do is to create another request and this is going to be the one for get current so I'll call this get current user so I'll provide the API endpoint or the path so our path in this case is going to be this SL me we try to make a request to this we see that we are not authenticated and simply because this takes in the access token be dependency so we have to go to our headers right here we shall create a new header and this is going to be our authorization header so we have to provide Bearer and then the token so in this case we have to get a new token I'll say so this says invalid username or password I think I changed oh did something if I say password one to three this will pretty much allow us to log in so I think I have a problem in valid username or password if I try to go back to create user account create a new account I think I did something with the database and the user was deleted if I try to get the new tokens using this current user if I go back to where we create the token pair uh we can simply submit this credentials with the password which was test one 2 3 and then right here we can be able to successfully log in now when I get the token all I'm going to do is to Simply go ahead and use this token within our route for getting the current logged in user I provide the token and when I send we now see that this is going to return the currently logged in user so you see how simple It Is by just doing that we've been a to Simply go ahead and get our current user without having to write a lot of code all we had to do is to create a dependency that CH another dependency and therefore we can return our currently logged in user now that we've been able to get our currently logged in user let us go ahead and use this to create roles within our application so what you're going to do is to add another field to our user model that's going to indicate the RO Ro that a user is going to have within this application and the default role that we shall have is going to be our user role for every user that shall be created within our application and then after having the user role ad means will be able to update the role over a specific user in our application just like we're going to see so right now we're going to Simply go back to our user model we're going search for our user model that's going to be located within our SL directory and inside there is where we're going to add onto what we have as our database model for users a field to Mark the role that a user has on our application so to begin we're going to start by saying that each of our users is going to have a role and this rle is going to be a string now once once defined we also need to specify what kind of data is going to be stored in this rle and the default value we're going to have within our data now the thing is this is a field that we adding onto a table that already has data and therefore you have to provide a server default that's going to allow post to understand which data we're going to fill for those fields that are going to be blank or for the newly created field that is going to be blank so to begin we're going to mark this as a field and we're going to use the field function and once we've created that and we going to make use of the sa column property or argument provided we're going to name this a column and this column is going to be of the post type so in in our case we're going to have PG do vure and once that is done then we going to say this is going to be not Nar so shall say n is going to be false and we're going to provide the server default now the the thing about this server default argument is that it's going to allow us to provide a default value to fill in because what we expect is this Ro is going to be created but shall not have any any value now by specifying the server default to user all we're going to do is to create the RO field and then populate it with the user value so that you can have the default value for the existing users in our database being the user once that is done I'm going save so forat my code a little bit and then now let's go ahead and use almic to make this modification to our database now remember that you're using lifespan events to make modifications to our database every time we create a table the lifespan event will run and then simply create that table in our database but from now on we're going to be making use of almic so that we can stop using server LIF span events so what I'm going to do is to go back to our source folder init.py and then I'll remove this argument of our life span function so I'm going to go ahead and remove it so that none of the code that resides within our init DB will run and once that is done then we're going to go within our terminal I'm going to open up a new terminal and in this terminal going to Simply go back two steps within our root project folder and in here is where I'm going to create a new revision to add that change to our database so to do that we're going to use alic revision and in our case we are going to tell it to autogenerate the revision so it's going to autogenerate whatever change we've made from our models and once we've been able to do that shall provide the message which is going to be add roles to users and this will go ahead and create the revision to apply this revision all we're going to do is to say almic upgrade and then head now if you're wondering how we are doing all this I created a video in the series where we set up a liic to work with our as synb I think it will be a very informative video so I'll press enter and then this will go ahead and make that change onto our database now to confirm what I'm going to do is to head over to my terminal and inside my terminal I'm going to open up the SQL shell or the psql utility so I'll say psql and inside here I'll connect to our database which is bookl DB and once I'm there then I'm going to describe the users table and this is going to show us that the RO field has been added meaning that we have made a successful revision so once this is done we're going to exit this and then we are going to go back to our quote right here so now that we have the RO being added then we're going to make a few changes to how we create a user and how we can be able to provide them a role so so we going to head over back to our Ro routes or our paths for creating a new user and the next thing we're going to do is to Simply head over back to our service where I create a new user now inside here we defined a bunch of attributes so some of them are going to be submitted from the client side While others are going to be modified such as the password hash so I'm I'm simply going to go ahead and also add the user part so I'll say new roll new user. roll and in this case we shall simply provide them with a default user role once that is done then let us go ahead and also add this within the token that we provide to each user so to do that we're going to head over back to our routes and where we encode our token to provide the user details we're also going to add the user role so in this case we're going to come and say that our role is going to be the users Ro and in our case we shall say user do roll once this is done now let's go ahead and work with that to be able to create the dependency that will allow us to implement our Ro based Access Control now Ro based access control is something that can be as complex but in this case we are going to just need a simple way of blocking access to those who do not have a specific role so to do that we're going to head over back to our dependencies and what you're going to do is to create a class that's going to be a dependency now remember that you created this D call method and every dependency that's going to have to be a class is going to have that because then all you need to do is to create those objects from those classes and call them as functions so what you're going to do is to come at the bottom right here we shall create that dependency and we are going to call this our role Checker dependency so this is not going to inherit from anything but we shall Define it as one that's going to have some attributes so each object that we're going to have of this class is going to B one to take in a list of roles so we're going to say that uh we're going to call this our required roles or roles or allowed roles or something like that so we may say allowed roles and this is going to be a list of roles so may give it that list type so once we have done this then we can even use typing so to be moreos and to be more specific we can use the typing module so we can say from typing we are going to import the list type and once we've done that and we can explain that this is going to be a list but for us to be specific it's going to be a list of string objects so once we've been able to specify this then it's also going to return none and once we do that then we're simply going to attach that as an attribute to this object to this classes objects so what you're going to do is to say self. allowed RS it's going to be equal to the allowed roles that we provided to this class going to format this a little bit once we are done with this then we're going to define the main class that's going to be called each time we create objects of this specific class or objects that you're going to inject into those specific path handlers I'll just come right here and say that this is going to be a function called the call method so we're going to Simply say a sync def so this going to be our D call method and it's going to be the one that we're going to use to Simply check if we are providing a role when accessing a specific endpoint so this is going to take in whatever it takes in as arguments and the keyword arguments but for us to be specific we're going to provide a dependency and this dependency is going to be our current user dependency now I think you guys are now seeing the where we can enjoy coding with first APA all you have to do is to provide the current user so in this case can say that our current user is going to be of course a user object but this user object is going to be a dependency so we need to get the its dependency by using the depends function and once you get the depends function we shall just call a dependency which is our current user so our current user is going to be what we get from get current user and once we've got that then you can simply say if our current user roll which is going to be the user's role now to make this simple we can import the user model or import the user class by saying from do models import user but I'll save time what I'm actually going to do is to import it just for Simplicity I'll save from Source models we are going to go ahead and import our user model this is actually going to be found within the same folder so I'll say from models we shall import our user and then by using type hints we shall just come in here and say that our user is going to be an object of type user and that is going to be returned from this dependency right here so when we get our current user we shall get the role of that user and check if it exists inside our allowed roles so we can say in do allowed roles which I actually called allow allowed R now let me just rename this so just going to go ahead and rename it by pressing contr d highlighting and getting all occurrences of allowed roles and I'll change this to allowed roles so if this is true then we shall simply allow access to the user and if it is false then what we're going to do is to Simply deny access by raising an exception so we shall just come right here and say return true and once this is done then you're going to come outside this if statement and raise an exception by saying graise h GTP exception and in our case we shall deny the E access we shall say status code is going to be equal to status do HTTP 43 forbidden and for our case we're going to provide the detail as that for denying this user or telling them that they do not have sufficient access to access this specific endpoint so in this case we can say operation not permitted or let's just say operation not permitted so we can just come right here and say that we can actually just say you are you are not permitted to have you are not permitted to access this or to perform this action so in this case you're not permitted or you you do not have permissions to perform this action so you are not allowed to perform this action and that will be just what we can simply tell the user when they access that specific endpoint where they do not have the role that is for that specific action so for us to make use of this we we can go back and simply see how we can use this or on our route for getting the current user so we can go back to our routes and all we have to do is to First import that dependency so we called it the role Checker dependency so you need to First create an object of that class by coming right here so we can call this the admin role Checker or you can simply even call it the role Checker what we can do is to just come right here and say that our role Checker is going to be an object of type Ro Checker and every Ro Checker object is going to have the allowed roles so in this case we can provide a list of roles and these are going to be strings so we can say that for the stylist say that this actually needs to be done by an admin the only thing we have to do is to go within our routes and inject that as a dependency so we shall come right here and since that is simply not going to return any object that shall use within our routes can just say underscore or whatever you want so I'll close what I did there so I can just come right here and say comma within our get current User it's actually get current user so I can say underscore and then this is going to be our dependency returning a Boolean and this Boolean is going to be uh getting our dependency so in in this case our dependency is going to be our role Checker object we need to format this a little bit so for now if you try to access this endpoint as a normal user when you go back to our rest for right here we shall see that we are not allowed to perform this action simply because we have now said that this route is to only be accessed by an admin but if you go back and say that even users can be able to get their currently logged in user so you can say that this can be performed by the Admin as well as the user then if you try to perform it then you can be able to get the currently logged in user now let us go ahead and add this protection to all the other endpoints we shall look into some of the endpoints that will be for the admin but let's go ahead and just protect those for a user so each almost each action performed by a user is going to be performed by an admin for example if you go back to the crowd the routes for the crow that's involved with books uh we can just simply go ahead and use the same Ro Checker object so what I'll do is to go within the route for our box and then inside each of them I'm going to go ahead and make use of this Ro Checker object so you have to go ahead and import this so I'll say that we also need the RO Checker class and once we've imported it now let's go ahead and inject it it so I'll just come right here and say underscore then this is going to be a list of this is actually going to be a Boolean and what we need is going to be the pends and shall provide our roll Checker so I'll say roll Checker so what I'll do is to just simply copy what we have here and then I'll go ahead and paste it or to simplify our life and to make it a little bit neater we can actually make use of the dependencies argument within our router. post or router. gate or within our HTTP methods so I can just simply come right here and say comma dependencies is going to be equal to a list of dependencies and in our case we're going to be making this of our R Checker so once we have done that I'm simply going to go ahead and provide it within every API Endo so I'll just copy the comma as well then I'll go ahead and just simply provide it within our routes So within our parts so this is going to be the one for creating a book then we shall also have this for uh the one that's going to be for getting a specific book by its ID and then we shall have this one for updating a book then you shall have the one for deleting a book and that is simply going to go ahead and protect this endpoint so that admins and users can access them so if I go ahead and save um our server is going to be running so if we go back right here our server is running we don't have issues uh seem like the server is not running or if it's running then yeah it's running something is wrong so the server is running right here so we see that the roll Checker object has no attributes roll dependency so the issue here is I try to inject the object directly without having to provide the depend function so how we deal with that is by simply going going ahead and wrapping our dependency object inside the depends function so what I'm going to do is to Simply go here and say depends and then I'll provide the object which is going to be our Ro cheaker so by providing this as a dependency it is already a dependency injected just like you see right here so this is just one simpler alternative to going ahead and depending it directly into inside the hand function so once that is done our server is going to be running and now we can pretty much perform any other action inside our crowd for books and in this case we shall have the right role and the right access to perform that action so in this video we've had a brief introduction to based Access Control when using fast API in the next videos we're going to be using this Ro based Access Control to create end points that will be necessary for ADD actions and also end points that will be necessary for user actions if you enjoyed this video please leave a like it's one way you can support the channel also check out other ways you can support the channel by donating to me on my buy me a coffee link as well as the patreon right in the description right there thank you guys for watching and I'll see you in the next video bye
Info
Channel: Ssali Jonathan
Views: 429
Rating: undefined out of 5
Keywords: fastapi, fastapi tutorial, fastapi rbac, fastapi roles, role based access control
Id: _k2M-LpxId8
Channel Id: undefined
Length: 32min 26sec (1946 seconds)
Published: Mon Jun 24 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.