Fullstack SvelteKit Auth 🔐 with Firebase & Magic Links! 🪄

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello and welcome i'm johnny and you want to sign up and log in users into your zveltkit app using magiclinks also known as passwordlessof magiclinks have become my favorite method to login for the user it makes it easy to switch devices and even seamlessly transition into our dedicated ios or android app we aren't living on a player that they're being responsible and using a password manager and no one is relying on third parties that may fall out of favor or change their integration strategies at this point it feels like we'll always have email so i integrated with a few viable third party auth service providers that offer magic links as an option i almost saw this video with one you can find at literally magic magic.link but in the end we're going with firebase magic's only concern is off like the top two industry standard of zero but i find it much easier and more modern firebase on the other hand has an insane amount of offerings besides all all with ridiculously generous free tiers i know a lot of you would want to use firebase anyway for its databases its storage and experience with firebase looks great on any cv and there is tons of tech companies at this point that have gone all in on it i've got timestamps and chapters like always but you may need to use them more than usual this video is gonna be a big one and i'll be going into greater detail auth is the thing that almost every app has so let's do our best to get it right and be able to problem solve when things don't go as planned sounds good alright sveltkit server set off with magiclinks and firebase let's go let's start with the least complicated and most fun thing a new login page i'll stop making brand new apps for these videos and just keep extending this one so i've already added a little question mark in our header it currently does nothing but the idea is it will appear when we're not logged in for curious people to click and navigate to our login page when we log in later we'll want this to become the user avatar instead and get us to the profile page we'll do that later now let's make it navigate us to the login page by turning it into an anchor with an href to slash login plain all html what renders there will correspond to our route's login so let's duplicate our routes about swell component we name it change its title and header delete some content and save and we can now indeed click and navigate to our login page what we want here are some instructions and a form with an input for email and a big button to submit if we specify that the input is of type email and that it's required we'll be getting some native browser validation for free note that we can even submit just by pressing enter let's add some telling classes to make things prettier we've got a killer combo going for styling tailwind with css variables it's fire especially in dark mode adam from gui challenges did an episode right after hours and i just had to implement his awesome toggle animation right form looks good what do we want it to do well even though one of the cool things with svelte forms is that we can make them work without javascript we are forced to create an onsubmit handler and prevent the default behavior all of solutions fall a bit short at the moment in that they really rely on their client-side libraries to make their outflow work and the old plane crash when you try to run them on the server i did find a way to email the magiclink server side but then we'd need to bring in our own email service provider like sendgrid and we'd still need to work with the firebase client library later to handle the callback so extra hooks with not much benefit in the end our authentication will still be server based with cookies anyway so let's bite the bullet and bring in the firebase clan lever npm install dev dependency firebase so something that happens due to the ways valkit runs and versailles really which is where we're deploying is that i don't really know its file will execute where maybe a file will run on the server maybe it will run on the client maybe it will run twice or more times on both maybe it won't run at all maybe it will run for one endpoint not the other this can cause trouble with both this firebase library and the firebase admin package but later as we need to ensure we run their initialize app methods but only once so let's create a source lib firebase client yes and create a method to get the client instance in there we'll be running initialize app with some firebase config we'll add later and return the client app with us initialized we also need to get the auth module for this app because we want to set persistence to in-memory this really means no persistence which is what we want given we'll be relying on our server on our own code to keep us authenticated cookies by default firebase would have been put in its own cookies all over the place which we don't want we won't be tracking the oauth state with firebase we'll only be using this library to go through the login flow in any case we only want to run this setup once and i found an undocumented way which makes perfect sense we can check if the get dabs helper method as length if we haven't run the initialize method it's going to be 0 which is falsey if we have it's going to be 1 so we can immediately return the result of yet app that's our app if you're wondering why did they even make a get apps method i'm sure there are use cases where you do actually have multiple different firebase apps running in the same page anyways we're here because we want to create a send magic link method we'll get the auth module again give it the clanned app and then we're gonna return the result of send sign in link to email a method firebase auth exports it takes the auth module we wanna use the email address to send the magic link to and some action code settings this is more interesting if you've got ios or android apps too for us we'll just pass it the redirect url to clarify once more in the docs you may see these methods called without the modules if you don't pass them in explicitly they will magically get them from the global scope or something but again we want to be explicit this is especially important because we're doing hot updates during dev and serverless deployments to versailles and our backend stuff will even more especially be flipping out if it can't find the modules or tries to recreate them alright this file looks good for now all we need is to set up this firebase config there are many options we can pass here according to which firebase features we want to use for us we just need project id auth id and database id we could hard code these as they are safe to expose the client but it's a good idea to use environment variables here in case we want to have a development or staging version of our firebase project in addition to the production one since zveltkit is using veed the way you use environment variables is with import meta and with variable name vit is needed as a prefix or else which will not expose it for safety if we want typescript to help us with the autocomplete we can create an n t dot ts file where we'll be extending the import meta and and we can manually add what environment variables we expect to have here we can also add a check in the client for whether any n var is falsy in which case try an error to remind us we haven't set them which is the case now to set them during local development we can add them to a 10 file which we shouldn't commit to version control it's in our kit ignore because we will be storing actually secret environment variables here later so how do they become available for our build step well in our case we need to remember to add them as environment variables in our versailles dashboard cool now that we know more about how environment variables work what's the values we should be putting here for that we need to actually create a firebase dashboard firebase is a google cloud services product so if you've got a google account you too can have it with a super gender spirit here since google is going for the strategy of making individual developers happy turning them into events lists and then closing deals with companies for millions for example magic offers 10 000 logins for free or 13 000 if you use a referral code and that's it you don't get any more recurring freebies that's more than you need to get started but firebase offers 2 000 magic links on the free tier per day forever that's not so we can create a new project and it is common to create a couple projects here like uh with swelt and with spelt dev if we wanted different environments but we'll focus on production today we can create a new web app and this is where we find the snippet to copy and paste again we won't be hard coding these we'll be taking the properties we need and setting them as environment variables while we're at it let's go to authentication we need to authorize the domains we'll be deploying to you localhost will work by default for local development as well as some extra domains for deployment firebase hosting but in our case we want to allow list the with swelt.com domain i bought this on a whim two years ago and the default domain versailles deploys to you unfortunately we cannot dynamically add domains here or use the wild character which means we'll need to run our full flow end-to-end testing in production but that's a story for another time for now let's also go to sign in method because we want to open up email and enable email link that's the magic links it's worth noting that firebase like the other providers i considered does offer other authentication methods as well and implementing them is quite similar to what we're doing today however magic links is my favorite so let's focus on that all right environment variables set send magic link convenience method ready to go let's send some emails let's go back to our login swelt file remember that we have this handle submit method which will be getting the form event which has a current target which is of course the form element itself we can pass the form to the form data constructor and get the email input value remember we're not controlling that input which is one of the cool things with zwelt you can make some good old html and javascript with super modern stuff all we need is to pass this email to our send magiclink method and the place we want to redirect back to and we're golden we haven't created or redirected yet but let's say it's going to be slash auth slash confirm however we need to specify absolute paths here so let's dynamically get this part of the url by using window location origin there are many things to improve but believe it or not we will get a magic link now if i submit the form let's check it out submit email received click opens in a new tab with a 404 awesome as expected we've yet to create that road but before we do that let's make this tab a bit better by introducing a bit of state in our lock-ins felt component this is where people reach for ease loading and hazard or booleans but i've heard where there is a flag there's a bug so i much prefer something like a finite state machine for this we don't need to bring in next state or anything fancy all we need is to decide the possible states this form can be in let's declare a form state type our form can only be in a few states we're working with a finite amount of states the form starts as idle then it's submitted then it can have success or error an actual error object not a string and now we can declare that our state will have that type and start on idle when we submit we set the state to submit in this method resolves when firebase has sent the magic link for us so if we await it we reach the success state and we can wrap this in a try catch block if we do catch an error clearly we've made it to the error stage we can use the state for some conditionals too we can say if the state is not success we saw the form we've always been showing so far else we can so success message and we can sit a bit and change our initial state to the one appropriate for what we want to design without actually submitting our form sicky time savers if we are in the submitting state we want a loading message but if the state is an instance of error if it is an error we want an error message as a finishing touch it may be neat to add the user's email to some of this copy let's have an email variable which starts as null but maybe a string and now we reassign this variable instead of creating a const with email value from the form now we can use it in our loading success and error messages and if we simulate a slow connection we can see the happy path for real we slowly go through the submitting state and end in success and we have been getting real emails signing all this time we will come back to the login page for further improvements later but for now let's make our slash auth confirm callback page work so in this page we've got a few more clan set firebase methods to run this is the point where even if we had brought our own email service provider and done everything so far server side we'd still need to do something client-side as there are no workarounds for the sign-in with email link firebase client method we want to run eventually you'll notice that our redirect url has had some further search parameters added to it as well that's the payload firebase needs to confirm that the correct person clicked the correct url and everything is legit but as an extra security measure firebase wants us to pass the user's email again we will figure out the email the user just submitted automatically using local storage eventually for now let's implement as if we couldn't figure it out for example let's say the user requested the login from desktop but then open the magic link from their phones which means we'll need another form here and it will be suspiciously similar to the one we did before so let's copy and paste our login.spelt file and turn it into a confirm.spelt file inside the auth directory this is more of a hidden page users soon navigate to by themselves so i feel putting it in this path and not the root makes sense let's amend our help and flavor text to maths with what we're actually doing in this page [Music] and let's handle our stage slightly differently we'll start with a validating state where we'll be checking whether the magic link is legit only then we'll move on to idle then we'll be submitting and we may have an error somewhere along the way let's write up our if else logic flow too [Music] all right in this page in this component we want to check whether we're indeed on a valid magic link as soon as it first renders i'd love to validate this on the server but as i mentioned the firebase client hard crashes with a lot of its methods when you run it on node so the way we run something when a component first renders in swelt is by using the magic on mount method where we can pass an inline fat arrow method with everything we want to happen at that point and what we want to happen is to execute a method we'll write now back in our firebase client file we want to check if the given link is indeed magic we get the auth module again and return the result of esignin with email link let's bring that method in our own mount and if our current location is not a magic length we want to set our state to an error with a helpful message to render and early out if we make it out of that if block we are in the idle state and waiting for form submission what happens when we submit this form well handle submit gets us to the submitting state and then we want to finally sign in with magic link let's write a new helper method in our firebase client file it's going to be very similar to is magic link but it's also going to receive an email and it will be returning the result of sign in with email link from the auth module so let's have our form handler called our new method and believe it or not if we hadn't set auth persistence to none if we were doing the auth client side we'd be pretty much done however i can't confirm we're still in the server-side adult video so we've got work to do still how are you holding up feels like i could have done like three videos on css animations at this point so if you are enjoying this leave me feedback subscribe comment watch this again and go along with me if you are not enjoying this i don't know go watch mr beast that guy is awesome but do leave some feedback before you go i do want to improve and speaking of improving let's improve what's going to be the most common flow let's remember the user's email from the login form and automatically sign them in when they open the magic link in the same browser we will use the browser's local servers for this we could use server side cookies if we wanted to complicate our lives again but there is literally no benefit given we're forced to complete sign in client side anyway so let's create a local storage directory and a magic email ts file where we'll write a few more helper methods we can decide on the key we want to store the user's matching email at and then the login form is going to set it in local storage so this will take in the email value local storage set item the email then our confirm4 will be getting that email and we should clear it from local storage in the end it can only cause problems if we keep it around so on mount we can now get that magic email and only if it's falsy we set the state to idle and return if not we want to finish the login automatically let's extract pretty much all of our handle submit into a login method which will be taking in a magic email it will be setting that to our component level email and now our handle submit can call login and pass in the email from the form and our own mount can also call login with the matched email from the component state and everything will work the same but the automatic method is clearly better as the user doesn't have to fill in yet another form alright to make this a more exciting demo let's saw something different on the screen let's get a reference to the credential here on successful login this results to what firebase calls a user credential this thing has the user and we can get the token the json web token will be posted to the server shortly let's set it as an error in our state this way it'll render immediately on the screen and we don't have to resort to console login sweet let's go through the flow in a live demo this is where i'd ask for confidence levels on whether this is going to work but surely i'm not going to waste your time by sewing this fail right [Music] this is a jwt5 or so on so we've done things correctly so far we can even check the firebase dashboard and indeed we've got a confirmed user someone went through the flow but to actually keep the user logged in we need to do a few things server side so instead of setting an error state what we actually want to do with a token is to send it in a post request well fetch our own oauth session endpoint we'll be creating right after the request method is going to be paused and there are a few ways to send this token we could just put it in the request body but the most traditional way to do it is with headers with an authorization header that has a better token there are a few authorization schemes like basic we're specifying we're using better here it doesn't really matter given we'll be the ones handling this request with our own valkyrie 10 point so we can just make things up but why make things up when you can follow a standard that's more easily googleable we're going to make that 10 point respond with our user as jason so if we change that then here to parse.json we'll have a reference to our user for later we probably don't want to stay in the screen after logging in so let's use sveltkit's magic go to method to go to say the index page this is all well and good but nothing is handling that post to auth session which means it's back in time to handle a post request to auth session we need to create our routes auth session ts file and export a post method from it to see it the other way around swell kit magic ensures this file handles requests to this path and that its post export specifically will be the one handling the post requests the request handler gets given locals which get put into the request by a hook we'll see later but what we're really interested in is the request itself we can new up the headers instance out of it and get our authorization header because we were old-school sticklers this header is not just a token it is a string which starts with bear as a space and then the token so we can be sticklers again split on that space which should give us an array with two elements the authorization scheme and the token if the scheme is not better or if we ain't got not token we can return an error response by returning an object with a 401 status and a helpful message i'd love to say that here we quickly get our user object and handle our happy path however we first gotta set up firebase admin it's similar to what we did for the firebase client but even less convenient but we can do it so let's npmi the firebase admin and create a firebase admin ts file getting the admin app is going to be more convenient than getting the client up actually remember we're doing this to ensure there is exactly one instance of the admin app running however we don't have to do anything further this time for installation so we can even inline the return value so the firebase admin app module exports similar methods to the firebase client app module we're checking if there is any apps to get if yes we get the app if not it means we haven't initialized one yet so we run our initialize up method and return its result which is the admin app about this conflict though this admin config firebase really wants you to deploy a secret file for ncla session however that's not convenient since we're deploying to burcell environment variables are relatively easy with vercell we went over them before but encrypting and decrypting files is not and hardcoding anything is not an option here the things to make the admin app work must not be leaked under any circumstances people will be able to cause all sorts of trouble if they've got admin privileges to our app so we will go through the steps to create a new service account we will download our json file of credentials but what will we do with it if we can't upload it well turns out there is another documented way to make this work the only config we need is the credential and we can create it ourselves using the cert method from the app module all we need is our project id which we've already got for initializing the client the admin client email and the private key all of this we can find in the service account credentials json file we just downloaded it's got other stuff too but that's all we need and remember never lick these never sell them to anybody but do get in there copy the values we want copy the client email paste it into 10 for viet firebase admin client email copy the private key paste it for viet firebase admin private key and let's not forget to add them in the versailles dashboard too or our deployments won't work and yes i am adding the vid prefix so it will process them and we can use them with import meta dot n which means that if we're careless we can use them in the front end nothing will stop us again it's on us to be safe and ensure we only use them in backend things such as the swell kit endpoints not the swell kit pages and components that said let's update our typescript definition file too and clear the final hurdle for initializing the client one last weird trick you won't believe works we need to replace any skipped new lines in the private key with unescaped new lines after all of this our admin app will initialize correctly so let's give ourselves a pat on the back and write our first helper method verify id token remember the token from the endpoint handler we want to verify it and it's very similar to how the firebase client works we get the auth module and can try calling it if the token is not valid this method will throw an error and we can return another unauthorized response but if it is valid this method will resolve to the decoded id token which is essentially our user amongst other things the decoded token has a sub which is the user's unique id as well as an email so we're full on the happy path here we've got a user we can return them as the body of a 200 response but we also want to return a cookie if you've watched the ssr dark mode video you've seen this before the way we set a cookie with swell kit is by including it in a set quickie header so we can say our session cookie is session equals some value and give it some options for safety including same side strict it should be only secure make sure it works anywhere in other domain and a max 8 max aids has to be in seconds let's say one week and we won't be dealing with refreshing the session today as we're already dealing with so much so what about the value of the quickie well firebase admin will construct this for us so we could actually take our string get in our firebase admin ts file and write a create session quickie helper method which returns the cookie we want to set before that we want to get the auth module and create session cookie [Music] our method will be given the token and the max aids firebase admins create session method takes the token in an options object we'll only be using expires in that's the time to expire in milliseconds so not our max h what's up max h is in seconds its expirism is going to be our max age times 1000 so we've got the session value now it's in our cookie we're using this helper method to return it with our response which also has the user in the body we're golden so a cookie will be set with these fets but why why do we want a session quick again that's right because we're doing server side off with zvelkit and we're almost there our server can now read that cookie if it exists and determine if the user is logged in or not again we did a similar thing in our ssr dark mode video where we've read the theme cookie and put it in zweltkit's special session store as a reminder stores is what you use for search dating as well and svelkit creates a session store for you based on what you've got as the return value of the get session hook if you haven't followed along with that video no worries all we need is a hooks index.js file which exports a get session hook whatever object we return in this method will be in our session store so if we return a user property it will also be available in the session store however for our theme it was kinda fine to only implement the get session hook as no endpoint really would need to know about the theme but now it feels like loads of endpoints may want to access the user too so just putting the user in the session object is not enough the proper way to do this is to first implement another hook called handle handle gets the request can do all sorts of operations to it and then has to resolve and return it we've learned too much in this video to go into a further level of detail for this what's important is that we are supposed to put useful data endpoints may want to use inside the request locals object so we'll straight up mutate that put the theme in there [Music] then our get session can get the theme from the locals and return it why go through all this trouble well we may put things in our request locals that our back-end should have easy access to but shouldn't make it to the front end shouldn't be in the session store for example the theme is fine and safe but let's put the id token in the locals [Music] for that we need our final helper method back in firebase admin one last time let's get id token from session cookie if there is no session wiki where it are null if there is we should verify it so we get the auth module again and we verify session cookie much like the verify id token method this will return the decoded id token but we don't want it to ever crash so let's catch any errors and return null if they happen if the cookie is not valid the user is null they're not logged in so now we've got the whole id token in the locals which we don't necessarily want to expose to the front end instead let's manually create our user object again if there is an id token we get the sub for the id and the email for the email if not the user is null sweet we've got the user in the session store how do we access them let's create a derived store as a convenience once again this is something we've done for the theme we want to create a store's user yes we're going to export user which is going to be as well derived store derived from the session store magically available from app stores the session may have many properties so the point of our user store is to directly get to the session.user and since we're here let's also write a convenience method for setting the user set user is going to take a user object or null since we'll probably want to provide local functionality and update the session store we want to spread the session to keep all other existing properties like the theme but set the user property to what we were given and now we can even use this convenience method back in our auth confirm page to set user to the user from the response we got back from auth session it's all coming together well in fact everything works now but how do we prove it let's do the thing that we said in the beginning and so the user's avatar in the header if they're logged in so in our profile component we want to use our brand new user store if we've got a dollar user we'll link to the profile page and saw some fresh ui if not we'll show our lovely question mark [Music] spoiler alert you can see that we are indeed showing the first letter of the user's email address in the header because we are logged in no smoke and mirrors here we do have a user this has actually worked which is great as we can design in peace suite we'll implement logout right after another demo now i'll log myself out by clearing the cookies in localhost and let's go so for refresh we get a question mark because we've got no session cookie we're not logged in i fill in the form get the email click the link ta-da and then we click the initial and we get a 4-4 as we haven't made that page yet but we can see that if we refresh anywhere we're still logged in it's persisted we're awesome no client-side http requests for this we are doing server sides well kit off with firebase for ulcs to celebrate let's make a rudimentary profile page by copy and pasting our rudimentary about page this is where we can solve the user's favorite lessons if we implement that functionality in the future after all we are logging users in so they can have some extra features right but remember this isn't a real app i made this for you to convey a more realistic use case so keep bringing your use cases and i'll see what type of video i'll be making next but we still got stuff to do in this video log out button time and we aren't forced to use clanshad javascript for this one so we can do it as well get style with a plain old form with a post accent to an end point let me note here that i don't consider this bit that important given we've gone over so so much today and i'll probably do a dedicated episode on spell kit forms eventually but i do think long term exposure does help commit stuff to memory so warning i'll go through this fast don't worry if it doesn't make sense this is not the video where it should make sense alright with plain old vanilla html forms we can only set two types of methods get and post however with swell kit we can specify a method overhead property in swell config js inside kit and then we can provide a clear parameter for underscore method which will turn the request into one of the more hipster methods you may want to use like put paths or delete that is our case we do not want to post to auth session again we are already doing that to set the session quickly now we want to delete our session cookie which will log us out so the method we want is indeed delete how do we handle a delete request to auth session we open our routes of smts and add a dell handler in it we already have a post handler we would be adding a delete handler if we could however delete this our reserved keyword in javascript like class so swell kit goes with del as the magic name for delete handlers and all we want is to return to 200 with a set quickie header why i said cookie header when we're trying to delete a cookie well that's how it works i'm afraid to delete a cookie we need to return it expired so we can copy our cookie string from our helper method we don't need any value what we need is a max age of 0 which will immediately expire and unset the cookie put the quick key down cool logging out will already work now even without javascript remix has been milking this functionality and it is cool but the default form behavior is why we started using so much javascript in the first place so let's use some to enhance the experience zveldt has this thing called actions you can give four elements to use and i noticed the sveltkit demo app has an enhanced action it's using it in all these forms so i copied and pasted it in an actions directory as enhanced form and we can use it in our logout form it prevents default form behavior so we won't navigate away and takes three optional methods a pending method for when the form submits another method if something goes wrong and a result method for when it's succeeded result is the one we care about as we handle logout our way by setting the user to null and navigating to our index page if all of this looks nuts but intriguing feel free to ask for a video where i explain it properly speaking of doing things properly let's add some redirects to our pages as a final bit of police we do not want unauthorized users to be able to get to our profile page for that we can use the magic load methods valkit pages have so we need another script tag this one has to be a module as we're going to export a load method swapkit will run this on the server load methods have access to the session object 2 so we can get it out and check if there is no session user return redirect to the login page with a status code of 302 if not we return an ok to 100 status we can do the same for our login page if we are logged in and we somehow make it to this page maybe we had it bookmarked we can redirect to our profile page and that's probably where we should be going when our confirm page is done while we're at it i'll show the address bar so we can indeed see i can try to go to the login page directly but not dice let's log out this works as expected too and now it's the profile page i can't see even when i try to access it directly so let's deploy this and have a full-blown full flow demo and call it a day using my phone so i go to this amazing new app that's so hot right now amongst all the software engineers i'm not logged in i tap the question mark i fill in my email address i submit email time open that up follow the link and i am logged in awesome i can see my profile and refresh any of these pages and i'm still logged in and there is no flash or loading spinner for the avatar as it is indeed the server that's already checked and determined that we are locked in the server renders the avatar straight away i can come back almost a week later and i'll still be logged in but i can log out now and boom my session is gone further enhancements would be some over engineering like putting our session in redis which could mean that i can log out from every device at once we could refresh our session on every request or after some specific requests or make some websocket magic to log into every window after i followed the magic link successfully but we're starting to work together as a community here so you let me know what you'd like to see what i'd like to see is you watching this video again and coding along and some say that if you leave my videos playing while you sleep you wake up as fresh as the cookies we just made thanks a bands for watching i'll see you around [Music]
Info
Channel: Johnny Magrippis
Views: 8,256
Rating: undefined out of 5
Keywords: typescript, javascript, svelte, sveltekit, auth, authorization, authentication, firebase
Id: MAHE4iQgh5Q
Channel Id: undefined
Length: 41min 57sec (2517 seconds)
Published: Mon Feb 28 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.