• last month
Transcript
00:00Alright guys, so in this lesson we're going to be setting up the configurations
00:03needed to govern the issuance and validation of our token or access tokens
00:10or JSON web tokens or JWT. All of those tend to mean the same thing. If you're
00:15not entirely sure of what an access token means and JWT, you can always go to
00:22JWT.io and you can read up on it. It is an open industry standard method of
00:28representing claims securely between two parties. So claims pretty much mean
00:33bits of information about who I am. And two parties generally would mean
00:39the server you're trying to access and the client that is trying to access said
00:43server. So in our situation, our mobile app would be our client and the server
00:48would be the other party. But then I, the user, I'm going to be going
00:55through the mobile app to try and access the API or what's on the server.
00:59So a JWT would have to be generated with claims or bits of information about
01:05who I am, which would be my username, my role in the system, various bits of
01:11information, but nothing too sensitive. Because if you scroll down, you see that
01:15you can actually decode what the JWT actually has in it, right? So this is what
01:20the string that gets generated will look like, but it's just an encoded
01:25string that has the bits of information that later tell us everything. So you
01:30don't want to put anything sensitive like username and password or anything
01:35that if I intercept a token, I could be able to see about your user and
01:39potentially use this information to carry out harmful acts, right? So use your, of
01:46course, let your context be a guide, but of course use your discretion when you
01:49are putting in information. Either way, we are going to be doing that together. So
01:54let us jump right into it. Our journey brings us to our app settings.json file.
02:00From here, I'm going to create a new section. I'm going to call it JWT
02:05settings. And all that will be in the settings will be a few configurations to
02:13help us with what we're doing, right? So we have one called the issuer. So this is
02:19typically the name of the app that is doing the issuing. So let's say car list
02:24API, that's the issuer, right? And then we have the audience. So who is the
02:30intended recipient of the token? So I can say MAUI app, right? Just for
02:38argument's sake, but you would probably want to make it a bit more generic if
02:42you have multiple clients, because you could have the MAUI app, you could have
02:46a Blazor app, you could have an Angular app, etc. So what would be the live
02:53span? So you can say duration or duration in minutes or lifetime,
02:58whatever. I'm going to be very specific, I'll say duration in minutes. Of course,
03:03you want to manage this because you don't want the token to expire too
03:06quickly. Because each time the token expires, the idea is that when the token
03:11is expired, the login session, quote unquote, is no longer valid, which is one
03:16of the checks that we'll be doing in our .NET MAUI app on the loading page. Maybe
03:21a token is present, but it's no longer valid because it has expired, right? So
03:26you always want to put in a value that makes sense. You don't want to ask
03:30somebody to log in every 15 minutes. I don't want to keep them logged in for a
03:34hundred days at a time, right? The next thing would be a key. So we have a
03:40special key that we'll be using for the encoding. Generally, you don't want this
03:45key to be accessed by anybody or external parties, because then they might
03:51be able to use the key to spoof tokens to get into your system. But for this
03:56example, I'm just going to keep it simple. It has to be some 16-digit, 16-
04:04character minimum long string that we're going to use. So I just say your secret
04:10encoding key, right? That meets the requirements, but you could use a GUID,
04:14you could use some special alphanumeric combination that is a secret to your
04:19organization, stuff like that. Alright, so we can go ahead and set up those JWT
04:26settings in our app.settings file. Sorry, app.settings.json file. And now we
04:33need to jump over to our program.cs file and let it know that it should have
04:39the JWT bearer authentication scheme, as well as let it know where it should be
04:46informing itself as to the rules that should govern. So after we add the
04:52identity core, I'm going to go ahead and say builder.services, and this time
04:58we're adding authentication. So add authentication. And this one, of course, is
05:07going to have the options, right? So we can just say options, lambda expression, and
05:12then set up our object section. And for this, we have first the options.default
05:21authentication scheme. The default authentication scheme here could easily
05:26be the text bearer. Personally, I don't like the magic string bearer because you
05:32don't want any spelling error there. So I actually prefer to use constants, and
05:36there are constants that are kind of built in that you can access. So the one
05:41that I use is JWT bearer defaults.authentication scheme, and that
05:47actually comes from a third-party, well, another library or an extension library
05:53that we need to install. So I'll go to manage NuGet packages, and we will
05:56browse and search just JWT bearer should be fine. And that will give us the
06:02Microsoft ASP.NET core authentication JWT bearer library. So you can go ahead
06:07and download and install that one via NuGet. And once we have that, we should
06:13be able to simply add a using statement and get rid of that error. So if I hover
06:19over this, you'll see that it's just a constant, and it does say the word
06:22bearer. So I just feel more comfortable with that, you know, fewer magic strings.
06:27The next one, which is going to take the same value, would be the default
06:33challenge scheme. So this is saying that I'll authenticate using the bearer token,
06:41but I will also expect that when I do a challenge, I will be challenging for or
06:49checking your authentication using the presence of the bearer token as well.
06:55So that's what we're telling our application to do, pretty much. Now after
06:59we have set that up, I'm going to now tack on additional code to add the JWT
07:07bearer, which is going to come with its own set of options as well. So options,
07:12and we do the same stuff that we've been doing. Then in here, we're going to define
07:18options.tokenValidationParameters. So now we're going to say what parameters
07:26should we use and pay attention to when we are going to be validating. So having
07:32challenged and checked and seen that, yes, we're using tokens, what are the
07:37parameters that we use to validate them? So I'm just going to fill this in quickly.
07:43All right. So here, we're basically defining the rules. Do we want to validate
07:47the issuer value? So we can say, yes, we want to validate the value that is in the
07:53issuer section. Okay. Who is a valid issuer? Well, we can go into our
07:58configuration, and then we can access from our configuration file, the JWT
08:04section, JWT settings section, and get the issuer value. So this is a nice,
08:10easy way to get into your configuration file, go into the parent object, and then
08:15get the key, and then it retrieves the value, right? So all this is saying is jump
08:20into configuration, go to JWT settings, go to issuer, and that value is a valid
08:28issuer, right? So any token that comes in that doesn't match this value is not a
08:35valid issuer. If I change this to false, that means it's not that serious. So I
08:40could easily put puppies in my token generation, and it would not care because
08:47it's not going to validate it, but I want it to validate it. Then we move on
08:51to audience. That could also be true. Now, if you have multiple audiences or you, I
08:56mean, if you have multiple audiences, the recommendation is that you make this a
09:00little less specific, right? So it wouldn't be MAUI app. It would be like
09:04car list API client or something like that. So that would be fine across
09:11multiple types of clients, but then I'm being specific here. So that means if
09:15somebody is trying to spoof a JWT and they don't know that my audience is
09:19specifically this value, we will once again validate it against that value, and
09:25if it is not accurate, then it will be rejected or seen as not valid,
09:33sorry. And then if we say false, then it doesn't matter. Then validate lifetime.
09:39Yes, we want to validate the lifetime of the token, right? So when we're generating
09:45the token, we will give it a lifetime, and this will make sure that it holds
09:52true that within the time frame that we said it should expire, it is still valid.
09:57If we say false, then people can use tokens from last year to do this year's
10:02business, which we definitely probably do not want. Clock skew here basically says
10:08that to zero. So if I'm not mistaken, if I fail to set the clock skew, you get
10:13something like a five to ten minute grace period with that expired token. So
10:18you might get a five minutes extra to use the token before it kicks you off
10:23the system, but by saying clock skew, time span zero or not zero or whatever value
10:30I put there, it will give you that much more or less time. So zero basically says
10:36you have zero seconds after the expiry time to continue to use the token.
10:42Then the final one that we're going to do together is the issuer signing key. So
10:48I did mention that we have that key, and that key is found in the builder.configuration.jwt
10:53settings key. But notice that this requires a security key, so
11:01that means we now need to encode this. So the next thing is encoding it, and
11:07we're going to encode it as bytes. So encode that JWT settings key
11:13value as bytes, but then that's still not enough. So we actually need to wrap it up
11:18as a new symmetric security key, and then by the end of that operation it is
11:24satisfied. So we have new symmetric security key, and then inside of that we
11:29have the encoding into bytes of the key value that we have specified in our
11:36settings. And then we close that, and oh I'm sorry, not there. We close this section
11:44and that's it for configuring authentication in our API. Now that we've
11:51configured authentication settings or the services that should govern it, I
11:56would want to add to the middleware that we should be using authentication. So I
12:02would say app.useAuthentication, and this will just draw that middleware will
12:08be informed by our configurations up top. Now let's discuss authorization. So
12:15authorization means are you allowed to do certain things, right? So if I wanted
12:21to lock down a particular endpoint, right, then I could just go to the end
12:28of that method, like for this map get, and then I could say .require
12:34authorization. What that does is say that anytime you're trying to hit this
12:39endpoint you have to be an authenticated user. All the others are free. So yes
12:43authentication is running, but this is how I tell you that you have to be
12:47authorized or authenticated in order to get here, right? And it can get a bit
12:53more complicated than that because this has overloads, and for the overloads as
13:00parameters you can add different authorized data, as well as different
13:05policies that would govern who is able to access, alright? So without getting too
13:11complicated, this is the default way to say you have to be logged in to access
13:15here. If I wanted this rule globally, however, because you might end up in a
13:21situation where you have 50-60 endpoints and you want all of them to be locked
13:25down by default, you wouldn't want to go through all of them and be putting that
13:29on every time. So what we could do is set up a global fallback policy. So right
13:36underneath where we added authentication, I'm going to do the same thing and I'm
13:42going to say builder.services.addAuthorization. So it's kind of
13:47helping me here, but that's not quite what I want. I want addAuthorization and
13:52when we do that we need our options as well. And what we're going to have is
13:58options.fallbackPolicy is equal to a new authorization policy builder
14:06object and this is just a builder. So for this builder, I'm just going to say
14:11that requireAuthenticatedUser and then build, alright? And that's really how
14:20simple it is. So once the app runs, you will see that it's fallback policy says
14:26you have to be authenticated and well anywhere else that doesn't specify it
14:33will just draw from that fallback policy. But we also add another middleware here
14:39which is useAuthorization and the order is very important. We use authentication
14:45then we use authorization because we need to make sure you are who you are
14:50then we check what you can do. We don't want to check what you can do before we
14:54see who you are because when we don't know who you are, you can't do anything
14:58right? So it have been unauthorized by default. We can further tighten this if
15:04we want by adding the authentication schemes that are allowed. So we could
15:12specify that we require, oh no I'm sorry I was writing the right thing initially
15:21and for for lost my train of thought. So we add authentication schemes and then
15:26the one that we would add in keeping with how we're doing our authorization
15:31or authentication rather is the bearer authentication scheme. Alright so now by
15:40default I shouldn't be able to hit any of my endpoints which then presents
15:44another problem in the sense that if I can't hit any of the the endpoints how
15:49am I going to log in since login is an endpoint. So whenever you want exceptions
15:54so in this case if we had 50 endpoints and five should be anonymously or you
16:01should be able to access five of the endpoints out of 50 with with anonymous
16:06credentials or anonymously then we can easily add this filter to the endpoints
16:14that says allow anonymous. Alright so whichever endpoint or endpoints that we
16:20want to allow unauthenticated users to access we just say allow anonymous which
16:26obviously would be our login and if you did registration then you would have
16:31that on the registration page as well. So you can choose which ones based on your
16:36context. So I'm just going to do a quick test and I'm not going to get into the
16:43code that generates the token just yet because that is a whole activity by
16:47itself but just to demonstrate that I cannot do anything so if I just simply
16:54try to get the cars it will tell me 401 I need to authenticate and see it's
17:01telling me the authentication needed is bearer. So we've successfully hardened
17:06our API if I try to log in and I try it out if I try using a username I
17:12shouldn't get a 401 well I will get a 401 but that's more because it
17:17couldn't validate me so if I try admin at localhost.com and that's the user
17:23that we just seeded and our password one and then execute then we actually get a
17:31200 right so our login endpoint is working successfully we got by the ID
17:37the username and we have the test token value coming back. So so far our efforts
17:44haven't been in vain now let us focus on making this token a real token that has
17:49real data that will be useful for our clients which contextually would be our
17:55.NET MAUI app