FastAPI Authentication Example With OAuth2, JSON Web Tokens and Tortoise ORM

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone in today's video i'm going to show you how to implement authentication in your fast api apps and the type of authentication i'll be using is oauth 2 with json web token so this is what people have been requesting so i figured i'd cover this particular method of authentication there are other methods of authentication but this is the one that people have been asking for and it's a pretty standard one when it comes to a lot of apis as well so i have my virtual environment here and the first thing i'll do is install fast api a fast api and i'll also install hypercorn as a server to run my app so everything should be installed and i'm going to create two examples to demonstrate this authentication so the first example will be really simple and it's going to demonstrate the flow and then the second example is going to actually authenticate someone and you know you can get their information everything i'll use a database and tortoise orm and just make it more realistic but i think i should show you the flow first without all the extra stuff around it and then show you how everything works in a realistic example so what i'll do first is i'll create a file called example so touch example.pi and in this example first thing i need to do is i import from fast api fast api and then i'll instantiate fast api just like that and just to make sure everything is working i'll start a pipercorn example app and then i'll just set the reload flag i'll go over to my browser and then it's going to be uh one two seven zero zero one port eight thousand and we see i get not found because i have nothing on the index but if i go to the docs uh we see no operations defined but i do have the fast api docs here so that part works so far so what i want to do for this flow is basically three things so first i'm going to create the oauth 2 schema and this schema is going to allow me to use this easily in fast api so really it makes sense to show you if i when i use this scheme but once i create the all 2 scheme and specify a url for generating a token we'll see on the front end here in the documentation what changes and then i'll also create a simple endpoint that will just return some data and this data is just going to mirror back the token so the first thing i want to do is i want to import from fast api security so from fast api.security i'm going to import the oauth 2 password bearer so this comes included with fast api which makes authentication really easy and we see it has all 2 in the name so you can probably guess there are other types of authentication you can do in fast api but all two is what i'll be starting with here and what i want to do is i want to instantiate that so i'll just call this oauth2 scheme and i'll instantiate this or2 password bearer and what i need to pass to this is a token url a token url and this is going to be some endpoint that generates a token for us so we have to create the endpoint that generates the token but fast api will handle actually calling this endpoint which i'll show you in a moment i'm going to call this token and then from there what i can do is i can create an endpoint app post and slash token so this is going to be called at some point to generate our token and our token is going to represent uh who is logged in right so i'll make this a function and i'll call this token and this route or this endpoint token is going to take in a couple of things at a minimum it's going to take in a username and a password so the idea is you pass a username and a password to this token endpoint and it's going to generate a token somehow and like i said we're responsible for generating that token so i could create a username and password thing myself in terms of the parameters but instead what i'll do is i'll use the oauth 2 password request form which also comes with best api and i will just generate a form and basically parameters that allow me to pass in that username and password and it's going to make it a lot easier when it comes to using the front end which i'll just show you in a moment so all auth 2 password requests form is what i want and then in here in the token what i'll do is the incoming request data i'll call that form data and then i'll take the all to password request form i'm going to use the depends pins class here and i'll instantiate this with nothing so basically what this means is that this form data depends on this oauth 2 password request form so whatever happens here is going to determine whether or not i can continue in this token function so if this fails for whatever reason it would never run this token function because it depends on what happens in here if it does work it's going to return something and that return value is going to be the form data so that's all that's happening here so we don't need to know exactly what's going on here but just generally it's just a form that we're allowing the users to fill out with a username and password and then it's going to take that information and pass it to the form data and we'll have it available in the function so what i'm going to do here for the token i'm going to return a token and you have to return you know something that can be used as json so in this case a dictionary and it needs to be called access token so when you're using all 2 access token is the name that you need and you need to pass something back so what i'll do is i'll take the form data dot username so this will have form data.username informdata.password but i just want the username and i'm going to append token onto the end of it and we'll see why i'm doing this momentarily and then the next thing i want to do is i want to create a regular endpoint so we can pretend like this endpoint is for app to actually do something and i'll define this as the index and what the index is going to take in is it's going to take in a token so this token is going to be passed by the the client which will either be the fast api docs that i have in my browser or it can be done through some kind of client i can call apis and i'll show you that in the realistic example but for this simple example i'll just use the fast api documentation that's automatically generated so this is going to be a string and this is going to depend on the auth 2 scheme so off to scheme so basically what happens here is all 2 scheme is going to be called it's going to determine whether or not there is a token available if there is a token it's going to return it and then i can use that token for whatever i want inside of the index here so what i'll do is i'll return that token and i'll say the token as a value and then i'll just pass the token so this is basically the simplest off to flow example that i could think of and now to make it make sense what i'll do is i'll start up the server so example app and then i'll have this reload and i do need a python multi-part because oauth 2 password request form is a type of form so i can just install this it's nice that it tells me so python multipart then once that's done i'll start up the server and i'll go back to the documentation refresh and now we see i have uh this token endpoint right so i have the token endpoint and what i want is i want the uh this to be good so i was just writing flask earlier so i put route it should be get so that's why i didn't appear on the other screen so now i have two endpoints i have one token and one for the index and if you look over here you see a little lock which means that this requires authentication and the reason why this has a lock is because it depends on the oauth 2 scheme so if this didn't depend on the oauth 2 scheme in some way it wouldn't have that lock but by having that it makes it really easy to go through the or2 flow so if i go to the index here and then click on the lock i get a form here and i have a username and password a client id and a client secret uh you can use these if that's how you want your authentication work or my example today i'm going to use the username and password so for me i'll say my username is anthony and the password is password now just click authorize and then you'll see this screen where it says i'm authorized i can either log out or close i'm going to close this and what this means is now that i'm logged in i can use this index so i'll hit try it out and then execute and if we look down here we see the token is anthony token so if you look back at the code the token is just the username which i typed in anthony plus the word token so it's returning that and note that i never actually called the token endpoint directly instead i called the index after i logged in and you see it's a lock now the the lock is locked now and that represents that i'm logged in so i could use the token endpoint directly but for the docs here you don't need to you just need to log in with the lock and it works so if i log out here and then i try to execute this i'll get this not authenticated error and that's because it's expecting there to be some kind of access token somewhere so there is a little bit of magic going on behind the scenes but i'll show you exactly what's going on with this when i do the more realistic example but this is basically the flow so at a minimum you need an endpoint that generates a token somehow in the later example i'll use json web token to generate this and then you can have your regular endpoints that will require authentication to use okay so that is the simple example so now i want to create a more realistic example so we'll do main.pi for this and i'll start by importing from fast api again so import best api i already know i'm going to need depends and then from fast api dot security i already know i'm going to need that auth to password bearer in the off to request form so i'll be using those in this app because i use them in the simple one and this one is going to be more complicated so let me stop the existing server because i won't be using that one anymore and i'll go ahead and instantiate fast api so because this is a more realistic example i want to have a database so i'm going to use tortoiseorm to interact with the database that i created i'm going to go ahead and import things from tortoise orm so from tor tis dot models i want to import the model class and then from tortoise dot or shouldn't be dot fields but just tortoise import fields just like that so let me just move this here so it's in a better order and once i have those two things i can go ahead and create a model so this model will be a user model so class user it's going to inherit from the model from tortoise.models and this is going to have just a few fields so the first field is going to be an integer field and this is going to be the primary key for this particular user and then i'll have a username field which will be a char field and let's say it's 50 characters long and then unique is true and then i'll also have a password hash and that will be a char filled as well our field and then let's just say 128 i don't know exactly how long the hash is going to be but um i have those three fields and to help me out a little bit later what i'll do is i'll create a couple of helper functions or methods inside of here so the first will be a class method so we'll have class method and this will be async def get user and it's going to take in a username right so what i want to do is i want to i want to return the get on the class right and then i also want to create a verify password method to verify whether or not the password is correct because i'll be hashing it so i'll just define this as a verify password itself i'm gonna take in a password and then for now i'll just return true once i have the password hashing working i'll return the actual result of the verification so now that i have my user model what i'll do is i'll go ahead and register this all on tortoise so what i need to do is i need to import the register tortoise function from the tortoise rm library so from tortoise dot contrib dot fast api i'm going to import a register tortoise and then at the bottom of the file what i can do is i can take this register tortoise and i can use it to get everything running with my database so i just need to pass in the app i need a db url so i'm going to use sqlite so sqlites i'll call the database db.sqli3 i need to specify the modules where the models are so it's going to be in this file so models are going to be in the main file so that's just what's going on there and then generate schema generate schema equals true so generate schema true just means that if the database doesn't exist or the models or schemas don't exist within that database then it will generate them for me so it will create the table for me then i can do add exception handlers as well to true just so um if there is an error everything will work and this should go outside of the user class so let me just de-indent everything okay so now when i run this i'm expecting to see a database so let me go ahead and start up hypercorn main app and then i don't need to reload uh no module name tortoise of course so i just need to install it so pip install tortoise orem and it failed because there should be a dash in between so tour this orm okay so it looks like it worked this time so now i can go ahead and start up the server and let's see generate schema i believe that's plural so i'll just add an s on there and this should be plural as well add exception handlers okay so looks like it worked and we see here i have the sqlite files on the left hand side and i'll just stop the server and i'll just open up the sqlite database in type.schema and we see i have the user schema here the user table id username and password hash the next thing i want to do is i want to create pedantic models from this user model so the user model will represent what's in the database and then the pedantic models will represent what's going on in the app when i'm using the users so i'll go ahead and import from tortoise i'm going to import the pydantic model creator and that will allow me to create models based off of my tortoise orm model so i'll create a couple and i'll just put them underneath the user model here so the first one i'll call user pedantic and the second one i'll call user in pedantic so these will be pretty similar the idea is one will represent everything that a user has and the second will just represent uh things that the user can actually pass in as input and we'll see how this works you don't necessarily need it like this but this is the example that tortoise rom uses so i'll pass in the user model and i'll give it a name so user and then likewise i'll do the same thing for the other one and this will also be the user model but i can't have the same name so i'll call this user in and then here you would normally exclude read only equals true you don't necessarily need it in my example but just as a way of showing you the difference between the two so the idea is if there is some read only data you wouldn't expect the user to pass it in because it's read only so that's why this has exclude read only but in this particular example it's not really necessary so now what i want to do is i want to create an endpoint that allows me to create new users and this is really just an example it doesn't really make sense to have an unauthenticated endpoint to create users as easily but we do need a way to get users in the database so i'll create this so what i'll do is i'll create endpoint called slash users and it's going to be post and it's going to return so response underscore model it's going to return a user pedantic right because that's what my user is and i'm going to create the user function so i'll create user function and then it's going to take in the user in right so this user in here this user is going to be input and then this user pydank is going to be output so using this uh what i can do is i can create a user so i'll just say user object equals user and then username is going to be user.username and then the password hash is going to be some kind of password passed in and it's going to be hash for me right so before i can hash the password of course i need to have some kind of library available to hash it so what i'll do is i'll use passlip so pip install passlip and this will allow me to hash passwords and if i just import from past lips so we'll put it up here we'll say from passlip.hash import bcrypt so i'll use bcrypt to hash this and really the simplest way with bcrypt and then i'll go back down to the user object where i create it and to generate the hash what i'll do is i'll use that b crypt that i just imported i'll call hash on it and then user dot password hash so what's going to happen is the user is going to pass in a username and a password hash the hash or password will actually be hashed when the user passes it but once it gets here it will be hashed uh by bcrypt right so once i do that i can await uh userobject.save remember tortoise is an async library so i have to await anything that may take time so to save i have to wait so then i can also await converting uh the user object which is a tortoise or m model to the user pedantic so i can take the user pythanic and i can say from tortoise orm and i can pass in that user object so what happens here is it's going to take a user object so this is a tortoise or m object and it's going to convert it to the user pedantic object which is the response type and the user will get that in return so basically i'm just creating the user and then passing back the information about that user so i'll see the id the username and the actual hash password so let's go ahead and start this up and let's see if i can create a user so i'll go back over here and refresh because i am now in a completely different app and we see i have posts users create user and it takes in a username and password so i'll go ahead and try this out and for the username it will just be anthony and for the password it will be password all right let's not use password because i want it to be something completely different so i'll just say my secret as a password so i'll hit execute here and i get an error so let's just see what this error is decrypt no back ends available so i need to install a bcrypt before using it with passlib it's not a dependency so let me go ahead and do that and now let me start up the server again and try this so the data is going to be the same anthony and my secret i'll try executing again and i just did and we see that i have id one anthony and then i have this hashed value so we're not saving my secret in the database because it's being hashed and let me go ahead and create a second user second user will be pretty printed and we'll say the password for this is my other secret so i'll execute this and then i have id2 pretty printed and then another hashed value right so let's go over to the database and take a look at that i'm stopping hypercorn and i'll just open up the database and i'll select star from my user table and we see in the database i have ids one and two anthony and pretty printed and then i have the hashed password so know where the plain text password is getting saved just the hashed one and now that i do have a hashed value in the database i can work on this verify password method here and that's pretty simple to use so it's going to be bcrypt.verify and i just need to pass in for the first argument the password that the user is trying and then for the second argument it needs to be the password hash so it's going to be self password hash because i'm i'm going to be calling this on a user object so we'll see that momentarily when i set up the authentication but i'm just creating this now because you know i have the hashing so now i can verify the hashing so now that we have that let's start adding in the authentication stuff the the flow the auth to flow so first thing i need to do is i need to create that oauth 2 scheme and what i'll do is i'll just put it here so off to scheme it's going to take that bearer again and then the token url will just be token so that's exactly like the simple example that we had before and now we need to create the token endpoint so what i'll do is i'll put it above the users so remember that the token endpoint needs to be post so slash token and i'll call this generate token generate token and if you recall from the previous example i have form data so form data and then i'll take in the all to password request form and it's going to depend on that next thing i want to do is i want to verify that the form data is correct so remember the form data is going to be a username and password so to authenticate basically what i have to do is i have to query the database both see if that user exists and i need to see if the password is correct so the easiest way to do this i think is to create another function and this function will return either a user object or false in the case that the authentication didn't work i'll just put this right above and i'll call this uh authenticate user identicate user it's going to take in a username which is a string and a password as well which is also a string so the first thing i need to do is i need to get the user object so i can say uh user equals await and then i can say user get username and just looking at this um i really don't need to get user like that but i'm just getting the user so username user.get username is username so now if this user exists it will return the user and if it doesn't exist then it will be null so if i say if not user that means that this will only execute if nothing was found so i'll just return false so that means that there is no user in the database that has that username and then i can also add another if statement saying that if user.verifypassword and then return false right so verify password will return true if the password is correct it will return false if it isn't correct so this will only execute if it's false and i'll return false in that case and then if both of those pass i'll just return the user so now i can go back into the generate token and i can say user is going to equal await authenticate user and i'm just going to pass in the username and the password so form data.username and the form data.password so now what i want to do is i want to return something if the authentication fails so for now i can just say if not user say return error invalid credentials now change this to be like a a better error in a moment but i'll just use that for now and if it gets past this point that means that the user both exists and the user has been authenticated because the password matches so what i'll do here is i'll say user object is going to equal a weight so i need to call a weight because i'm going to user pydanic.from tortoiseorm again taking that user right so i'm just converting this tortoiseorm user to the user pedantic object and now what i want to do is i want to create a token right so now i know at this point i'm authenticated and i have the user object available to use so what i'll do is i'll install pi jwt and then i need to import that so here at the top i'll import it so from jwt i think i just want to import so import jwt and then i'll go back down here in my token i'll go ahead and create the token so at a minimum i need let's see so jwt encode this is the method i'm calling here the function on jwc so in code and then i'm going to convert my user object to a dictionary because this is going to be the payload for the json web token and then i need a secret so i'll just say jwt secret anything i'll make this a constant at the top uh my jwt secret you obviously want something that's more of a secret than that but just for example purposes i'll use that and this will return a token and next what i want to do is i want to return that so remember that i need to return access token token and just to make this more standard you can also return the token type so token type is going to be bearer so i should now be able to get a token in return when i pass in the username and password so let's see if i can try that so i'll start up the server and then i'll go here refresh and then i'll go to generate token let's try it out so anthony is the username and my secret is the password i'll execute this and as a result i get this access token so what i'll do is i'll go to chat i o and i'm going to take this access token let me see if i can copy the whole thing here and we'll just put it in the debugger here so we can see what's going on so i'll paste that in there and if you look here what happens is you see my id so one we see the username is anthony and we see the password hash is this so normally you wouldn't have the password hash in here but you know just to keep it simple for my purposes i'm including everything because i'm passing let me go back to my code i'm passing uh the user object as a dictionary and inside the user object i have the password you don't have to do it that way but there's not exactly any harm in this simple example although really you wouldn't want your password hashes to be included in the payload so whatever you want in the payload you can include here but for example purposes i'm including this but it's really up to you to decide what needs to go in the payload so i have that here and i know i can get the token and let me just show you what happens when i don't pass in the right information so i'll just take away the t in secret execute and i get invalid credentials because that's what i'm returning here if it doesn't work so the last thing i need to do is i need to do the reverse of this i need to take a token in and determine what's in that token and basically allow that to control whether or not the user can be authenticated as that user so what i'll do first is i'll create an endpoint i'll call this app get slash user slash me so basically what's going to happen is when the user goes here and they're logged in they're authenticated they're passing a token it's just going to return their user object so here the response model is going to be the user pythanic model and then i'll create the function and we'll call this git user and yeah just thinking about it now i'm not going to use um this particular git user so i don't really need it you just delete that and get user i'll pass in user and then user pydank and actually yeah i'll use user pythanic because i'll show you what's happening with this in a moment so instead of user in i'll use user pedantic and the reason why i'm doing that is because it's going to depend on a function called get current user so i'm going to define this git current user in a moment but just know that because it depends on it this is going to run first and it should return a user object in the end so all i want to do here is just return that back return user so we'll see that uh when i run the example so the last thing i need to do for this is create the get current user function so i'll create that here so async def get current user i get it user current now i won't get current user i wrote that backwards so get current user will be the name that i want and it's going to take in a token and this is going to depend on that oauth 2 scheme so what happens is as long as there's the oauth 2 scheme somewhere in the dependency chain i'll get that lock on my route here so i have a git current user so it depends on get current user but get current user depends on oil 2 scheme and because it depends on oil 2 scheme it's going to return that lock so now inside of here in my uh get current user function what i want to do is i want to decode the token so remember this is passed automatically in a sense so what i'll say is payload is going to be equal to jwt.decode and i pass in the token which is a parameter and then i need to pass in the secret so jwt secret i need to pass in the algorithms that needs to try so al gore rhythms and i believe this is hs256 that's the default one so that's what i'll use and then i can get the user for the particular id so i'll say user equals await um and then user.get and this time i want to get using the id and then the id should be in the payload because the payload is just everything that is here in purple so id equals payload dot get and there's an id in there and then if this doesn't work i want to return an error and you know this is a good time to import http exception and also i believe status so what i'll do is i'll raise that http exception if this fails for whatever reason so i'll say raise http exception status code is going to be status.http401 unauthorized and that's all caps and the detail will be invalid username or password let me just put this on two lines yeah so that will just make it a little bit better as an error message and what i'll do is i'll raise this uh here as well instead of just returning error okay so raise the exception if this doesn't work but if it does work then i'll have this user object available and then what i want to do is i want to convert this to the user pedantic because remember i'm expecting a user pedantic object here so userpytanic.from tortoiseorm and user and the reason why i'm using this one instead of user in is because the user isn't passing this directly the token is being passed so i believe this is it for the example so let me go ahead and restart the server and hopefully everything works but of course that almost never happens the first time so let's try it out let's go to the docs again and we see the lock here on users.me so i'll hit the lock and i'll put in my username and password so anthony and my secret is the password i'll hit authorize then we see it says authorize i'll try it out execute and in the results we see that it did work the first time i see id1 anthony and password hash so this is the object and if i were to log out of this try it again i get not authenticated so that's the error message that's being raised and if i log in with my other user 3d printed and then my other secret is the password for this one i'll hit authorize close execute and then we see a pretty printed and then the password hash for that along with id number two so of course in a real app what you would do is in here you would just do whatever is necessary for your app and then you'll have the user object available for the currently logged in user and then you can do anything you want with it but note that you don't have to get the user object yourself in this route you only need to define it once then you can create as many routes as you want as many endpoints as you want and they can all depend on your current user and then you'll get the user object in return and then you can use that in each individual endpoint i told you before i'll show you a different way of doing this because the documentation is a bit magic so what i'll do is i'll start a postman okay so we're in postman now so what i need to do is i need to add in my url so port 8000 and the first thing i need to do is i need to log in so i need to request a token right so i'm doing my url token and then in the body what i'll do is remove this from a different api uh form data here the key is going to be username and the value is going to be anthony and then the other key will be password and the value will be my secret so if i send these two values the username and password and i get or excuse me a post request if i send that i get an access token in return and just to show you that this is actually authenticating if i take off the t in secret and hit send i get invalid username and password so that's exactly what i want to see so i just sent the request and this is the token that i get so i'll just copy the token and then i'll create a request to the users.me or user slash me so 127001 8000 users me so first i'll send this and i get not authenticated so to authenticate myself i'll go to authorization here and then they have oauth 2.0 that's what i'm using uh we have bear token and i can just pass in my token somewhere so access token i just put it in here postman makes it pretty easy to handle these things so now this token will be sent along with the requests so if i send now i get the information for anthony here at the bottom just like we saw in the documentation but now i have to actually request the token directly and then take that token and put in in all the requests that require it so now if i go back and log in as pretty printed we'll see i'll get a different token so my other secret i'll hit send i'll take that other token copy it in here and then send and then we get the information for pretty princess and if i just mess up this token a little bit so it's invalid and hit send then i get invalid username or password again or this can just be like invalid authentication whatever but we get a 401 which means that the authentication isn't working correctly so that's all that i want to show you for this example i believe that should be enough to get you started with basic authentication in fast api and from there you just continue creating your endpoints and you use like get current user and like i said you might want to think of a different thing you want to put in the payload i don't think it's the best idea to put the password hash so you know think of your payload maybe it can just be the user object with everything but the password hash maybe it can just be the id it can be whatever you want here but you're just passing some kind of payload for the token so you can get that out when you decode it here so if you have any questions about this feel free to leave a comment down below if you like this video please give me a thumbs up if you haven't subscribed to my channel please subscribe so thank you for watching and i will talk to you next time
Info
Channel: Pretty Printed
Views: 30,200
Rating: 4.944931 out of 5
Keywords: fastapi, fastapi authentication, fastapi oauth2, tortoise orm, authentication, api auth, api, python, jwt, oauth2 jwt, json web tokens
Id: 6hTRw_HK3Ts
Channel Id: undefined
Length: 40min 2sec (2402 seconds)
Published: Tue Jan 05 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.