Adding SecureStore Persistence0:00
Okay, let's continue our auth setup and work on adding SecureStore, as well as our backend with Laravel Sanctum. We need to add persistence, and SecureStore is a good place to do that. So SecureStore is similar to local storage in a browser. It allows you to store key-value pairs. But as the name implies, SecureStore is secure and does encrypt your values. So this makes it a great place to store things like auth tokens. Also note that the size limit is 2048 bytes. So make sure the things you store aren't too large. So let's go ahead and install this.
So make sure the things you store aren't too large. So let's go ahead and install this. And we'll work on persisting what we currently have here. So let's expo install expo-secure-store in our application here. And let me start up my server again. And like I showed you in the last video, we're not persisting anything yet. So if I log in, we do get our app screens. But if I reload the app, nothing is persisted. So we're back to the login screen. So once we make use of SecureStore and persist our token, or our user object, then once we're
So we're back to the login screen. So once we make use of SecureStore and persist our token, or our user object, then once we're in the app screen and reload the app, it should still remain in the app screen. Okay, so the API is very similar to local storage. So let's go ahead and set that up. So in our auth provider, right here, when we're setting the user, let's also persist this to SecureStore. So in this case, we want to set a key. So we'll do SecureStore. And the method for setting a key is setItemAsync.
So we'll do SecureStore. And the method for setting a key is setItemAsync. And the first parameter is the key, we'll name it user, and the second parameter is the value. So in this case, we'll just hard code Andre for now. And for logging out, we want to make sure to delete this user key. So same thing, but it's deleteItemAsync. And we just need the key here. Okay. And it is a promise.
Okay. And it is a promise. But in this case, we don't need to do anything with that promise. So let's make sure to import it correctly. We can import it like this. Let's grab this, paste this into our application here for the imports. Okay. So let's save that. And in our root JSX, up here, where we have this comment, it says check SecureStore for the user object.
And in our root JSX, up here, where we have this comment, it says check SecureStore for the user object. Let's go ahead and do that. So we can do... Actually, let's add the import first. Okay. And now we can get rid of this setTimeout. And to check the SecureStore, we can do SecureStore.getItemAsync. Okay. And the examples in the docs use async await, but I'll just stick to promise syntax here.
Okay. And the examples in the docs use async await, but I'll just stick to promise syntax here. So we're going to get the user key or check for the user key. And then if it exists, then we get this userString. So let's go ahead and use that. And let's check if it exists. So if userString, then go ahead and set the user. So remember, we have the setUser coming from our auth context. So we can set the user here. And in this case, we're just hard coding Andre.
So we can set the user here. And in this case, we're just hard coding Andre. And let's make sure to setIsLoading to false after that. Okay. And we can just do something generic for the error case. Catch the error, and it's just console.log the error. And again, setIsLoading to false. Okay. So that should be it in terms of using SecureStore. So let me save that.
Setting Up Sanctum Tokens4:07
And we are still in our app screens. Now if I log out, that should be the same. And it should destroy the key in SecureStore. And that should bring us back to our login screen. So let's log out here. And it works as before. Now we can start incorporating the backend. And for that, we'll make use of Laravel Sanctum. So as of the latest versions in Laravel, Sanctum is already installed. So if you take a look at our backend here and go to composer.json, you'll see that Sanctum
So as of the latest versions in Laravel, Sanctum is already installed. So if you take a look at our backend here and go to composer.json, you'll see that Sanctum is already installed here. And if you take a look at the routes/api.php, the route that came with it, you'll see this middleware called auth:sanctum. So let's go ahead and make use of Sanctum to generate tokens for our users. So let's go into the documentation. And first, we need to issue API tokens, or at least just add the trait for HasApiTokens. So let's do that first in our User model. So it's already here.
So let's do that first in our User model. So it's already here. We just have to use it, use, or sorry, hasApiTokens. Okay. And now we can go to the mobile op section. And there's actually code here that allows us to log in. So it validates the email and the password and also a deviceName. So if you're logging in through multiple clients, you can see which device it was logged in from. Or if you don't need this, you can just hard code it in the backend.
from. Or if you don't need this, you can just hard code it in the backend. We're checking if this user exists. If they don't, then throw some errors. But if they do, then go ahead and create a token with this device name. So let's do that. Let's grab all of this. Let's paste this into our app or in our routes/api for now. So we'll put it after this. Okay.
So we'll put it after this. Okay. And I'm going to change the endpoint to login. Okay. Let's make sure to import these. So Hash and ValidationException, I believe it's this one. And let's go ahead and see if this works. So let me save this. Let me just add a User in my Seeder here so I can use that User to test. So I'm just going to say User::factory(), create(), and I'm just going to create a User.
Let me just add a User in my seeder here so I can use that User to test. So I'm just going to say userFactory, create, and I'm just going to create a User. Email is me. Okay. And it's the same name as well. Okay. So let's go ahead and migrate this. So this is my backend, php artisan migrate --fresh --seed. Let me make sure to import User here. Let's try that again.
Let me make sure to import User here. Let's try that again. Okay. So we now have that User and I'm going to go into my REST client and try this out. So let's duplicate this endpoint here and let's call it login, create. It is a POST request. The endpoint is /api/login. Okay. And we are accepting some data here. So multipart/form is fine.
And we are accepting some data here. So multi-part form is fine. And what do we need? We need email, password, and the device name. Yeah. Okay. So email, let's add that password. Let me just do an incorrect one for now. And the device name, device name, let's just say mobile. And let's try hitting this endpoint.
Cool. So this doesn't seem to be in JSON format. So let's change what it returns here. So let's just store this in a variable. This is the token coming back and let's return response()->json() and let's return the token. And that's going to be that variable. And let's also return the User that's logging in, but only a few fields. And we'll store this in secureStore as well. So let's say user only. Let's grab the ID, their name, their username, grab their email, and maybe their avatar.
So let's say User only. Let's grab the id, their name, their username, grab their email, and maybe their avatar. Okay. Let's try this out. Actually, the second parameter should be the response. We can say 200 or 201 is for created, and we are creating a token here. So let's say 201. Okay. So let's try this in my REST client again. Let's go ahead and reload this.
So let's try this in my REST client again. Let's go ahead and reload this. And now we're getting a JSON response with the token and the user information. So now if we were to grab this token here, and if you wanted to hit any protected endpoint with the auth:sanctum middleware, then we'll have to pass in this token as a header. So let's try that out. Let me just move this up here. Let's duplicate this. Let's call it protectedEndpoint. Let's create.
Let's call it protected endpoint. Let's create. It's going to be a GET request to /api/user. And this is the one that comes by default. So this one up here, right here, you can see the auth:sanctum middleware, and all it does is return the logged in user. But we don't need any of this to GET request. And if we try this without the header being passed through, we do get unauthenticated. Okay. But if we do pass in the correct header, or in this case, it's a bearer token, we can say
Okay. But if we do pass in the correct header, or in this case, it's a bearer token, we can say off and pass in a bearer token here. Let's paste in that token. And if I did this correctly, this should return the user information, because that's all we're doing here. Return request()->user(). And it does work. Cool. And again, if this token is incorrect, let me just add a character here and try again,
Cool. And again, if this token is incorrect, let me just add a character here and try again, we should get unauthenticated. Awesome. And let me just put that back. And you can also see the tokens that are created in the database here. So if you go to personal access tokens, there are the two tokens that we created. So here is where the device name is stored in this name column here. So now for logging out, we want to make sure that we delete this specific token. So let's go ahead and do that.
Creating Logout Endpoint10:55
So now for logging out, we want to make sure that we delete this specific token. So let's go ahead and do that. Back to our code here. Let's just grab this. Log in. Now, if I were using a login starter, like Jetstream or Breeze, then you can make use of the logout function there. But I'm not using that here. So we'll just do it manually. All we have to do is delete the token.
So we'll just do it manually. All we have to do is delete the token. So let's get rid of all of this. Let's change the endpoint to log out. Okay. And we can grab the User or the logged in user using the request, or you can use the auth facade as well. But the request is fine. So request user like this. And there's a method on the User called currentAccessToken.
So request User like this. And there's a method on the User called currentAccessToken. And this will grab the token that they're using. And then we can just delete it. Okay. And we can just return response()->json(). And you can put whatever you want in here, let's say loggedOut 200. Okay. So let me save this. Let's go back to our REST client.
So let me save this. Let's go back to our REST client. Let's make sure to grab this token again. The endpoint is logout. Actually, we have to make sure to wrap this in middleware. We need access to the current User. So let's say middleware. And it's auth:sanctum, just like the one above. Okay. And we'll clean this up later on and make a route group for all the protected routes.
Okay. And we'll clean this up later on and make a route group for all the protected routes. That looks good. Back to Insomnia API logout. It's a POST request. Let's try an invalid token first. So let's add a character here. Okay. And let's try the correct token. And hopefully this deletes one of the tokens in our database.
Wiring Axios Auth Flow12:47
And let's try the correct token. And hopefully this deletes one of the tokens in our database. So let's try that. Okay, we get the response here logged out. And let's refresh this. And that one is deleted, which means we are logged out. Let me just delete this one as well. Okay, now let's work on the client side. So back to our React Native app here and back to our authProvider. Let's go ahead and make use of Axios here.
So back to our React Native app here and back to our auth provider. Let's go ahead and make use of Axios here. So I'm just going to paste in two more pieces of state that we need. One for the loading state and one for the error message. So let me just paste that in. We have isLoading and error. And loading is set to false by default. And error is null. Now for our login, let's go ahead and delete this comment. Set isLoading to true at this point.
Now for our login, let's go ahead and delete this comment. Set isLoading to true at this point. And let's go ahead and make a request to our backend. So we can say axios-config. And it's a POST request to /login. And let's make sure to import axios-config. And that doesn't work. So let me just grab it from one of the other screens. Okay, let's grab this. And let's paste this in here, up here somewhere.
Okay, let's grab this. And let's paste this in here, up here somewhere. Okay, so we're making a POST request to /login. We have to pass in the email and the password. So let's do that. So the email is email, or we can just leave it out since it's the same name. password is password. And we also need that deviceName. Again, you can just hard code this in the backend if it's the same all the time. But in this case, we'll hard code it from the front end.
Again, you can just hard code this in the backend if it's the same all the time. But in this case, we'll hard code it from the front end. Say mobile, okay. And then we're going to get a response. So let's say response, okay. And if you remember from our REST client, the response is going to be the token and the user. So let's make a new object here. So let's say const userResponse. And let's just add all the fields we want to store in our secure store here.
So let's say const userResponse. And let's just add all the fields we want to store in our secure store here. For example, the ID will be response.data.user.id. The name is pretty much the same thing, but name. The token is just going to be response.data.token. Or you can destructure response.data if you want. What else do we have in our backend here? So yeah, let me just add the rest. So username, email, and avatar. And I'm also going to put the token first.
So username, email, and avatar. And I'm also going to put the token first. Okay, so I added the rest of the fields here. And now instead of setting the user to a string, we can set the user to this object here. So we'll say userResponse. Let's also set the error to null in case we do this multiple times. Okay. And let's make sure to store this within our secureStore. And secureStore only supports strings, just like localStorage. So we have to make sure to JSON.stringify the userResponse.
And secure store only supports strings, just like local storage. So we have to make sure to JSON.stringify the userResponse. Okay. And let's also set isLoading to false somewhere here. Let's just put it right after here. false. Okay. I'm sorry, I put all this stuff outside of the then. I want that to be inside. Okay.
I want that to be inside. Okay. And then within here, we can catch the error. And again, we can set isLoading to false. And we can also set the error. Let's see what we get when we have an error. So for login, let me just put the incorrect password. And this message is fine for now. But later on, I'll change it to this more specific error here. So for now, it's just going to be error.response.data.message.
But later on, I'll change it to this more specific error here. So for now, it's just going to be error.response.data.message. Okay. And we'll display it somewhere on the screen here. Let's just console.log that for now, actually. Okay, let me just save this to make sure I have no errors. Okay. And let's do something similar for logout. So let's grab this whole thing. The whole Axios call.
So let's grab this whole thing. The whole Axios call. And also the setIsLoading. And let's paste that here. Okay, so we're posting to the logout route. We don't need any params here. Actually, if you want to follow strict REST rules, you can make the logout function a DELETE request because we are deleting a token. But POST is fine as well.
because we are deleting a token. But Post is fine as well. So we'll just leave it. So we don't need all of this stuff. So we do want to set the user to null if it's successful. So that means we delete the token on the server. Okay, that's fine. And we want to delete the item in secure store. So let's paste this in. And we can get rid of this here.
So let's paste this in. And we can get rid of this here. And set isLoading false as well. Okay. So this looks fine. Let me save this to make sure it's correct. Okay. But remember, when we're calling logout, we have to pass in the bearerToken as well. So again, for logout, where is it here?
we have to pass in the bearer token as well. So again, for logout, where is it here? Is this the logout route? Yeah, it is. So we're passing in the bearer token as well as a header. So to do that within Axios, we can add it as a header before we call it. So we can do axios.config.defaults.headers.common. And the header that we're setting is authorization.
headers.common. And the header that we're setting is authorization. Authorization, okay. And the value is bearer. And then the token. So it's bearer space, the user's token. And remember, we're grabbing the user information from our auth provider. And we did store it as a token here. So it should be user.token.
And we did store it as a token here. So it should be user.token. And we're grabbing the user up here. Okay, so let's try this out. User.token right here. User.token, okay. Let's save that. And in our value prop, where we're exposing all the variables to all of our screens up here,
where we're exposing all the variables to all of our screens up here, let's also expose the error since we need this in our login screen. Okay. And we're also gonna need the isLoading state for our other screens, okay. Okay, so I'm gonna save this. It's a lot of code. We didn't test anything out yet.
Displaying Loading and Errors20:57
Login screen. And let's just put it somewhere underneath here. So I'm just gonna paste it in because this video is getting quite long. Let's just make sure to grab the error and the isLoading state from our context. error is loading. And I'm just gonna paste in the ActivityIndicator and the error as well. So we'll paste that in here.
and the error as well. So we'll paste that in here. So if there's an error, then go ahead and display the error here with a red color. And if we're loading, then just load an activity indicator here. Okay. Make sure to import activity indicator. Let's go ahead and simulate some lag on the backend.
Make sure to import activityIndicator. Let's go ahead and simulate some lag on the backend. Actually, let's try the error first. So let's just type in anything here. Incorrect password. There's the error. And let's simulate some lag on our login endpoint. sleep(2). Let's try that again. There you can see the activityIndicator.
Let's try that again. There you can see the activity indicator. So that works. Let me just delete this. And I'm gonna log in one more time and just make sure that we can hit this endpoint here where we're just returning the user's information. So let's go ahead and log in here with the correct user. Okay. Log in.
Testing Protected User Endpoint22:13
Okay. Log in. So let's just do this on an empty screen. So we'll do it on the search screen. So back to our front end, let's go to our search screen. Let's just grab the Axios import from one of our screens here. Copy that, paste that in here. And I'm not even gonna make a piece of state.
Copy that, paste that in here. And I'm not even gonna make a piece of state. I'm just gonna console.log whatever comes back from the backend. So we'll make use of useEffect here. And we'll call that endpoint. Let's make sure to make this empty. Okay. Let's go back to our AuthProvider and just grab the axios call from here.
Let's go back to our auth provider and just grab the Axios call from here. So let's just grab the one from log out here. Don't even care about the isLoading state. Let's grab this one here. Actually, we need this as well, which sets the bearerToken. So let's grab all of this. Let's go back to our search screen. Let's paste that in.
Let's go back to our search screen. Let's paste that in. We need to grab the user from our context. So let's grab that as well. We can grab that from the root like this. Let's put that in here. Let's make sure to grab a useContext and authContext. Okay. We don't need setUser. So we are making a GET request to /user.
We don't need setUser. So we are making a GET request to /user. And I just want to get rid of this. Let's just console.log the response. response.data. console.log response.data. Just to make sure that everything is correct in terms of our auth setup. So save that. Does it load here?
So save that. Does it load here? And it looks like it does. Cool. Again, this is coming from the server from this endpoint here. Not to be confused with what's in our secure store. So if I were to comment all of this out, we can just grab what's in the secure store, which is also within our global user.
we can just grab what's in the secure store, which is also within our global user. So we can just console.log user.avatar or any of the fields we stored in our global variable. Save that. I should run again. And there it is right there. Cool. So yeah, we added secure store to persist some of our data as global variables.
So yeah, we added secureStore to persist some of our data as global variables. And we also added Laravel Sanctum for the backend so we can generate tokens for our users. We then wired everything up with Axios. And as you see, everything is working correctly. And we also have some error validation showing in the frontend. So let's go ahead and stop this. Let's make another commit.
Committing Changes and Fixes24:52
So let's go ahead and stop this. Let's make another commit. So we have commits on both the frontend and backend. So for this, let's say git add, git commit -m "working with backend". And for our backend, let's say git add, git commit -m "add endpoints with Sanctum". This is Andre from the future. I noticed I've missed a few things while editing, so let's go ahead and fix them.
Fixing SecureStore JSON Parse25:23
I noticed I've missed a few things while editing, so let's go ahead and fix them. So the first thing is I forgot to update the global User when we're loading the app. So you can see that here in root JSX. So let's go ahead and log in and you'll see what I'm talking about. So let's log in with this user here. So I'm going to log in. So it's gonna set the user to this user response,
So I'm going to log in. So it's gonna set the user to this userResponse, which is an object of the token and some of the user's information here. And at this point, everything is fine. Remember, we have this screen that console.log the userAvatar, like I just did a few minutes ago. So let me open up the console here. I'm gonna go to this screen
So let me open up the console here. I'm gonna go to this screen and we do get the correct avatar. However, if I reload the app, so this is when this code gets called root JSX. So it's gonna check secure store for this key, which does exist, but I forgot to set the object here. So at this point, the user string is a JSON object, but it's a string. So we have to convert it back to an object.
but it's a string. So we have to convert it back to an object. Actually, before we do that, let me just reload the app and you'll see that this is undefined now. So I'm gonna hit R to reload and let's go back to that screen and you can see it's undefined. So let me just log out and log in because our global state is no longer correct. Okay, I'm logged in again.
because our global state is no longer correct. Okay, I'm logged in again. So now all we have to do is JSON.parse the userString coming in. So JSON.parse userString. And this should work now. So I'm gonna save this. Let's go ahead and open our console again. Let's go to the search screen and we do get that avatar. Let's reload the app again.
So yeah, I'll make sure to add these fixes within that last commit that you just saw.
