Video details

Modern backend with TypeScript, PostgreSQL and Prisma - Part 3

TypeScript
08.25.2020
English

The goal of the live-stream series is to explore different patterns and architectures for modern backends with Prisma. Prisma Client is an autogenerated type-safe database client for Node.js (ORM replacement).
The series will focus on the role of the database in every aspect of backend development including data modeling, the API layer, validation, authentication, authorization, and deployment.
In this second stream, we will build an API, add validation, and write tests.

Transcript

- Hello and welcome, we're live and welcome to the third part of this livestream series in which we talk about how to build a modern backend with TypeScript, one second, I'm just getting here some loop back. Okay so, we are going to talk about how to build a modern backend with TypeScript, PostgreSQL and Prisma. And in the last part, in part two, we spoke about how to implement the REST API with Hapi.js and as part of that, we introduced validation and testing, and I'm going to share my screen just so we can go a bit over what we did last episode. And then we can go on to this episode's content, which will be focused on authentication. We're gonna do email-based passwordless authentication. And in addition to that, we will also implement some authorization rules. So if you joined for the second part, we also released the blog post which covers everything that we did in the second livestream and that you can view on the Prisma blog. And I think the really, the important part for today's episode that is worth covering is the list of endpoints that we've implemented. So in the previous episode, we only implemented a subset of the endpoints just to get a feel for the different patents, for the different CRUD patents and by CRUD I mean create, read, update, and delete. And of course there were many more endpoints which are implemented in the repository and you can find the, you can find the full example on this address, I'll share that in the comments. Hello Mark and hello, someone who is joining, I believe from Korea. So welcome all. And I just shared the link to the full working API with the authorization and the authentication implemented. So you can already go and have a look at that. So coming back here, we implemented a lot of endpoints that were related to the different resources. So we had users and we had courses and the connection between users and courses was done through this course enrollment table, which was a many-to-many between users and courses. We also had tests and these are associated with a course. So each course can have multiple tests and then each test can have multiple test results. And each of these test results are associated with the test in addition to the student who sat the test and also the teacher who graded the test. So I'll open up here the model, this is the Prisma schema. I'll make this a bit bigger. And we also have Tom joining from Taiwan. So welcome Tom, nice to have you. And throughout this livestream, I want you to feel free to engage if you have questions, if I'm going over something too fast, just leave a comment. And every once in a while, I don't have always the screen open, but every once in a while, I will open up the livestream window and I will be able to see your comments. Okay. So, here we're already starting to see some of the changes with this token model, but you can ignore that for now. The main thing that I wanted to show here was that we had this model and we sort of defined all of the different relationships and these were associated with these REST resources that we implemented. So that's all good. But today we're here to talk about authentication and authorization. So before we get into that, let's talk a bit about some of the differences between authentication and authorization. I'm just going to remove the screen share for a moment just so we can really focus on that. And I wanna talk about some of the differences between authentication and authorization. So, these two terms, authentication and authorization, are often used interchangeably but they actually serve different purposes. And generally speaking, they're of course, both of these things are used to secure applications in complimentary ways. So while they serve different purposes, they are often used together in order to secure applications and grant different permissions. But put simply, authentication is the process of verifying who a user is. And authorization is the process of verifying what that user has access to. So a really good example, a good mental model to have to think about this is going to an airport. When you go to an airport, you usually bring your passport and your boarding pass and authentication could be the process by which you show your passport when you come in and they allow you through the security gate. And what you do is you're actually showing an official document that is very hard to forge. And by that, you're authenticating that you are who you actually say you are. In the same example of the airport, authorization could be seen as the process by which you're allowed to board the flight. So when you get to your gate, you present your boarding pass, which is normally scanned actually, and verified probably against the database of flight passengers. And then the ground attendant authorizes you to board the flight. And so, these are basically the two concerns that we have, who are you and what are you allowed to do. So how do these two concepts of authentication and authorization play in the world of web applications? Well, web applications typically use a username and a password to authenticate users. So, usually when you log into a web application, you pass a username and a password, and then the application can identify that you are in fact, the user you claim to be. And that is because the password is supposed to only be known to you and the application. Now, some of you may be thinking, but wait, the web application is not supposed to actually know the password. So we'll make a bit of a side note here to talk a little bit about password hashes. So web applications typically don't actually store your password. Instead what they do is they rely on a technique called hashing to store a hash of the password. So what is a hash of a password? A hash is just a mathematical function which takes any arbitrary input. So in this case, it will be the password. And it generates this fixed length string. And this, if you use the same input into this half hash function, you will always get the same hash, the same sort of result of the function. And so, what is the power of this hash function? Well, the power is that you can go from a password to a hash, but you can't go from a hash to a password. So what that allows the application to do, it allows to verify that when you log in and you send in your password, that it's actually correct, but it doesn't require the application to store the actual password. Why is this important? Well, in recent years, web security has become a growing concern and there have been many websites that have been breached, including Adobe and many other services. In fact, I'll share a link in the comments if you're interested. And during, a lot of these leaks what was discovered was that a lot of services are actually storing some passwords in plain text, they're actually saving the exact password. Which means that if someone hacks into the web service, you have a problem because the password is actually stored in plain text and so hackers usually try to use that same password in order to try to log into other services that might be associated with your account. And so, this growing concern has resulted in many different types of solutions that improve authentication. Some of these include multifactor authentication. And multifactor is really a technique whereby you present more than one piece of evidence. So when you authenticate, when you try to log into a service, usually you just parse in your password. But with multifactor authentication, usually there are two bits of information that you need to parse. So one of them might be the password and the second might be an authenticating a one-time password. It might be this one-time password that you get by SMS. Another example that is common outside the world of web applications is when you withdraw money from an ATM, when you withdraw money from an ATM that requires two factors of authentication. The first is the possession of your bank card. And the second is the pin code that you use in order to pull out the money. So, these are all nice techniques. The case of a card is something that is obviously hard to do in web applications. And so, the one-time password with an SMS or an authenticator app is the very, it's the more popular approach. There's also another approach known as an authentication key, or an hardware authentication device known as a YubiKey, which is also like a physical USB key that does the second part of the authentication for you. But in this article, what we're, in this, sorry, in this livestream, what we're going to implement is email-based passwordless authentication. So what is this approach? This is an approach which improves, first of all, it improves user experience. And the second is it improves security. The way it works is it works by sending a secret token to the user's email account when attempting to log in. The user is then asked to open his email and then he gets this token. That token could be just a number, an eight-digit number, or it can be an alphanumeric token. And then what you need to do is you need to take that token and then do the second step of authentication by parsing that back into the application. And so this is a form in a way of two factor authentication, or rather I should say, it is a two-step authentication method. Okay so, what are some of the advantages of this approach? I'll talk a little bit about the security of this and the user experience. So essentially this approach piggybacks on the email service of the user, which can generally be assumed to have already authenticated the user. Most of us don't just log out constantly from our email service. And so, because you're not, as a user, you're not required to set a password and remember it for the application, the user experience is improved. Security is also slightly improved because the application is relieved from password management responsibilities, which can be quite a big attack surface. So if you do normal authentication in your application, you have to manage passwords, you have to make sure that you're hashing them correctly. You have to make sure you're using salts and you have to make sure that your application in general, does not leak those passwords in any way. And so by outsourcing a lot of the authentication process to the user's email account, it means that we inherit in our security, both the benefits and the weaknesses of the user's email account security. But these days, most email services provide the option of second factor authentication in addition to other security measures. So in the case of Gmail, for example, when you log in in a new location, it usually does extra steps to verify essentially with multifactors, with multiple factors, in order to authenticate you. Also this approach avoids having to make sure that the users pick strong passwords, and it also avoids the problem of users reusing the same password on multiple sites. And so, when we speak about security and authentication, it's never black or white, it's always a spectrum, and there's always different attack surfaces depending on the approach. But I think as you'll see, and I'll do a demo, you will see that this approach works pretty nicely. So let's talk a bit about how this process would work. And I created a little diagram here which I'll, I'll share my screen, let's see. Okay, add to stream, and then. So, here I have created this diagram which shows the process of how our grading app will authenticate a user. So the first step we have here on the left, the user and then we have the backend and the backend communicates with the database. And as we'll see, it also, we'll use an API in order to send emails. So the first thing a user does is he uses the logging endpoint. And this login endpoint is open to anyone. He parses an email. And then what the grading app will do is, it'll first check if the user exists in the database, in the users table, if not, it will create it. And in addition to that, it will also create a short-lived token. This token will be persisted in these tokens table, and it will also have a validity. So it'll only be valid for 10 minutes. So now we have token and that token is gonna be an eight-digit numerical number. And what we're gonna do is we're gonna send that token to the user's email. Then the user will check his email account, hopefully, and then see, oh, I got this token. And then we'll do the second step, which is step four here. And, in that step, he will parse the email and the email token. And at this step, if both of these are correct, if they match what we saved in step two in the tokens table, we will know, okay, this user in fact, owns this email account and so we let him through. But what does it mean letting him through? Well, remember that I said that this token is only valid for 10 minutes? In fact, this short-lived token is only useful in order to use it in the authenticate endpoint. What the authenticate endpoint will do is it'll verify it and if it matches, it'll generate what we call a JWT token. And it will save that token to the database, it will save a reference to that token. And I think actually this is not the most up to date version. So I will just open up the latest version of this diagram. Exactly. So, in fact, it'll only save the token ID into the payload of the JWT token and save that in the tokens table. So, it's worth pointing out that so far, I've mentioned two kinds of tokens. We have this short-lived token that is only valid for 10 minutes and has an eight-digit alpha numerical form. And then there is the long-lived JWT token, which will be used to actually access most of the endpoints. And so this token will be sent back to the user in the authorization header, and then the user should sort of keep that and then use that for all of the requests. For example, if he wants to create a course, he's gonna parse it along in the authorization header. And then the application, the grading app backend will be able to determine who that user is. Okay, now we'll come back to this and talk a bit about what JWT. So JWT is a standard, and you can read more if you're not familiar with it, you can read more about it in jwt.io. But the basic idea with JWT is, is that you have this token that is generated and is verified. And so when you generate, you can put any information to it. And that is simply, it's not encrypted in any way. It has these three parts, the header, the payload, and the signature. The payload is where we store information. And so in our case, one moment, I will actually take a real token here, and I will copy it in. Let's see, okay. We've got the spinning wheel, okay, great. So it's saying it's an invalid signature and that is because it doesn't have this secret. But the important part is that it has three parts, the header, the payload and the signature. The payload is just a basic encoding of this JSON object here. And in this JSON object, I'm really just saving the token ID. Because in our database, we actually associate this token with a user and you can see that perhaps here. So this is the Prisma schema. And what I've done here is I've added this tokens table and it has a one-to-many relationship with users. So a single user can have multiple tokens. And there are two kinds of tokens, there's email and API. The email is the short-lived one and the API grants us access to the different endpoints of the API. It also has an expiration and a valid field. And of course the relation scalar field that links it back to the user. And so, if we go back to the, to this, once the JWT token is parsed in step seven, every time it gets parsed in, the grading app looks at the payload of the token and it sees, oh, this is token ID number 100. But before it, but because a user could parse theoretically any payload, this is where the signature of this token comes in handy because the signature verifies using, it verifies that the payload that is parsed here is actually valid. And how does it do that? JWT relies on cryptographic hash functions. And in the case of this token, in order to generate these JWT tokens, you need this secret. What the secret allows you to do is, so first of all, the secret is only known to the backend. And this secret basically allows you to verify that nothing has been changed in the JWT token. So hopefully that was a good background on what JWT is. And I wanna talk a little bit about the different approaches with JWT. So some of you may be already familiar with JWT. And there are different approaches that can be taken with JWT. The one approach is using what they call, what we call stateless authentication. And in stateless authentication, the approach is that we save the user ID, and we save all of the information that is necessary for us to authenticate the user without making a round trip through the database. This has of course, a lot of benefits, but the reason that we're using stateful and by stateful, I mean that we have just this token ID in the payload of the JWT token. The benefit of that is that if we want to invalidate a token, imagine that someone had their account hacked and we need to invalidate the token that he's using, we can do that by updating the validity field here. And then every time, say the attacker, got ahold of the token, if we update the valid field to false, then he will no longer be able to do that. And so, this increases the security and in effect, what we're actually implementing here is some kind of a session mechanism. It allows us to remember across multiple requests, who the identity of the user is using this JWT token. So that's all great. Now how about I do a little demo here? So I have the application running here and this is already in the complete state. And I'm gonna do these demo before I actually show how all of the implementation for this works. So I have the server running here and maybe I will also put the diagram on the side just so we have that. So the first step, irrespective of whether I have an account yet or not is going to be to call this login endpoint. And I'm gonna have to parse the login endpoint my email in the payload. I'm using this HTTP client, which is called HTTP, sorry, HTTPie. It's very similar to cURL but it has a slightly nicer interface. So I really like using that. So, I'm gonna make a call to log in, and I'm gonna put in the email here, normanprisma.prisma.io. Okay so the first thing is I got a 200, that's great. Theoretically, the backend should send me an email. However, because I'm running in development mode, I have it just logging the email token that has been generated to stdout. And so here I have this token. Generally speaking, this will be sent to my email. I will then go to my email, copy it from there. And then, what we can actually see is so we can actually look now at the database and I'm gonna open up Prisma Studio for this. So, just to. Okay so I have a lot of data here, but I'm interested mostly in the tokens table. So I'll look at tokens and I can filter here with using the email token ID equals to this. And there we go, we found this. So this was created 25th, and this is associated with user ID 319. And if I go to the user's table and look for this user ID, you'll see that it has my email. So, that's all great. And you can see here that there's already five tokens, five different tokens. That's because I've been debugging the app and playing with it a fair bit. So, that was this step two in which the token was created. And in fact, this was an email token. Now, after I call this login, the next step will be to call the authenticate. And here I'm gonna parse it, the email token. And I'm gonna paste in that number that we had before, I hope that you can see this. No, that's not the correct one. I'll copy it again. And what we have here is we have this authorization header and I can actually go and copy that into JWT. So, the great thing about JWT is that you can actually just easily read the payload because it's not encrypted. In fact, in our case, we can't verify that it's correct, but the backend can, why? Because it has access to the secret. And so we see here that the token ID was 309 and in fact, that matches what we had in, what we saw in Prisma Studio in the tokens table. Great so now we have this token and theoretically, I can already go ahead and make different calls, for example, to courses, right? So if I just make a normal course, a normal request to the courses endpoint I'll get a 401 and that is because I'm not authenticated. But if I parse the authorization, and this is sort of the syntax with HTTPie for parsing in a header, you use the colon, whereas if you use the equal sign as we did here with the email, that will actually send it in the payload. And so now I can make this and I'm getting all of these courses and the tests that are associated with them. So that's all wonderful. Now let's dive into the implementation for this. So I will pause here and see if there's any questions that have come up. All right so we had, BO Hazard joined from Dusseldorf, welcome. Hope you can do also Nexus plugin in the future, Prisma plugin. Yeah, perhaps we can do that too. The letters are too small. Terminal font, okay so I'm sorry about that. That's almost always a complaint on these livestreams. So really sorry about that. But hopefully, you can, hopefully it's large enough now. Great. So let's continue with the implementation. So I've already gone ahead and created this token model. I think I've covered most of the details related to this. And another thing that I've done here is I've added, this is admin. This is admin and we'll talk about authorization in a moment is something like pseudo access imagine that. So, authorization as I mentioned earlier, was what our user is allowed to do in the application. We already know who that user are but now we have to determine what is he allowed to do. And so generally in our application, most of the authorization rules will be derived from the course of the, so, let me rephrase this. So the authorization model of the backend will define what a user is allowed to do. And there are two things that will grant that user permission, permissions to do operations in this API. The first is whether a user is an admin and that is denoted by this is admin field. If so, they're allowed to perform every operation in the API. Otherwise there's a second check, which is, is the user a teacher of a course? If so, the user will be allowed to perform CRUD operations on all core specific resources. So for, if you remember, we had each course can have multiple tests, it can have multiple test results and it can also have different enrollment in the course. And so all of these things will be accessible to a teacher of the course. So, we had the first thing was we had, are you an admin? The second thing we had, are you a teacher of a course? And if so, should you be allowed to do this? If it is associated with a course that you're a teacher of, you're allowed to do that. And then third, there are certain operations which require you to be authenticated, but don't necessarily require you to be the teacher of a course in order to do. An example of that is the courses endpoint which I just called. So as we saw it, if you remember, when we just called the courses endpoint, not being authenticated, we weren't able to get anything. But the moment that we parsed in the authorization, in fact, we weren't a teacher of any of these courses and yet we were able to access that. And so, there are some endpoints which are open to every authenticated user irrespective of whether he's a teacher of or not. And this is one of them. And then lastly, if we go back to the diagram that we had here, we have this login and authenticate endpoints, and these need to also be open to everyone because otherwise you wouldn't be able to sign up. Now, quick note on logging versus signing up. The approach that we're taking really doesn't make a big distinction between logging in and signing up. The only difference between logging in and signing up is whether we create a user or not. We still have to create an email token but if the user exists, then we just skip that part of creating a user and then we just create the email token. So that was a note on that. And then we'll see how that's implemented in the authentication part of the application. So, I think this is a good moment. It was a bit of a long prelude and we're half an hour in, so let's dive into the code here. So we had this token table and I'm just going to check out the branch that I was, I think I had some. Right. Okay, great. So what have I done so far? If you remember, we have this server, this is the main entry point to the application. I'm not sure why it's not lining up nicely. Maybe we can extend that. Yep, great. And we're going to actually create two plugins as part of this. The first one we're gonna do is we're gonna create this authentication plugin. And this authentication plugin will make use of a library called Hapi auth JWT2. This essentially allows us to use JWT in a Hapi.js application. And so if we look at the package, JSON, I've already added some packages here and I'll go over those. So the first one is SendGrid mail and this will be necessary so that we can actually send real emails. SendGrid is an email service that has a really nice API. And it also has a free plan that allows you to send up to 100 emails a day. And it's really simple to send emails. It's, you have this promise-based API and once you sign up. Oh, okay, that's a shame. Let's log in. So, it would be great if you are actually implementing this or following this to sign up for an account, that's really simple. And then the only thing you really need besides that is you need to generate an API key. And as you can see, I already have generated that. But really the process is as simple as create API key, and you can define it, the different levels of access. You might wanna just be able allow it to send emails. And so when I, I believe I just created full access, but I'm also not making full use of this. So, that is the SendGrid API. Then we had .env. .env we'll be using in order to set environment variables. So if you remember JWT, the JWT tokens rely on a secret and that secret, as the name suggests, should be secret. And so generally speaking in applications in backends, there's this idea of secrets and the secrets are usually parsed at runtime using what we call environment variables. So I think in the Prisma, so an example of that is this, I believe I created it, nope. Okay, so here I have this, here I have the two secrets. You can't see the full part, but we have the JWT secret and we also have the SendGrid API key. And .env will allow us to access those. So, going back to package JSON, did we have anything else? Yeah, so we had this Hapi auth JWT2 and this JSON web token. Now, let's talk a bit about how Hapi allows us to implement authentication. Hapi has his idea, well, rather two ideas when it comes to authentication. It has this idea of a scheme and a strategy. A scheme is like a class of authentication. And a strategy is an instance of that scheme. So in our case, JWT will be the scheme that is provided by this Hapi auth JWT2. And the strategy is the strategy will be an instance of this scheme, which we'll implement in a moment. So to start, I've already gone ahead and created here an auth plugin and, we will get to this part in a moment. But the important part, and this isn't fully implemented, is that it relies on Prisma. And that is because we need to access the database in this plugin. And we also need to access, we also rely on the Hapi auth JWT. And so, and here we actually check that the JWT secret has been set. Because this is really important. And as you will see, the JW sees, JWT secret actually reads it from the environment variable. And if that doesn't exist, it'll just default to something, which is the reason why we just warn here in the console rather than abort the operation. Now, then we have these two endpoints and I'm gonna pause here to make sure that this can be read, okay can you see me encode? Yes, I can. Thank you, Ilya. Wonderful. Okay so we had this login and I'm trying to see if I can find this for reference. So we have that on the side. And then we have this here. And the payload is just the email. And then we have the second endpoint which is the second step of authentication, which I've defined a handler for, but it's not fully implemented and this is what we're going to do. And that takes two payload parameters, the email and the token. If you are joining this stream and you haven't seen the previous ones, it may be worth pointing out that Joi is the runtime validation library that we're using. And that sort of, it was part of the Hapi ecosystem. And it's a really, really nice way to declaratively define rules for how you expect your data to look like. Okay, let's go and start implementing this login handler. So we have this login handler here. And, the first thing that we probably, wait before we actually implement that, we need to declare what we call the authentication strategy. I forgot about that. So our authentication strategy, we defined that using the server.auth.strategy. And the nice thing about using TypeScript is that we get really reach auto-completion. And that's another thing probably worth pointing out that Hapi itself is not written in TypeScript, but we have these definitely typed packages which give us the typing's for the package that we're working with. And so I've added one also for the JSON web token. So this is how we get all of this nice auto-completion here when we define this. And then, the first thing that we tell it is the name of the strategy. Do we have that in the ? Yeah, so we have the strategy if you remember, was an instance of a scheme and the scheme has already been defined by this Hapi auth JWT plugin which we need to register, which I will do in a moment. Now we can use a name for it. But I've already created here a constant to define this strategy and the name of the strategy is simply API. Then we need to give it the actual name and, here I'm just putting JWT which I know is the one defined by the plugin. Let's see if I can get that working just so you know. So as you see here, they're actually using the same name for the scheme and the strategy, but the strategy name defined by the plugin is JWT. And so JWT is what we'll use. And then here we parse in different options. In our case, the first thing that we care about is the key, which will be this JWT secret constant that I've defined. Verify Options. These are all very sort of, these are options that are exposed by the plugin. And so you can see verify options. So here you can parse in different things. The most important thing with, I mean, one of the most important things with JWT is to make sure that we specify the encryption algorithm that is used. And in this case, we're going to use this JWT algorithm which I've already defined and that uses HS256. And, let's see, do we have anything else? And then of course, we have this validate function. And for that, I'll define here, validate API token, which I believe already exists. And if look at the documentation for this, this validate function, essentially, needs to return an object. Let's see, there we go. Here's the documentation. So this is a function which is run once the token has been decoded with the signature and it has the signature of decoded, request and h which I've already defined. And decoded will give us the actual decoded payload of the token. And then the request and the h, these are standard Hapi lifecycle methods that we have in all of our handlers, request and h. The important thing is what object we return. We can, we have the is valid field which actually defines whether authentication succeeded or not. And by default, I have it set to false. And we also have these credentials objects. So for example, if all, if true, imagine that this was our authentication. So we might wanna return here something like a true and then credentials. And then this credentials object will actually be parsed to the route handler. So we'll get to that in a second. But this is our validate function. And, in our validate function, there's a couple of things that we want to do, but before we do them, I'm going to register this plugin so that everything with the types works well. One moment. Let's see. So we have the server here, and then we want to import this JWT, Hapi auth JWT plugin. So import Hapi auth JWT from Hapi. And then we will register that, oh, sorry, I made that smaller. We will register, the order doesn't matter here because we have these dependencies that are defined for us. And, let's also register the auth plugin which we haven't imported, oh. And VS Code did a nice job of automatically importing that for me. And, we also have the email plugin. That's already defined, define that ahead of time. And really the only thing that it does here is it, if the SendGrid API key is already set, it will, set the API key and then set the function on server app, send email token to be a function which actually makes a call to the SendGrid API. So literally this is all it takes to send an email in Node.js using the SendGrid API. But as you remember in debug mode, we don't wanna start sending too many emails. And so for that purpose, I have added this debug option here. Now, a quick reminder, a quick refresher, you might remember this server app object, which we used also in the Prisma schema. And that allows us to centrally, parse an object around the application. So this server app object is accessible usually within all of the requests. And so this is a good place to put things that you want accessible throughout your application. It could be seen as some kind of a form of global state, which is generally discouraged. And there is this little trick here that we do, because what we're doing is by adding Prisma or in this case, by adding this send email token function, we're actually modifying this server application state interface. This is like a TypeScript thing. And so, this trick is called module augmentation. And this is a way to tell TypeScript, the TypeScript compiler, that we've added another type to this interface. And so here we augment it with this Prisma and here we augment it with this send email token. Why is this nice? Why is this important? Well, besides eliminating some of the errors, everywhere that you try to access serverapp.prisma, you will get all of the auto-completion because TypeScript will be able to know that this is a Prisma client. And in this case, it will know the signature of this function. Okay, so what have we covered so far? We added these three plugins. This is the one that we, is an external package. And then we have our one, which we'll take this authentication scheme that is defined by the plugin, by the Hapi auth JWT2 plugin, and we'll define an actual strategy for that. And this is what we did here. This strategy, this is the name this, we're gonna tell all of our endpoints by default to use this strategy. And one of the ways that we can do that is by, there is a function that Hapi exposes that allows us to determine the default auth strategy. So in our case, that's, what was it? API auth strategy. And this is a really good security practice to by default secure all of your endpoints. Yeah. Now, because we've defined it as the default strategy, and if you remember the login and the authenticate, they need to be open. Then here in the options, you can define this auth and we wanna set it to false. Okay, great. Now let's look at this validate API token function. because there's a couple of things that we wanna do here. Validate API token, or was it? Oh, there we go, great. So, first of all, we need a couple of things. We're gonna be accessing Prisma. And we also want to validate the payload of the API token. So I've already created here this little schema, this Joi schema in order to do the runtime validation. This is not strictly necessary, but an extra precaution that is good. Let's see, these are the long, we are here, right. So, first thing I wanna do is I wanna get Prisma from request.server.app. And if I hover over this, you can see that it's a Prisma client and that's thanks to this module augmentation. So I can close this now. And I wanna get the token ID from decoded. And, let's see, what else do I wanna do? Oh, yes, I wanna validate the payload. So for that, I'm gonna use this apiTokenschema.validate and to it I parse decoded. And, this returns an error. So if you look at this, it has this validation result. And this error will be undefined if there's no validation error. In such cases, if you're not 100% sure, there's always, of course, the API documentation which help with this kind of thing. So that's great. So if there was an error, what we want to do. Here's return is valid false, and then we can continue. Now we can do a try catch here. In fact, I have this snippet here, which makes it easier. And I'm gonna try to fetch this token. So, I'm gonna try to get this token ID that is in the token payload. I'm just wondering whether it's, it makes sense to first implement the login method, because it seems like that's the natural flow. First you log in. Yes, I will pause here and go to the login endpoint and first implement login handler. Then we can actually do the validate API token. I'll just make sure this is syntactically correct. Save this, I'll try to run this, npm run dev. Let's see if we get any errors or if the server starts. Okay, it looks like the server is already running somewhere. And it's running, great. Okay, I'm gonna pause here to see if there's any question so far. Okay full screen. Someone asked if Prisma supports transactions. Yes and no, we don't support long running transactions but all of the operations that you use in Prisma are in a transaction and as you will see, I'll also will show how to use the actual transaction API if you wanna group multiple database operations into a single transaction. But the long running transactions are a bit off topic, but we have an issue about this and this is something that is not currently planned. Okay, so coming back to the login handler. In this login handler, we get the, first of all, we want two things from the request server app, and that is Prisma and send email token from request.prisma.app. And then, sorry, request.server.app. And then we have the email which we get from the request payload. Request.payload and then we'll do a type assertion here to make sure that it is an email, wait wait, login input. Yeah, I think I've already defined the type for this. So login input yep as log in input. And then, const email token is equal to generate email token. And this is the function that will just generate an eight-digit number. And, where were we? Login handler, okay so we have that. And then we need to also set the token expiration when we save it to the database. So, we can use this add function from the date functions here, and then I can add minutes. And I've also defined a constant here, which is 10. So we want this email token to be valid for a very short period of time. That way, if someone actually gains access to the user's email account, it won't be valid. So you can't just look in the archive or in old emails and then still use that as access. Okay, just run prettier here. Okay, now we can actually start our try catch and do the database stuff. And, remember that this endpoint is used for both logging and registration. And so, what we wanna do is we wanna create a user if it doesn't exist and a short-lived token. And there's a really nice way to do this with a Prisma. So, I'm gonna create a token here, prisma.token.create, and then in the data I'm gonna parse it the email token. This will actually just take that field. And just for a quick reminder, I'll put the schema here on the right hand side. So, do we have that here? Where is token? Oh, okay. Yep, we changed branches. So just update the Prisma schema here. And, okay so. And you'll be able to see what has changed. So first of all, the first name, last name and social became optional. And then we also added, this is admin. And then we also have this token. We're getting a squiggly line because I actually haven't defined the token types. So we'll do that just below here. So we have these two token types which are used in this type field, the token model. And now we can go on just to make sure I'll run and Prisma generate. And, and I'll also make sure that we have that, oh, okay, yeah, we have some mix up because of all of the different branches, but that's okay. What we can do is we can go clean up the migrations and then, generally this is not something you would do, but oh yeah, we also need to drop the database. Okay, it doesn't exist, great. So now we can save the migration. Now it will create the database for us and also run the migration, great. So if I go again into the PG, the Postgres CLI, I can see all the different tables here including the token. Yep, that's right. Great. Coming back to our code. So oh, did we run Prisma generate just in case? Okay, that looks good. Restart via server. Okay, so we have the email token here and, expiration is token expiration. We're already getting the squiggly line here because there's different things that are missing. Let's see, what else do we need? Oh, the type, that's important. TokenType. this is an email token that we're generating. And we also wanna associate this with the user. And, remember that I said we can use this endpoint for both creating, for basically, logging in a registration. This is where something like connect or create comes in handy. And so here, I'm gonna parse it to create. And email is the only required field in the user model. So, that really is nice and it allows us to just quickly create, and then later the user once he's authenticated, he can actually go ahead and update his account. Okay, I'll pause here to see if there's any questions. Nope, okay, great. So, in this case, we create. And then in the case of connecting, we just need to tell it here where. I just need to parse that, where, and then email is email. So, or we can just do email. So basically it'll connect it to the user whose email is the one that was parsed in the payload here. Otherwise it'll create one with that email. So, let's go on. And now we've created the token and created a user if that was necessary. And now we can call await, send email token. And I get the nice typing so I'm gonna give it the email. And I'm gonna give it also the email token. And then return, h.response.code200. This is a standard way to return an empty body with a 200 error. In the case of an error, I'm just going to return a, bad implementation and just, parse along the error message there. Okay that was our login endpoint. If I go ahead and try to call it, let's see what happens. So first of all, let's see if this works, if this even starts the server. Okay. So JWT secret is not set. That's probably because we don't have .env configured, but that's okay. We can already access it, great. So I'll make this bigger, hopefully that's big enough. And I can already call the HTTP or I can try to see if I have that in my history. Yup, that's the correct command. And here I should have that logged. That's great, okay so, we got this email token and let's look in the database to make sure that in fact, the token was created so. from token. And, 94758821. And that's exactly the one that we had here. In fact, when is it, when does it expire? In fact, it does expire in 10 minutes from now. So that went well. Okay, now, let's implement already the second part, this authenticate handler. Here too we want to get Prisma. And we have two things in the payload and the email, email and email token, request.payload. And we're doing a type assertion, authenticate input. That's something that are predefined here. It's just a simple interface with two fields. Okay, now what do we wanna do? Well, we wanna verify that this email token, in fact, matches that user. And we're gonna use again, Prisma in order to fetch the token. Email token is the email token, or I think we can just do that. And there's another thing, and now we're gonna fetch relations. So Prisma API will, by default, just returns, by default, it just returns the scalar fields which are all of these. But for us to get the related user, we have to explicitly tell it. So we can do that here by telling include. And it already, the auto-completion it allows us to do that. A really nice thing is if I hover over this, you can actually see that the type is the union of a token and a user. So that was our try. Also we have the error. And in our error handling, I think I would just do the same bad implementation that should work for now. Okay, great. So we fetched this email token, and now we wanna make sure that it's still valid, right? So the first thing is, I generally like to exit early if there's a way to detect an error. So if fetched email token.valid, if it's not valid, we wanna returnBoom.unauthorized. That's the first thing. The second thing we wanna check is is whether it matches the, whether the email that was parsed in matches the one that we found in the token, well in the associated user with the token. So, he fetched email token. Also that could be that's why that's the first checked here. .user.email matches the email from the payload. Now what we wanna do is we wanna create the long-lived token. Or wait, first thing we wanna check is, has it expired, right? Almost forgot about that. Let's see. Okay, cool. So, if fetched email token.expiration, if it's smaller than new date, then we also wanna return unauthorized here. And we can also parse it a message. Great, okay so now we handled both of the cases in which the token, the email token is invalid. In this case, we're already on a good path. Now we just need to create a long live token. And so the first thing is we need to set the token expiration, and that will be similar to how we did it before. I can even copy that to save us a couple of seconds. But instead of minutes, what it's going to be, it's gonna be hours and, here I have the authentication token expiration in hours. And the current expiration for it is 12 hours. This is something that can obviously be adjusted. There are different security concerns when it comes to these expirations. Okay so we have the token expiration. Now I can create the actual token. Usually this is a more of a convention or a style choice, but usually what I like to do is I like to prefix when I fetch an entity, I like to prefix, we fetched and the reason is usually, and when you're fetching entities, you're fetching entities that are related with perhaps user payload. And so it's really nice to have that distinction to know, oh, this is, I'm accessing something that I fetched from the database rather than user input. So this is kind of like a trick to avoid human mistakes. Great so we have this created token. And now we're going to call prisma.token.create, and we're gonna parse it some data, first thing is type and here we wanna use API type, expiration is token expiration. And then we also wanna connect this to the user. So here we have, again, this connect, connect or create or create. In this case, we just want to connect. And the reason is if a user reaches the authenticate endpoint, which is the second part of the authentication, we can be 100% certain that in fact his account exists. And so here we just parse in the email and this email will be the one from the request payload. And in fact, it's already been verified. So we are good here. Okay so we created this token, what do we do next? Well, we just run here. Next thing we wanna do is we wanna invalidate the email token because it was just used and we only allow it to be used once. And so we'll go prisma.token.update where ID is fetched emailtoken.id, data and valid is false, simple as that. Okay, so now he can no longer use that. And remember that this is where we checked it. So if you tried to call the authenticate, do a replay attack or anything like this, you won't be able to. So even if an attacker somewhere, it shouldn't happen if you're using TLS. But if an attacker is trying to steal via sniffing traffic, he won't be able to because you can only use an email token once. Okay so then we have the one last step if you remember, from our nice diagram, where was it? This one, so. Right we're at step five. So the users call the authenticate endpoint and now we generate a long-lived JWT token. And, so we generated a token ID here. This token doesn't really do much right now. The only thing is it's recording the database that is connected to the user. But now what we're gonna do is we're gonna take this created token, which is the API authentication token and then add this token ID, the token ID of this token to the JWT payload. And so, after we've invalidate, I can. Date, email, token, and then create new long-lived. I like to call the authentication token the long-lived token because it's just easier to distinguish between the two. So create new long-lived token to be referenced in the JWT payload, great. So here, I think I've const auth token, that's the one that we're gonna send, generate API token, and all I need to parse it is the token ID. And I can do that from the createdtoken.id. And let's just quickly look at that function. So this function makes use of this JWT package, which is this JSON web token which is the standard sort of reference implementation. And here we do this signing. And if you remember, we had this JWT secret and also this algorithm. So here we're parsing the payload. And really the payload only includes token ID. Right, after we do that, we need to somehow return this to the user and we can do that with h.response, the body is going to be empty. And then the code is 200, and the header we're gonna set the authorization so being hard work, sticking to the American spelling, but I think I've done okay and it's consistent. And then auth token here. Great, that looks pretty good. Let's give this a go. So where did I call the login? You know what, let's just do another one. Okay, so it just generated another email token for me. And maybe I will stack these like this and pull this a bit up so we have more space. Great, so I've called the login, now I wanna call the authenticate and what I expect to get is I expect to get, sorry. I expect to get an authorization error in the response. And in fact, I do and I can actually copy this to the JWT website and see what is the payload. And the payload that we get is token ID three. And it also has this extra field in the payload. And this IAt is stands for issued at which is automatically added by the JWT plugin. Yeah, so that's something. Now let's test it another time. So if we invalidated this token, then we shouldn't be able to succeed with this again. Let's see what happens when we do this again. Oh, we get unauthorized invalid token. And in fact that was what we implemented here, invalid token. Great. Okay so we've implemented these two endpoints. Now users can essentially log in, do this two-step process, but that is just the beginning, right? Because we haven't secured any of our actual endpoints and we haven't introduced a system to validate the API token. So we should probably do that now. And the validate API token, it will get called on every request that gets called to an endpoint, which requires this authentication strategy. So remember here we defined the default API auth strategy, sorry, the default strategies, this API auth strategy. So I don't know if you remember, but we had this, I created a status endpoint, which was a really, a really, really simple endpoint which just returns an object with up is true. And so if I call it now, let's see what happens. I'm just gonna make a simple call. Oh wait, sorry, it's just the route. So, because we set the default authentication strategy, we now require authentication for this. I'm gonna go ahead and disable the auth here. And we'll try to call it again. Okay, and that's working. So, this is really interesting because this is where we would define a lot of the different things that we care about. I mean, in the root definition, this is where we would define the authentication strategy that we require and also the authorization rules which we haven't even gotten to. Now, we're almost an hour and 20 minutes in. Let's see how far we get today. Otherwise we'll do the authorization in an upcoming livestream just to make this a bit more consumable. Okay, we still have some viewers, some listeners. So thank you to all of you who've been so far, who've stuck around. If you have any questions, of course, be sure to use the comments. And now I wanna talk about a bit about this auth, so you can set it to false as we saw before, or you can actually set it here and you can set a lot of different things. You have these different modes, you can set it to required, and you can also tell it the different strategy. And I think it's generally good to have these explicitly set for every route, even if you have the default. So of course, here, we don't really need it. So I'm just going to set it to false. But let's perhaps look at the courses endpoints and, look at what kind of authentication configuration. So first of all, I'm gonna set auth here to this object and I need to also import this constant here. Beautiful thing with VS Code is that it's really nice at picking up all of these things. So as we said, automatically imported that for me. And in fact, I'm just gonna go ahead and add that to all of the different endpoints here. Options, auth, great. Maybe we can just copy all of that. No, we just need the auth. So, we have that here. Also, this it's required and that's required. Okay, that was something. I'm just gonna get something to drink. Okay so we define the strategy, but now we have this validate and now that we've defined this strategy, this validate function will get called every time that you try to access an endpoint. So, just to simulate this, I'm gonna just return here is valid true. And I believe that this is already set here as the validate function as part of the strategy. And so let's try to call this endpoint or, you know what? Even simpler this just courses to get the courses. So of course, if we call it simply, it's gonna give us that. But let's try to find the JWT token that we generated earlier, this guy. Okay, and then we're gonna tell it authorization is equal that. Okay, now we were permitted. Now I wanted to show this validate function. Why might this validate function be useful? Well, it's a good time for us to check a lot of different things like, is this long-lived token even valid? So if you remember, in the authenticate handler, we made sure that the email token was valid, it hadn't expired. It matched the email that was parsed in the payload. And then we created this API token, right? And we sent that back to the user. The same thing needs to be done when this API token is used. And so, the validate API token is, will contain a similar logic in order to validate the API token rather than the email token as we did in this authenticate method, in the authenticate handler. And so here again, we get the token ID. We also validate the token schema to make sure that it adheres to the schema that we've predefined. And then if you look at it, in fact, it has the most important and required field, this token ID. Issued At, I think that automatically gets added by JWT, the library and expiry also sometimes gets but we don't really care about that, because we store that all in the database, the expiry and when it was issued and so on. So, okay, so we checked the validation and now we can actually check fetch the token from the database. And for that, we can do the same thing that we did fetch token await prisma.token.find one where, ID is equal to token ID, right? Yeah, that's the one that's coming in the payload. And we also want to include the user information here. And in a moment you'll see why. So first of all, if there's, if the token hasn't been found for whatever reason, then we want to return something like is valid false. And we can also parse in an error message. This is, again, something you can see here. This validate, so here we can parse in this error message. But actually, if for whatever reason this happened, we probably don't wanna parse too much information back to the user. And so we'll just invalidate it. Great, now if fetchedtoken.expiration is less than now, so automatically if you instantiate a date object that gets the current time. Return, and then here in this case, we can already, we can send back a message because it doesn't seem to assume any malice. This is the try, did I add the catch? Nope, let's use this. Okay, so we call it expiration. And, now there's another thing. Fetchtoken.valid. So if there's no fetch token or it's invalid, it'll return false. Okay, so now we handled both validity and expiration and an unfound token. Now, if it is valid, which we can assume here, then what we wanna do is we wanna actually get all of the, well, we can already return, is valid true. And then here in this catch, we can return false. We might wanna add a message at some point but right now it's not critical. And also just in case it doesn't, it reaches this point, it's also good practice to invalidate that by default. Okay so, now we validated the user, but we've already fetched the token for the user. And we might actually be interested in more information about the user, especially when it comes to the authorization layer. So so far, we just made sure is a user authenticated or not. But now we get to the point of authorization which is, what is a user allowed to do? And what we can do is in this object, we can actually parse a credentials object. In this credentials object, I'll make this a bit nicer. We can parse, just assume this is like some random value, right? Hello world. And let's look at this courses, get courses handler. So I'm just gonna log here, request.auth.credentials. So I mean, you probably can guess it by now, but the idea is that anything that we parse here in the credentials will get parsed to all of our route handlers that use this authentication strategy. And so if I make the request now, let's see what happens. Okay, and did that log? Somehow missed that. Get courses handler, oh I don't think I saved that. Okay that's being restarted. Now let's make that call. And you see this is the object. So, what I want to do here is, is I want to actually fill this with all sorts of information. The first thing I wanna add is all of the stuff that was in the token, which is the token payload, which is really just a token ID. So, I mean, this could just be token ID, decoded.tokenId. In addition, I wanna know is the user an admin? And I can get that from the fetchedtoken.user.isanadmin. And the last thing I wanna get is an array of the courses that the user is a teacher of, why? If you remember, our authorization model was based around these three concepts. The first was, are you an admin? If you are an admin, you should be allowed to do every operation. This is why we parse it in here. And then the second is, are you a teacher of a certain course? If you are, you should be able to perform operations related to that. And so it's nice to have in a single place to populate this credentials object. And so that we don't have to redo this every time in each of our route handlers. And so teacher of, but for that, I actually have to do another trip to the database which I will do now. So, I will get teacher of await prisma.courseenrollment.findmany, where, user ID is the fetchedtoken.user.id. And we want where the role is, userRole.teacher. And for these, we can use this select two, all we care about is the course IDs. So essentially this will be, you can just hover over it and then see, this will be an array of objects that contain just a single property and that is the course ID. And because an array of object is a bit unnecessary because it's just numbers that we care about, we can turn this into an array of numbers by just going teacherOf.map. And then here we can do a destruction, get the course ID, and then return course ID. And so this will be just an array of numbers because here we destruct the course ID from the object and then just return that. Okay. And so, pretty much we are done with this validate API token function. Let's call these get courses handler again. And here we see, oh, great. So we have this token ID. We know if the user is an admin and we also know if he's, we get this array of courses that he's a teacher of. So the next thing I wanna do is I want to actually show how to implement the authorization logic for some endpoints in the courses plugin. So of course, really like in this file that we have open here. And, let's go ahead and do that. I'm just gonna get something to drink before and I'll pause to see if there's any questions. Okay. So I've created a nice table with all of the different endpoints and the permissions that we're interested in. And I will show that in a second. So here I'll create a new file and I'll make this a single tab. And basically this is a list of all the endpoints. And we care right now about these courses like these. Right, these. So I'm actually just gonna move these to the top. And in the last column here, I've created this authorization rule. So this is, what kind of access do we wanna allow to these endpoints? And, for the first two, the login and authenticate, these are open. Although the second one does require an email token. And then we have these four. So to create a course, the authorization rule that we have is that any authenticated user can create a course. So if we go to courses and look at our root definitions, we have, this is the create course. And in fact, authentication is required. And that's it. Let's see, can we get this a bit? Let's move this to one. Okay, so for create a course, we're done basically, I'll add here a little check. Same thing for get courses and same thing for get a course. These are things that if you're authenticated, you should be able to create courses, you should be able to get courses, you should be able to get a single course if you like. But if you wanna update a course, you need to be either an admin or a teacher of that course. And so we'll look at how to do that exactly. Let's see. Great, so let's look now at this update course. Where's the update course? Okay, this is the update course. So we've defined the authentication strategy. And now how do we add authorization rules? Well, Hapi provides a way to provide these sort of pre-functions. These pre-functions get called before the handler. And you can assign multiple ones. And the nice thing about these pre-functions is that they're re-usable. So here we might define a rule like a pre-function that is, is teacher of course or admin. And we're getting obviously an error because it hasn't been defined but we're about to define that now. So what does the signature of this function look like? Well, we can just go down to the bottom and define it here. And this actually takes the same request lifecycle parameters as all of the other handlers. So request and the response toolkit. And, we can access the same things that we can access in our route handlers. So in this case, it's admin teacher of request.auth.credentials, that's already nice. And then we wanna check is admin. If it is, return h.continue. That is the way to sort of like exit from this pre-function and allow it to continue. The way to sort of, to exit early, say, we detect that, hey, the user is not authorized to do this operation is we can just throw a boom error, boom.forbidden, and we don't even need to parse in a message. Okay, so this was the first thing, is it an admin? But the second thing we wanna check is if it's a teacher of the course. And that is something that we can get using by accessing the request parameter. So if you remember, this is used in this update course handler which has this course ID. And in the credentials object, we have the courses that the user is the teacher of. And so if I go back to this function, I can actually parse this, get the course ID by parseInt of the request params.courseID and also set it to RAD extend. And then, we just need to do a check here, teacher of. If that includes the course ID that is being fetched, if it is, continue. Okay, that was simple enough. And the nice thing is that we don't have our route handler bloated with all of these authorization logic. So now let's go back up to the route handler. And, let's see where the create course handler is. Great, so there's another thing that I wanted to do. When you create a course, you should own automatically be assigned to that course as a teacher. And that is because otherwise you have no ways to modify unless you're an admin. And so it makes logical sense to automatically associate when you create a course to associate yourself with the course as the teacher. And so here, when we create the course, we can actually create a relation in the course enrollment table. And I'll open up the Prisma schema here. So that it makes sense. So we are creating a course and we also wanna add a record in this course enrollment table, which is this explicit many-to-many relation table. And so, the way that we can do that with Prisma is we have this members and here we want to actually create because this is a many-to-many. And here we assign it the role. So userrole.teacher. We want to link that to the user. So user. And here we can connect that to the user in the credentials. So we can go request.auth. Wait, so you, connect. Right, parse it the ID. And then we have the user ID in the credentials object. So if you remember, I'll open up of, in the validate function here. Oh, we didn't add the user ID which we should do. We have token ID, user ID and, let's see, I wonder if the credentials object is typed out. It is in fact, I already did this sort of module augmentation. Yep, so here we get fetchedtoken.user. actually, we can just do user ID. We don't need to go through the user relation. Add a comma here, that's all great. Okay, and then here, what we'll have is request.auth.credentials. Oh, and there we go, we've got user ID. Okay, I believe that's it. And so what did we do just now? We adapted, this wasn't specific to authorization, but it's related. And that is when we create a course, we automatically assign the authenticated user who's creating the course with the course as a teacher. And so as a teacher, you'll have the permissions to update it. And that's something that we can test because we just added to the update this pre-function. So let's give this a go, let's try to create a course. And the first step is we'll use this post courses endpoint and we'll parse it the payload, which for create is, we need to parse it a name and course details, these are the two required fields. So, I can go here, post and it's the courses, the authorization token should still be valid. And then I need to parse it the, save this, and then I need to parse it the name. Live course with Prisma. And course details, that's, I believe, required too. Learn more about auth and authz. Authz is like the short way to write authorization. And, okay, great so it created that. And now, I want to update it. So, I'm gonna use the put function, and, blah, blah, blah. Okay. And that's because the updates requires the course ID. So I need to parse it here the ID that we got and that was ID number one. And in fact, we got that. In fact, if I try to just fetch courses now, all the different courses, or even just one, this is a get request. You can see that the course details have been updated. Now, why don't I try to log in as a different user and then try that? So I'm gonna try logging in as Alice, remember that I can do this because I have this email token up here. We're not actually sending emails. And then I can call the authenticate, Alice and then parse this new email token. And there we go, I got this JWT token. And so now if I try to use that same put request which updates a course, but use a different JWT token, I shouldn't be authorized to do that. And if we did everything right so far, that should fail. And in fact it does. And it does so, in, sorry, not here, in this is teacher of course or. So if I change the message, we should see that. And indeed we see that here in the message. Okay, we're almost about to wrap up. I think we've covered a lot of the different concepts, authentication, the different authentication strategies, we looked at JWT, what it meant to implement JWT in a Hapi.js server, what is the secret, this two-step process for the passwordless authentication. Oh, sorry. And before we wrap up, I'd like to showcase how these things could be addressed in how this affects the test. So if you remember in the previous part, we actually tested all of these different endpoints. And now we introduced a lot of authentication logic and authorization logic, and our tests should fail because the tests don't actually ensure, they don't actually parse any authentication information. So, let's make this big, I'll open up the course's tests. And, the idea here is well, that if I try to run this test, npm run test watch, probably all tests will fail. What I'm gonna do is for now is I'm going to disable the default auth strategy so that not all of the tests will fail, but only the ones that have explicitly defined the auth strategy, which is the courses endpoints. Okay so, we have this course's test and if we go into the terminal, okay the machine seems to be a bit, let's try to run this again. So normally these run pretty quickly, oh yeah. And by quickly, I mean a couple of seconds. Not so sure what's going on, oh, perhaps the server is running and that's. Okay, I'll filter my courses.test. Okay so obviously all of these tests failed and you can see that the expected was 200 and it got a lot of 401s and that is because we've implemented all of these authorization and authentication part. And so, this is not a big deal. We can define some test helpers that will allow us to make all of this a lot, a lot easier. And we'll try to wrap up in about 12 minutes. So the idea is, that when we inject here a request, we can actually tell it, we can actually parse here credentials. And so these will be the credentials that are actually parsed to the route handler. And if you remember, we had this is admin. So, let's see, oh yeah, right. I'm missing some of the fields. And so, we want to actually populate this with real credentials and for that, we're gonna have to create a user. And so I created here like a little helper function, which creates, creates a user. Yeah, so it creates a user and it creates a token for it. And so let's try to use that. TypeScript. Okay, cool. So, I'm gonna actually save this into a function called, so essentially this will create a user and return the credentials. I don't believe, right, yeah. I don't believe that we do need to create a token here because it won't actually, or will it? Yeah, it will. Okay, great. So I'm gonna call this test helpers, and then we're gonna use that here. So, test user credentials equals to create user credentials. We can probably import that. And then, here we need to parse in Prisma. So we have that from the server.app.prisma. And whether the user is an admin, I don't think we need that. And so, here we can parse the credentials. Let's see. All right, yeah, that's not defined in this scope. Oops. So I'll define that in the upper block, and let's see. All right, we need to also tell it the strategy. And so, I can't remember what that is, oh API auth strategy. And we can also import that from the auth plugin. So let's see if that test parses. Okay, still not parsing, it got a 500. Let's see why. Perhaps I can fill just by this test. So, we're using Jest here and it has this, the ability to filter by a test name. And so we care about the create course. Okay, I believe that that didn't work. Let's see what's going on. Okay, the simplest way to, for now as to reduce the noise is to just comment these out for a moment. Let's see what we're getting here. Okay, so we are having courses test. Right, yep, so we need to define a type for this. And I believe the type is, credentials, was it? Right, yep. This is the one that we've defined in our auth credentials. That's the one. Right. And, is missing. Oh right, this is an async function. So that should be resolved now, let's see, how's this going? Okay, great, so our first test parsed and that is because we parsed the credentials. So what this function did was it created, directly in the database, it created a user and it created a token for it, an API token that is valid actually for seven days. And it returned all of the courses that it's a teacher of, which are probably none. And whether it's an admin and the token ID. So this is basically what the route handler would get from the JWT token. And this is really useful because now we can actually simulate real requests coming in. So in this case, we can also parse it along and I can enable these two lines too and comment them and same thing here. And get course returns a course, that also requires us to be authenticated. Same thing here. Let's see how we're doing so far. Okay, looks like there was some error. I'm not sure, oh, it could be that it was skipped. Okay I'll clear this filter so that it runs all of these. And so far, all of them are parsing and you can see that these are pretty, these run pretty fast. So also I'm using, it is worth mentioning, these are actually running against the real database. So it's like a full integration test of the API. And so this is where it gets interesting because update course, well, you need to be either an admin or you need to be the creator of that course. Okay, update course, it gets a 401. Oh, that was this one. But let's look first. So in which one did we create the course? In this one, and when we created this course, we parsed this test user credentials. So this test user became the teacher for that course. And so that's why when we were updating it, it did allow us to do that here because we, well. Right this failed, oh, this will, we expect this to succeed, why? Because it's the same user who created that course and thereby it makes him, he's allowed to do so. Okay, so we have this update course for which we're getting a 403 oh, interesting. Okay, let's see why might we be getting that. Put update courses is teacher of course, oh, right. So, the reason that this is happening is because, one moment, courses test, is because, so this test user credentials, it's created here. And when it's created, the teacher of is set by the values that it got when the user was created. But, when we create the course here, this test user credentials isn't updated. Because as I said, it's a simulation. I mean, this auth object that we're parsing is because it doesn't actually go through this JWT validate function. And so here, when we create this course, we actually need to add that to the credentials. So test user credentials, and this is how it would be done. So, update the credentials as they're static in tests, they're not fetched automatically on request by the auth plugin. And so here, what we do is we update it and making the teacher of this course because he is in fact. And so now, we should expect these tests to work. Let's save that. And indeed they do. And let's try the delete. I do expect that this won't work and that has to do with. Cascading deletes, oh no, that did work. Okay, let's have a look again at the handler for that, for the delete course handler. Oh indeed, it's already been updated here. I already updated this prior to the stream starting. But basically this, so someone asked about transactions earlier. Oh, okay, I'm really sorry, Chantilo, Chanlito that you couldn't see the code. I'll try to make this a bit bigger, but it's a bit late I guess for that. The thing that's worth mentioning here is that this Prisma transaction method allows us to group two operations into a single transaction. So I guess that partially answers the question of transactions. So these are the kinds of transactions that Prisma supports. And yeah, let's do a bit of a wrap up of what we covered today. So, we started by looking at the, oh, we spoke about authentication and the different endpoints that we had in the application. We also looked at the updated data model and we looked at the update data model and I demonstrated how the two-step passwordless authentication would work with this diagram here. And then we went on to talk about the different approaches with JWT, with stateless and stateful JWT. Let's see. And then we went into some of the plugins that we're gonna use, talked a bit about security, the differences between authentication and authorization. And then we actually went ahead and implemented using the Hapi auth JWT plugin. We implemented two plugins. One for authentication, that was the auth plugin, where we defined a strategy using the JWT scheme. We learned that strategies like an instance of a scheme. We defined the validate function and the two endpoints for authentication, the login and the authenticate, we saw how we can use a single endpoint to both register and log in an already registered user. We saw the, how the secret worked. And we did a lot of checks for expiration and validity of the tokens. And we also looked at the email functionality and how we can use a server application state to provide more functionality to the rest of the application. The same way that we did that with the Prisma plugins. So in the email plugin, we defined the send email token, and then we also use the SendGrid API key and the SendGrid API in general, in order to send these emails. And, I believe that that was it. Oh and then we, of course, at the end, we looked at how the tests should adapt and how to, we looked at the policy that we wanted to integrate here for these different course endpoints. And we saw how to use these pre-functions in Hapi in order to define authorization rules. So, if you stuck along this far, thank you so much for having joined. I hope you enjoyed that. And I hope to see you on the next livestream. If you have any questions, feel free to reach out. I'm available in Twitter. And the code is also available in GitHub. You can actually see the full code for, with the authorization for all of the different endpoints. So just a quick reminder that we had a lot of these different endpoints and there's some intricacies that go into implementing the authorization rules. So thank you so much and have a good morning, afternoon or night, depending on where you're joining.