46 - Set Up Login Logic and Flow

  • 2 days ago
Transcript
00:00All right guys, so we're back in our.NET MAUI app,
00:03and now we need to outfit it with logic to handle going,
00:07fetching a token after a successful login,
00:10storing it, and then handling that whole flow.
00:14Let us start off by setting up
00:17our API access or extending the API access requirements.
00:23Here, we have endpoints to get the cars,
00:27we get the car, add the car,
00:29delete the car, so we need a new one that goes and logs in.
00:34I'm going to say public async task,
00:39and we're calling this one,
00:41or we're going to need it to return something.
00:43I'm going to call that AuthResponseModel.
00:47We'll create that as we go along.
00:50I'm going to call the method login,
00:52and it is going to take what I'm going to call a login model.
00:58These are two models that we are definitely,
01:01not the login view model now,
01:03not the login view model,
01:05just the login model.
01:08These two types need to be generated, of course.
01:14I'm just going to generate them in their own,
01:17generate type login model in new file,
01:21and I'm going to do the same thing for
01:22the AuthResponse generate in its own file.
01:26Then I'm going to move them over to
01:29the folder that I know they should be in, which is models.
01:35I guess since we've already generated them,
01:39let us modify them to what they need to be.
01:44The AuthResponseModel, I know that it should have
01:49a property for the string user ID,
01:53because that is going to come back with that AuthResponse.
01:56We should also have one for, what was it?
01:58The e-mail, if I'm not mistaken,
02:02the user ID, and I think it was username actually.
02:06Remember that we do have the API project right there.
02:09If we don't, then we can always double-check with
02:11the API development team as to what the fields are.
02:16But yeah, I'm actually just
02:18going to keep shortcuts and borrow all three.
02:21We know we have a user ID,
02:23username, and token.
02:25Then for the login model,
02:28well, all we really need from that would be the string,
02:32username, and password,
02:36because that's what is going to go over,
02:39and that's what will be ingested in the form of the login DTO.
02:46Login DTO is expecting fields called username and password.
02:52Those are fine. Now that we have these two data types,
02:57we can continue and flesh out what happens in the login method.
03:04I'm going to do the same try-catch
03:06following how we've been writing the code up until now.
03:10When we catch, I'm going to set the status message
03:14to be failed to login successfully.
03:18We could shoot that back over to
03:21the login page just in case of anything.
03:24But in our try,
03:27what I want to get is a response.
03:29I'm going to say var response is equal to await,
03:33and then we call our HTTP client,
03:36which we want to do a post as JSON async to our login endpoint.
03:46The data that we are sending over is coming from our login model object.
03:52Then we do the same thing where we ensure that we got
03:56a successful status code because if we get back a 401,
03:59then obviously, you fail to log in.
04:01If you get back a 500,
04:03we'll just say yes, you fail to log in.
04:05Otherwise, we can set the status message to be equal to login successful.
04:15I'm going to return and JSONConvert what came in.
04:24JSONConvert.deserialize object into the type of auth response model.
04:35Then of course, we're going to await
04:40the read as async of what came back in the response.
04:46Nothing too far from what we've been doing,
04:49especially like with the post.
04:50It's a post method we're going to send over,
04:53but then it's a combination of the post and the get
04:55because we're doing the same post as JSON async,
04:59but we're expecting something to come back,
05:02which is what we did in the get where we
05:04returned or got the response and parsed it.
05:08Well, probably I'm getting a little carried away by
05:12doing the response.content.readAsStringAsync,
05:16but hey, that's just another way you can do it,
05:19and that's all we're going to return.
05:22We should get back that auth response model.
05:26While I'm here though,
05:27I am going to create
05:30another method that will help us
05:32going forward whenever we need to make our calls.
05:36This is going to be a public async task.
05:38I want to say set auth token.
05:41Now, the reason for this one is,
05:45remember that everything is now locked down.
05:48The only endpoint that's not locked down is login.
05:51That means I can't get the cars,
05:53I can't do anything without the auth tokens.
05:55I need to set the auth token on
05:58the HTTP client before this request goes over.
06:02I'm just going to create a method that does
06:05that one time so I'm going to go and get the token,
06:07which we will be storing in a place called
06:10secured storage or secure storage rather.
06:15I'm just going to do that same get,
06:18and that's getAsync, so I have to await that.
06:21We're looking for whatever has the key token.
06:26SecureStorage, this is not the first time you're seeing it,
06:29but SecureStorage is like a key-value-pair
06:31storage system that is secure in the phone.
06:34You can store a little bits of
06:36information in that area as you go along.
06:40But now that I've retrieved it,
06:42I need to add it to the client.
06:44HTTP client.defaultRequestHeaders.authorization,
06:51and that's going to be equal to a new authorization.
06:55There we go, header value.
06:59I'm going to use the magic string here.
07:02The magic string is bearer,
07:05so I'm saying I'm adding an authentication header value
07:08of bearer and the value is the token,
07:13and it will formulate how that request header looks.
07:18I can simply call setAuthToken above the call.
07:28I'll just do that above the getCars so that when we do
07:32the login and stuff at least the getCars method should work,
07:35and if not, then we can refine it.
07:38That's what I'm doing for the Car API service.
07:43Now that the Car API service has its login method,
07:46let's jump back over to our login view model and tidy up
07:49what needs to happen after we try to log in.
07:53Here, we attempt to call the API.
07:56That's what I had said.
07:57Firstly, we need to compile the login model.
08:01Login model should be equal to a new object of
08:05login model as is needed by the method.
08:15Let me set the values here.
08:18Login model will take username and password.
08:22What I'm going to do is just set a constructor
08:25here to allow us to simplify
08:28and by extension enforce certain things.
08:32This constructor will enforce that it needs a username and password.
08:38That should simplify how we declare.
08:41Let's try this again.
08:42Login model, and then I'm just going to
08:45take username and password and pass that in.
08:48Nice and simple. Now that we have our login model initialized,
08:54we can now save our response.
08:57We get that spelling right.
08:59Response is equal to await car API service,
09:04which I do not have.
09:06What do I need to do? If you said inject it,
09:08then you're absolutely right.
09:09I need my constructor,
09:12and this constructor is going to inject car API service.
09:18There we go. Add the missing using statements.
09:23Now that I have my car API service and I need that field generated for it.
09:30Now that I have my car API service,
09:32I can await car API service.login and give it the login model.
09:38Then I'm going to say if response.
09:42What happens is that I'm returning defaults for the response.
09:46It may not be null,
09:48but what I can do is make sure that the token is present.
09:53I can say if string.isNullOrEmpty for the token.
10:02If it is not null or empty,
10:04and there is a token value there,
10:07then we know that we have logged in.
10:10At any rate, what I can do is display an error.
10:16Well, I'm already displaying the alert here.
10:20We can refactor this a bit and instead,
10:26I can display login attempt results.
10:35I'm just refactoring this as I go along.
10:38Then what I'll do is display a message here.
10:42Then this will take a string message parameter.
10:49The string message could be,
10:53so if it fails,
10:56I can just say invalid login attempt here.
11:02Of course, you could put that as
11:03a constant string somewhere and just reuse it.
11:06But I'm just trying to not agitate the process as much as possible.
11:12But the reason I'm doing that is I would want to display,
11:16let me say display login message instead of login error,
11:24because it might not necessarily be an error.
11:27We're only displaying alert.
11:32I'll leave it as is,
11:34display alert, display login message.
11:38Here, after we get the response,
11:41I'm going to display what the status message is.
11:46Car API service.statusMessage.
11:50Because remember, we're setting the status message.
11:54Whatever the status message is that got set,
11:57we will display that.
11:59Then we're going to check if the token.
12:01That was a little segue, I'm sorry about that.
12:03Go off, get the login results,
12:07display whatever status message was set as a result.
12:10But then we come down here and check,
12:12is the token present?
12:14That now would be this if statement,
12:17if login was successful.
12:19Let's just move these comments out and reduce all of the noise.
12:24What do we do if it is not null or empty?
12:29If it is not null or empty,
12:32I want to store the token.
12:36I didn't put that comment there.
12:38Store token in secure storage.
12:43Based on how you've seen me use it,
12:46you should be able to deduce that it's simple enough to just say,
12:49I wait, get the secure storage dot,
12:52and you can set and get.
12:53If we set, then it takes two parameters.
12:56It takes the key, which I'm using the word token.
12:59Then we can set the response.token as the value.
13:03That's it for storing the token really.
13:06We can also do other things.
13:09We can extract the token and try to look in it and see what's happening.
13:13But we don't have to necessarily go through all of that because
13:16our auth response here has enough information.
13:19It has the username,
13:20which I'm going to use to display something on the menu.
13:25The next thing that we want to do,
13:27I mean, we did display the welcome message.
13:29This would be the welcome or not message.
13:33I want to say display message here.
13:37The next thing that I want to do is build out
13:40the menu that I want the users to see.
13:42Here we do need to actually interrogate the token so we
13:46can get what role the user is in and so on.
13:50To do that, let's jump over to managing your packages for.NET MAUI app.
13:56Then we're going to see that we need this library called JWT.
14:02I was just on the system.identitymodel.tokens.jwt.
14:06Go ahead and browse for that one and install,
14:11of course, the latest version accordingly.
14:13Now, once you have that installed,
14:15you get access to this class that we know from our API project,
14:22which is our JWT security token handler class,
14:25and then that allows us to read the token.
14:29If I'm not mistaken, I could actually have done that in one line.
14:34There we go. I can just say token var jsonToken is
14:44equal to new JWT security token handler.readToken,
14:49and it will pass in that token coming in from
14:53our response and we're parsing it as JWT security token.
14:59Now, with this JSON token,
15:03I can actually go through and look at
15:06each property as plain as it is a C-sharp class.
15:12I can even look at the valid dates,
15:15everything right here,
15:16and interact with it in standard code.
15:19Now that we know that we can access rather details of the token,
15:26what I want to do is set up
15:28a backend preferences or a backend object for the app.
15:33So that we can have access to certain things like
15:35the username and anything like the role.
15:39If during runtime we have decisions to make based on the role,
15:42we need a central place where we can access that.
15:45I'm going to create a new model,
15:50and I'm going to call that one UserInfo.
15:55New class, and we call that UserInfo,
16:00and it is going to just be a point of reference for
16:06everything about the authenticated user that we think is
16:10important to the runtime of the apps.
16:14I'm just refactoring a bit,
16:17and then of course, this needs to be public.
16:19What would I want to know?
16:21I would probably want to know their username or e-mail,
16:26something from the token.
16:28Let's say we want the e-mail address.
16:30We'd probably also want to know which role they're in,
16:33so I'm going to say role.
16:35As many things as you may need to store,
16:39you can go ahead and store inside of this.
16:43Now, we have the model.
16:46Where do I use this model?
16:48I'm going to add this model to the app.
16:52Go to app.xaml.xml.cs,
16:56and remember that around here,
16:57we could add a static property that we could access anywhere.
17:01We did that with the database service,
17:03but we're going to do something similar here.
17:05The database is actually somewhere you
17:08could end up storing something like this,
17:10and you call it each time.
17:11But I mean, there are so many ways to do this thing.
17:13I'm not saying one way is right or wrong.
17:16But what I'm going to do is say public static UserInfo,
17:20and we're just going to call it UserInfo here.
17:23Of course, we add any missing using references.
17:27Now that we have this static reference to our UserInfo,
17:33we can actually, well,
17:36one, use it anywhere we need it,
17:38and two, start assigning stuff to it.
17:40Now that it's in the app,
17:42if I go back to the login view model,
17:44I can start filling it out.
17:47I can firstly create an object of type UserInfo.
17:52I'm going to say var UserInfo
17:55is equal to a new instance of UserInfo.
17:58Now that I have it, I can start filling it out.
18:01I'll probably just do that like this.
18:04When I start assigning values, I have a mismatch here.
18:07I said email, that should have been username.
18:09Apologies. Let me just go back over and update it
18:12so it can remain consistent, do the refactoring.
18:16We assign the username that the user would have used to sign in.
18:21Whether it's the property version or the field version of it,
18:25it's the same value, and then role.
18:27I want to assign the role.
18:29How do I get the role?
18:30Well, a role is a claim,
18:32and the claims are all in the JSON token.
18:35If I wanted to get the role from the token,
18:38I can say var role is equal to JSONToken.claims,
18:45because that is a list.
18:47That means I have to use link node to query,
18:52or I could use the where,
18:53but I think I'll just use first or default,
18:57and then go in there and find where the type,
19:04q.type.equals, so find the type that equals claimTypes.role.
19:15We can use the same claimTypes constant to get that string.
19:20Then after I do all of this,
19:24I'm just going to do question mark just in case it's null,
19:27but when I say dot,
19:29I can then get the value.
19:33All of this, in short,
19:36is saying go into the list and get me
19:38the first claim of type role and get me that value.
19:44Then that's the role. Now,
19:46I know the role that the user is in.
19:48That's when interrogating that token comes into handy,
19:54because once the user has logged in,
19:55you want to be able to interrogate
19:57the token and make a decision accordingly.
20:00Now that we have this user info set,
20:03I'm going to show you another form of storage that you can use.
20:06You can look for preferences.
20:09Every type of mobile phone has preferences,
20:13and you can add preferences.
20:16I'm going to say, does
20:18the preferences or set of preferences contain this key?
20:23I'm going to name the key name
20:27of and the name of the class,
20:32which is UserInfo.
20:34I would say name of UserInfo.
20:38If it does not contain it or rather,
20:45if it does contain it, remove it. Let's do that.
20:47Preferences.remove, the same key.
20:55Then we can go on to actually store the value.
21:00What I tend to do is serialize the values.
21:04This is the object,
21:05but preferences is a key-value pair storage,
21:09similar to secured storage,
21:11where it takes two strings.
21:13String key, string value.
21:15Preferences would be the same thing
21:17where it takes a key and a value.
21:19The value here, I can easily serialize into the form of JSON,
21:26and then put that inside of
21:27the preferences and then call on it if I need to.
21:30I'm just giving you some ideas,
21:32but I'm not going to go to preferences route.
21:35I'll just keep it simple.
21:36I know I'm going to assign the app.UserInfo,
21:40which is the globally accessible UserInfo instance
21:44that I wish to use.
21:45I'm going to give it this newly instantiated UserInfo.
21:49Just looking at it, I could have easily just done this.
21:54Fewer lines of code.
21:58The next thing I want to do is create
22:01some indicator that you have logged in.
22:05Well, we do need to navigate to the main page for sure.
22:09What I'll do in this part is await the shell current,
22:15go to async, and then we'll go to the main page.
22:18I'm also going to introduce an else here
22:21so that we don't see this message.
22:23Because what I've noticed is that it will navigate away,
22:27and then it will continue to run.
22:28You'll see the navigation happen,
22:30and then you'll still see these messages.
22:32That's why I end up with these else's
22:34and a lot of nested if statements,
22:37just to make sure that it doesn't run on
22:40when you navigate away.
22:45I think we have done enough to solidify the login process.
22:52For the loading page view,
22:56make sure that it looks something like this.
22:59We have our token.
23:00We did all of this part already,
23:02but we didn't do the else.
23:03So what we'll do is grab the token
23:07if it is already there.
23:08So remember, we're checking if there's a token,
23:10if not, go to login page.
23:13But then if it gets this far,
23:14then there is a token.
23:16Now, after we parse it out,
23:17we're going to check the valid to value.
23:20So this is the end date,
23:21the expires datetime stamp.
23:24So it's a json.valid to,
23:26and then datetime utc no.
23:28And if that is true,
23:31then we remove the token from secure storage,
23:34and then we go to the login page.
23:36So it's either there is no token,
23:38or there's an expired token,
23:39at which point we remove it,
23:40go to the login page.
23:42Otherwise, we go straight to the main page.
23:45All right.
23:46So that means if the token is present,
23:49if you logged in five minutes ago,
23:50based on our parameters,
23:52and you log in again,
23:54then within the five minutes,
23:56you shouldn't have to go to the login screen again.
23:59All right, guys.
24:00So let us run our app and try to log in.
24:04So as soon as it comes up,
24:07all right,
24:08and of course,
24:09we put in our credentials,
24:11admin at localhost.com,
24:16password one,
24:18and we log in,
24:20and then we get our success message.
24:21So what happened here?
24:22It said login successful.
24:25This message is coming courtesy of
24:28our status message that got set
24:31in our login method.
24:32Right.
24:33So we ensured it was successful.
24:36We set the status message,
24:37and we returned the token response object.
24:41Right.
24:41So that means if I set a break point
24:44on this if statement,
24:46and I click OK,
24:48that means if I interrogate this response object,
24:51I'm going to see that token,
24:52as well as any other information that came over
24:55from the API as a result
24:57of the login operation.
24:59So I'm going to remove this break point,
25:00and I'm just going to continue.
25:03And the expectation is that by the time
25:06it finishes everything,
25:07it should now show the main page
25:09so we can continue.
25:11And there we go.
25:12We have the main page.
25:13Now notice that the get method
25:16is working properly.
25:17Why is it working properly?
25:19It's something that we did from when
25:21we're setting up our login endpoint.
25:23We set the auth token.
25:26So set auth token meant
25:28that we went into the secured storage,
25:31got the token,
25:32set it as the default header.
25:35Right.
25:36And then we added it to the top
25:39of our get cars operation.
25:44So now,
25:45because we're using a single instance
25:47of everything that HTTP client
25:49is a single instance,
25:51just by setting the auth token
25:53at the top of get cars
25:55has actually set it
25:56for every subsequent request
25:58using the same HTTP client object.
26:02Based on how we built the application,
26:04that's fine.
26:05It may not always be like that.
26:07Generally speaking,
26:08you would use something more transient
26:10or HTTP clients tend
26:11to be a bit more transient.
26:13So you would actually have to set
26:15that auth token every time
26:17you're about to make an API call.
26:18But for now,
26:19we have one API,
26:20one client, and it's fine.
26:22And that's pretty much it.
26:23So now we see that we can actually
26:25get to the main page
26:27after logging in.
26:29Right.
26:30And if you want,
26:31you can actually test it
26:32with bad credentials.
26:33Try to sign in
26:35and we'll get that exception 401.
26:39No problem.
26:40And then we continue.
26:41It failed,
26:43failed to log in successfully.
26:44We can, of course,
26:46tidy up those messages
26:47and a little exception
26:50which we can investigate
26:52to see what the cause of that is.
26:54But notwithstanding all of that.
26:57And I believe that that null exception
26:59is because I'm actually
27:01doing this check here
27:03because I'm trying to check
27:04if the response that token.
27:06But if this is null,
27:08then there is no token attached.
27:11But either way,
27:12our login is working.
27:13We can always tweak.
27:14But next time,
27:16sorry, in the next lesson,
27:17we're going to add a few more things
27:19because what we want to do
27:20is have specific menu items being added.
27:24And I want to simulate
27:25what a user would see
27:26versus what an administrator would see,
27:29as well as putting some headers
27:32at the top of the app.
27:34Once you have authenticated,
27:35you know, something to say,
27:36hi, your name and a logo button.
27:39So we have all of those tweaks
27:40that we want to get through.
27:42But right now we have
27:43our login working.