1 00:00:00,490 --> 00:00:03,310 One of the simplest ways to handle authentication on the web 2 00:00:03,310 --> 00:00:05,820 is known as HTTP basic auth. 3 00:00:05,820 --> 00:00:09,050 It's a whole lot like being presented with a login form on a site or 4 00:00:09,050 --> 00:00:12,270 on your OS but it comes out in your browser. 5 00:00:12,270 --> 00:00:16,365 The neat thing about basic auth though is that you can send your credentials, 6 00:00:16,365 --> 00:00:20,586 your username and password to it through HTTP headers which means that our users 7 00:00:20,586 --> 00:00:24,755 can send their credentials when they request a protected section of our site. 8 00:00:24,755 --> 00:00:28,599 Miguel Grinburg created a great package for doing http based 9 00:00:28,599 --> 00:00:33,436 auth in a Flask project that handles a lot of the needs I have on this project. 10 00:00:33,436 --> 00:00:35,910 A link to the docs for that in the teacher's notes. 11 00:00:35,910 --> 00:00:41,050 But, as usual, before I can use it, I need to install it. 12 00:00:41,050 --> 00:00:44,705 So I'll do pip install flask-httpauth. 13 00:00:47,750 --> 00:00:48,880 And there we go. 14 00:00:48,880 --> 00:00:51,181 It's all installed. 15 00:00:51,181 --> 00:00:52,302 And so now we need to use this. 16 00:00:52,302 --> 00:00:53,861 Now, one thing I wanna point out. 17 00:00:53,861 --> 00:00:57,660 I'm using version 3.1.1. 18 00:00:57,660 --> 00:01:00,330 Make sure that you're using 3.1.1 or higher. 19 00:01:00,330 --> 00:01:03,450 Some earlier versions didn't have all of the little bits and 20 00:01:03,450 --> 00:01:07,600 pieces that we need, so you get to use 3.11 or 21 00:01:07,600 --> 00:01:12,020 higher to have all the stuff that I'm gonna use in this course. 22 00:01:12,020 --> 00:01:16,220 Okay, so with that installed we can start building the actual auth part. 23 00:01:16,220 --> 00:01:22,810 Now I'm wanna do this in a new file which I'm gonna call auth.py because I know that 24 00:01:22,810 --> 00:01:27,195 I'm gonna want to use the authentication requirements in more than one place. 25 00:01:27,195 --> 00:01:29,730 Since, we have more than one resource. 26 00:01:29,730 --> 00:01:34,050 And imports are a little easier if the stuff I'm importing isn't in app.py or 27 00:01:34,050 --> 00:01:34,720 something like that. 28 00:01:35,920 --> 00:01:38,950 Also, I know I'm going to make more than just one kind of auth. 29 00:01:38,950 --> 00:01:42,500 Thanks to the magic of writing a script ahead of time. 30 00:01:42,500 --> 00:01:44,910 So having all of these things in one place, 31 00:01:44,910 --> 00:01:48,610 separated from everything else, makes the whole thing so much nicer. 32 00:01:48,610 --> 00:01:53,077 So in auth.py I'm gonna create a basic HTTP, a basic, 33 00:01:53,077 --> 00:01:56,560 HTTP basic, yep auth implementation. 34 00:01:56,560 --> 00:02:01,050 So here we go from flask import g, we're gonna need the flack g object, 35 00:02:01,050 --> 00:02:04,489 the global object, to pass around who the user is. 36 00:02:05,530 --> 00:02:11,026 From flask.ext.httpauth import HTTPBasicAuth. 37 00:02:11,026 --> 00:02:13,880 And the cool thing about BasicAuth is a thing your browser can just do. 38 00:02:13,880 --> 00:02:16,520 You don't have to do anything special. 39 00:02:16,520 --> 00:02:19,578 So remember earlier, I was talking about how we're looking in form, so 40 00:02:19,578 --> 00:02:21,295 that you can just send forms to your API. 41 00:02:21,295 --> 00:02:24,706 You can send HTTPBasicAuth to your API as well, 42 00:02:24,706 --> 00:02:29,324 which means you don't really need JavaScript for any of these. 43 00:02:29,324 --> 00:02:34,286 All right so the one we'll call basic_auth asHTTPBasicAuth, and 44 00:02:34,286 --> 00:02:37,400 then we'll make a new function here. 45 00:02:37,400 --> 00:02:41,740 And so @basic_auth.verify_password. 46 00:02:44,440 --> 00:02:45,300 verify_password. 47 00:02:45,300 --> 00:02:50,040 So this function will take a user and a password and 48 00:02:50,040 --> 00:02:52,808 make sure that user has that password. 49 00:02:52,808 --> 00:02:54,380 So we're gonna accept email or 50 00:02:54,380 --> 00:02:58,760 username because I don't really care which one they give to me. 51 00:02:58,760 --> 00:02:59,470 One is as good as the other. 52 00:03:00,990 --> 00:03:07,492 And so then we're gonna try and do user = models.User.get. 53 00:03:07,492 --> 00:03:15,659 And it either needs to be models.User.email.==email_or_username, 54 00:03:15,659 --> 00:03:21,730 or models.User.username==email_or_username. 55 00:03:23,350 --> 00:03:23,980 All right, cool. 56 00:03:23,980 --> 00:03:28,471 So the nice thing about this is, again they can use either one of these things. 57 00:03:28,471 --> 00:03:34,960 So now if not user.verify_password(password). 58 00:03:34,960 --> 00:03:38,760 So we've gotten the user based on the username or the email address. 59 00:03:38,760 --> 00:03:40,690 Now make sure that password is true, 60 00:03:40,690 --> 00:03:43,210 otherwise we're gonna return false if it's not that user. 61 00:03:44,870 --> 00:03:49,500 except models.User.DoesNotExist. 62 00:03:49,500 --> 00:03:51,410 Then in this case we also wanna return false. 63 00:03:53,240 --> 00:03:55,640 Else, assuming everything's good, right. 64 00:03:55,640 --> 00:03:59,480 So we've gotten a user and their password is correct. 65 00:03:59,480 --> 00:04:01,710 The user does actually exist. 66 00:04:01,710 --> 00:04:06,050 Then we're gonna set g.user = user and we're gonna return true. 67 00:04:07,410 --> 00:04:09,770 I'll walk you through this one more time. 68 00:04:09,770 --> 00:04:13,620 We try to get a user that has either the email or the username that was supplied. 69 00:04:14,700 --> 00:04:17,010 If we don't get a user, so we get this exception, 70 00:04:17,010 --> 00:04:19,430 then we're gonna return false right away. 71 00:04:19,430 --> 00:04:22,540 If we do get a user then we're gonna verify their password. 72 00:04:22,540 --> 00:04:25,910 If that password doesn't verify, then we're gonna return false. 73 00:04:25,910 --> 00:04:29,632 If the password does verify, we jump all the way out here to this else. 74 00:04:29,632 --> 00:04:33,955 We set the global user to be the user that we got and 75 00:04:33,955 --> 00:04:40,404 we return True because they are verified, or the password is verified. 76 00:04:40,404 --> 00:04:41,364 So then that's all that stuff. 77 00:04:41,364 --> 00:04:46,810 All right, so it's time for me to go mark some stuff as requiring authentication. 78 00:04:46,810 --> 00:04:48,780 I know, again through the magic of television, 79 00:04:48,780 --> 00:04:53,060 that I'm gonna want a variable named auth as the variable that I use. 80 00:04:53,060 --> 00:04:57,120 So I'm just gonna do auth = basic_auth, for right now. 81 00:04:57,120 --> 00:04:59,650 Just so that I always have this thing called auth that's hanging around. 82 00:05:00,890 --> 00:05:05,920 All right, so let's go secure some of the review and course methods. 83 00:05:07,140 --> 00:05:14,010 Let's start in courses.py and up here I'm gonna do from auth import auth. 84 00:05:15,110 --> 00:05:18,240 Not the best import ever, but you know what? 85 00:05:18,240 --> 00:05:19,320 It works, it works. 86 00:05:20,322 --> 00:05:24,750 All right, so now I need to protect the things that I want protected. 87 00:05:24,750 --> 00:05:25,520 I think. 88 00:05:25,520 --> 00:05:26,730 Let's look at these one on one. 89 00:05:26,730 --> 00:05:29,940 I think getting a list of courses should be open. 90 00:05:31,420 --> 00:05:36,580 But I think that posting a new course should be protected. 91 00:05:36,580 --> 00:05:40,106 So @auth.login_required. 92 00:05:40,106 --> 00:05:42,585 Okay, so I'm gonna put that there. 93 00:05:42,585 --> 00:05:47,635 All right, and then I think that getting a single 94 00:05:47,635 --> 00:05:53,176 course should be fine but we should protect the put and 95 00:05:53,176 --> 00:05:58,120 we should probably protect the delete as well. 96 00:06:00,410 --> 00:06:05,610 And I would even go so far as to say like users can't delete these things, 97 00:06:05,610 --> 00:06:10,630 average everyday users we want this to be restricted just to admins or 98 00:06:10,630 --> 00:06:12,720 somebody with special privileges. 99 00:06:12,720 --> 00:06:15,310 Yeah, this week probably we wanna work on something else, but for 100 00:06:15,310 --> 00:06:16,330 right now that's fine. 101 00:06:17,360 --> 00:06:20,300 So let's go repeat some of that here in reviews. 102 00:06:20,300 --> 00:06:22,560 So from auth import auth. 103 00:06:24,080 --> 00:06:26,270 And then, on reviews. 104 00:06:26,270 --> 00:06:29,530 Again I think getting a list of reviews, should be fine. 105 00:06:29,530 --> 00:06:31,666 Posting in a review should be locked down. 106 00:06:34,916 --> 00:06:38,736 Getting an individual review should be open but updating a view. 107 00:06:41,676 --> 00:06:49,180 And deleting a review should both be protected. 108 00:06:49,180 --> 00:06:55,130 And, I actually think those should be protected in a new and different way. 109 00:06:55,130 --> 00:07:00,140 I think those should be protected to the point of not being able to edit or 110 00:07:00,140 --> 00:07:01,700 delete ones that you don't own. 111 00:07:02,810 --> 00:07:06,120 So the first thing we have to do is we have to import g 112 00:07:06,120 --> 00:07:10,580 because we have to be able to find out who the user is, right? 113 00:07:11,610 --> 00:07:18,940 So then, let's come down here and let's change our review-post. 114 00:07:18,940 --> 00:07:26,025 So, we're going to do the **args, but then we're also gonna do a new one. 115 00:07:26,025 --> 00:07:27,180 We're gonna do created_by. 116 00:07:27,180 --> 00:07:29,210 That's what we called it on the model, right. 117 00:07:29,210 --> 00:07:30,325 Let's double check. 118 00:07:30,325 --> 00:07:33,506 created_by. 119 00:07:33,506 --> 00:07:37,350 And we're gonna make that equal to g.user. 120 00:07:37,350 --> 00:07:39,560 So that way we know who created it. 121 00:07:39,560 --> 00:07:40,820 Okay. 122 00:07:40,820 --> 00:07:42,470 Then, we're gonna come down here to the put. 123 00:07:43,920 --> 00:07:48,550 And we need to do the review or 404 but I think we have to change that. 124 00:07:48,550 --> 00:07:50,110 I don't think we can do just review or 404. 125 00:07:50,110 --> 00:07:54,270 I think now we need to do something a bit more complex. 126 00:07:54,270 --> 00:07:59,002 So let's do review = models.review.select, 127 00:07:59,002 --> 00:08:03,010 .where Models.review. 128 00:08:03,010 --> 00:08:06,025 Created by is equal to g.user. 129 00:08:06,025 --> 00:08:09,509 And models.review.id is equal to the id that comes in, and 130 00:08:09,509 --> 00:08:12,870 then we'll close that where, and then we'll do a .get. 131 00:08:14,240 --> 00:08:22,310 And then we need an accept, models.Review.DoesNotExist. 132 00:08:22,310 --> 00:08:23,466 return make_response. 133 00:08:23,466 --> 00:08:27,240 json.dumps. 134 00:08:27,240 --> 00:08:35,350 Error that review does not exist or is not editable. 135 00:08:35,350 --> 00:08:36,460 So that's cool. 136 00:08:36,460 --> 00:08:37,070 There's that. 137 00:08:38,620 --> 00:08:44,800 And then we'll send that back with a 403 because that is a bad response. 138 00:08:44,800 --> 00:08:50,370 Then out here, we can get rid of that, and 139 00:08:50,370 --> 00:08:53,340 the rest of this is just like it was before. 140 00:08:55,330 --> 00:08:56,710 Yep. None of that has to change, 141 00:08:56,710 --> 00:09:00,290 but that means we need to import make_response, and 142 00:09:00,290 --> 00:09:02,300 we also need to import json. 143 00:09:02,300 --> 00:09:04,080 All right. 144 00:09:04,080 --> 00:09:08,556 So now, let's go do the same thing. 145 00:09:08,556 --> 00:09:10,050 In fact we can just copy this. 146 00:09:11,370 --> 00:09:13,860 Do the same thing right here in delete. 147 00:09:13,860 --> 00:09:18,510 So instead of that review there, we're gonna paste in 148 00:09:18,510 --> 00:09:23,900 this thing where we get the review that belongs to a particular user. 149 00:09:23,900 --> 00:09:28,580 So this way, you have to own the review to be able to edit or delete the review. 150 00:09:28,580 --> 00:09:30,010 I think that helps a lot. 151 00:09:31,360 --> 00:09:33,070 And then like I said on courses. 152 00:09:33,070 --> 00:09:36,950 I don't think we would lock down the stuff beyond, 153 00:09:36,950 --> 00:09:41,140 they're requiring a user unless again we wanted to make it to where it required 154 00:09:41,140 --> 00:09:42,930 a user with particular permissions. 155 00:09:44,480 --> 00:09:47,220 Now, this does have the downside of sending usernames and 156 00:09:47,220 --> 00:09:49,870 passwords in plain text across the Internet. 157 00:09:49,870 --> 00:09:54,340 This is when I remind you that you should always have an SSL certificate for 158 00:09:54,340 --> 00:09:56,070 anything that uses a login. 159 00:09:56,070 --> 00:09:58,750 And especially if there is money or personal details involved.