Lesson Description
The "Authorization" Lesson is part of the full, Build a Fullstack Next.js App, v4 course featured in this preview video. Here's what you'd learn in this lesson:
Brian explains the difference between authentication and authorization, emphasizing layered authorization for user actions. He also implements a TypeScript function to check if a user can edit their own article.
Transcript from the "Authorization" Lesson
[00:00:00]
>> Brian Holt: Now, anyone can edit any single article, which is perfectly fine. I mean, it's kind of like Wikipedia, but even Wikipedia is like, we're going to put it like an editing moderation queue, someone's going to say like, yes, this is OK. No, Elon Musk, you can't edit your article again, right? That kind of thing. But this should work, I mean, I should be able to because this is my article.
[00:00:28]
So I think if I say save article. Oh, this still has the alerts on it, that's really annoying. Did this get called appropriately? It did update called. And then. Which one was that Proin? There it is. So there's the one that we just edited down there. Which is great. We can go back here, we should. Uh, I guess I don't have a delete button on here, do I?
[00:01:16]
So, we made a delete action. Uh, there's actually no delete button on the article here, which is fine. And I don't have a redirect on this either, which is kind of annoying. But let's go, I'm going to sign out of this, and I'm going to create another user. Uh, I'm going to say sign up. We'll make a test at example.com. And 123 test. 23 test.
[00:01:53]
OK, and now I have a test user here. And the problem here is that this user can definitely come in and just be like. Add stuff here and say save article. And you can see the test user was able to edit my own article. That's problematic, right, for the way that we're trying to design this. So now we're going to go to authz so that a user can only edit their own articles.
[00:02:24]
So when I say authz, authorization, when I say authn, authentication, this is kind of how people refer to them. They're dumb, but they're also just like etched in my brain, so that I can't really get rid of it. You'll see them written like this, I see authn and authz. So authentication is logging in, logging out, signing up. It's like handshaking with the service and being like, this is who I am.
[00:02:51]
And that's, that encompasses what when I say authentication, and authorization is, who are, like what are you allowed to do now that I know who you are, right? Just because I signed in with Facebook doesn't mean I can go just like delete other users, right? There's a levels of authorization, you could call them roles, which would be like RBAC, which is role-based authentication control.
[00:03:13]
You can go as far as like doing full RBAC with this. Auth.js supports it. They're called teams instead of like roles, right? You just be like, I belong to the admin team or the moderation team, and then you could just say like if this user is a part of this team, they can do these things. That is RBAC. If you ever see that RBAC anywhere, that's what that means.
[00:03:38]
But together, if you shove authz together and authn, that is what people call auth, like just like holistically it's those two things together, it's just useful that they both begin with auth. But I find that like I just bring that up because I find it confuses people constantly. What's the difference? Why are they the same, like why are they like considered separately?
[00:04:00]
They are separate concepts. Like, there are whole companies that are just authorization frameworks, like Oso is one of them, like, or like Google Zanzibar, which maybe you've heard of. If you have, again, you're probably working on something that's quite hard and I'm sorry. Um, is a whole just authorization framework, it doesn't care anything about authentication, it just cares about like what you can do as this authenticated user.
[00:04:31]
So we're going to do just a tiny bit of authz right now. So we're going to go to your database folder. Uh, we're going to create a file called ac.ts. And we're going to just do a little thing here, we're going to say import eq from drizzle-orm. Uh, import db from @db/db. And import articles from schema, well, I want it from not this, I want it from @db/schema.
[00:05:31]
OK. Export const authorize. Uh, user to edit article equals async function authorize user to edit article. And this is going to have a logged in user ID string and article ID. The reason why I like this pattern done in this way. You and I right now, we're just going to authorize someone to edit their own article. That's fine for now for what we're building here, but inevitably if you have user-generated content, you need moderation, so you need moderators, you need admins, and so you're going to have to have several layers of authorization of like, if you're this, you can do it, if you're this, you can do it, if you're this, you can do it, and nobody else can do it.
[00:06:15]
So I like containing these authorization rules into functions and then the functions become modifiable and testable in various different ways. All right, so we're going to say const response equals await db. Uh, select. Yeah. Author ID, articles.author ID from articles where eq articles.id article ID. And if response.length. Uh, return false.
[00:07:41]
Otherwise, we're just going to say return response[0].author ID is triple equal to logged in user ID. So we're going to take an article, a logged in user ID, an article ID and we're going to go just see, does that user exist? That's what this check does. And if that user exists, are they the one that we're checking against this article? That's the whole thing.
[00:08:09]
Now if you've written enough SQL you could probably think to bend our queries, our existing queries to check this implicitly. Totally fine, and for like if you're working at Facebook scale, you probably have to do a query optimization like this. This is not a Facebook scale app, surprise. I don't know if that's upsetting to anyone, but, uh, in this case, like it makes this like is a now very compact little function that I can use in lots of places, and it's individually testable, like there's a lot of code benefit to like being a little less optimized here, so I like it.
[00:08:43]
And I would only optimize this if I like, was really trying to cut down how many queries I was sending in my database. Like, Neon can handle lots of queries as can Postgres across any provider. So, I'm not super worried about this. I don't know, does that make sense? I've spent so much time prematurely optimizing code in my career that like, I'm like anti-prematurely optimizing code now to the point of like I make wildly assumptions that like don't scale and frequently don't, but then I go fix them then, right?
[00:09:13]
Like my favorite saying, I can't remember who said this is like if you try to prematurely optimize code, now you have two problems. You have, uh, you still have your scaling problems that you didn't anticipate, and now you have optimized code that isn't optimizing for anything, right? But this idea, what you're seeing right here, I would encompass this and I would call this, this is authz, this is specifically authz code, this user is allowed to do this thing.
[00:09:44]
Yeah, so I mean I actually wrote down this pretty well here in my notes here. I could have written this down two different ways or like. I could have written here where equals and I could have put in two equals here like this. Like I could have put that up into my query. Why didn't I? Because now I know the difference between that article doesn't exist and, um.
[00:10:16]
Yeah, like, letting the database do this is ever somewhat slightly more performant, probably. And you also like may need to add an additional index to accomplish that, but I like doing it in code because I find that more readable and you can actually disambiguate why the user was not authorized to edit that. Either something didn't exist or the user didn't, um, and whereas if they or the user didn't have the ability to edit it, this you won't be able to tell, like it's just not going to return a row and you're going to have to infer from that that the user can't edit that.
[00:10:55]
But if I came across code looking like this, I would not be mad. So this was just a choice that I made. OK, so, now that we have that, let's go put that back into our actions directory. So we're just going to put this line here at the top of our articles. Is that this one? Nope, we want to do it from the actions. Which is up here. OK, from up here, I'm sure this is why I'm complaining about organization.
[00:11:33]
Yeah, who cares? We'll have this command line fix it. OK, we're going to import that. I think that it wants this, yeah, it just, it wants libraries first and user code second. And then we're just going to put this like same check on every single one, we're just going to say if and we're going to put the await in here. Authorized user to edit article user.id plus ID.
[00:12:17]
Except we don't have to put this on create because this doesn't make any sense. So hold on. Don't put it on create because that doesn't, you're creating a new one, you don't need to authorize anyone to do that. But it's this one. And here we're going to say throw new error. And this is going to be forbidden. And now we can know if it's OK for this person to be editing this thing, so now test user can't, won't be able to edit my articles anymore.
[00:13:05]
So again, we'll just do that down here as well for delete. Even though we're not using delete anywhere, technically this does exist and it could be invoked, right? So you still want to authorize it or delete it, one of the two. So, I mean I'm throwing errors here, again, please log these, right? You'd want to log them somewhere to know that someone's trying to like hack the system or your app is hacking itself.
[00:13:42]
Yeah, cool. Any questions about databases, authorization, anything like that? Makes sense? Alright. Oh, let's, what is this? User.id. So yeah, line 68, you do have to, that was just a promise before, you did have to await it. So, we're just finished 04 database here, so feel free to go ahead and grab that. I'm sure I probably have some. Uh, npm run lint, I'm sure I have some problems, yeah, npm run lint.
[00:14:41]
Fix unsafe. Uh and format's probably fine. Yeah. Type check, it's always a good one to run as well. Oh yeah. Uh, wiki article viewer. Let's check that one out. That's in components, I think. And what's it mad about? Number is not assignable to string. That's, and that's line 25. So, yeah, this is going to actually end up being a number. Is that all I had to do?
[00:15:19]
No, authors incompatible. Uh, that one is a string though. Oh, string or null. Yeah, I'm trying to remember why I ended up changing these things. Viewer props and then article and I have. I changed this like viewer article because it's not actually really an article anymore. And I, yeah, I did change this to be author or null. As well as this one down here, this one can also be null.
[00:16:01]
But I don't think that solves it quite yet. Property ID is not compatible, and then this is still a number. Right. And is that enough for it to stop yelling at me? It is, OK. So yeah, I'll go backport that one as well. So ID is a number and author can be null and image URL can be null as well. And then I actually did make delete work, uh, if you're following along, so if you come to the next step, the delete button might not like magically show up.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops