How to implement sign-in with Ethereum using Remix | QuickNode Tutorial 003 #ETH

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Hey everyone this is Noah coming at you  with another QuickNode video tutorial   in this one I'm going to show you all how to  implement a single sign-on with ethereum in   a full stack javascript app I'm going to be using  the remix web framework to show you how to do this   to start it you're just going to run this create  command npx create remix you can then create the   name of your app I did sign-on-ethereum it's going  to ask you where you want to deploy and for this   I just chose the remix app server option and  then we're going to be doing this in typescript   and then do we want to run npm install yes  and after you have it all set up you can go   ahead and see we just changed directory into  that sign-on-ethereum that it created and then   we opened up vs code and with vs code open we  can start seeing the kind of build of the project   but before we do that we want to go ahead  and actually get our server running so to do   that from our terminal we can run npm run dev  and this will go ahead and boot up our server and now we can take a look here at what a  boilerplate app looks like for remix you   can see we've got everything lives in this  app folder we've got our routes and then the   entry for the client server and then the build  folder and all of the dependencies and everything   and we can see here that we've got a localhost  3000 just like you're familiar with if you've   been using create react app or nextjs and you  can see here this is what it looks like and   what we're going to be doing is we're going to  implement a login route and from here we want   users to be able to log in or register and so  right now you can see we're at a 404 not found   but before we start implementing all  of that to have users connect to your   app typically you have a database involved  and for that we're going to be using prisma   and prisma is just a nice orm that will allow us  to uh showcase this in a really easy way and I use   it for a lot of projects I think it's a really  good tool if you're in the javascript ecosystem   so to create a prisma connection we are going  to do npx prisma init and then we want this flag   --datasource-provider and then we're going  to provide sqlite and if you just do npx   prisma init I think the default option is postgres  and but you can see what this does is it creates a   schema prisma folder and you can see that it used  sqlite and then it's going to use the environment   variable of the database url it also created this  .env file and you can see that it will create   a file of dev.db so from here before we start  getting too ahead of ourselves we're going to want   to create a user model so our model will be a user  and it will have an address and that address will   be of type string and it will also function as our  id for the database and so with all of that added   you can see here that we've got  we can do npx prisma db push and   there we go and prisma actually has this  pretty nice tooling where if we did npx prisma   studio you would be able to see at localhost 555.  So we'll just go ahead and create a new tab here   localhost 5555 awesome and so you can see  now this is like a look into your database   from your browser that prisma allows you to  do and so you can see here all of our models   so we have a user and we have zero instances  of our user model so we'll just keep this tab   open we can take a look whenever we're done to see  that we did in fact create a user in the database   so our next step on the agenda is actually  creating a way to connect to the database   because if we have too many connections it's  going to overwhelm our server because every   single time that we are saving a change it's going  to rebuild and restart the server and if we don't   have any logic we're just going to create a bunch  of connections to our database and overwhelm it so   in order to do that we're going to open up our app  and we'll create a folder we'll call this utils   and utils will have a file called db.server.ts and  that'll be a typescript file and we will have this   available on GitHub for you I'm not going to go  over this since this is not mission critical just   know that if you want to be doing development  this is what you'll need i would just go into   the link in the description for the repository and  copy paste this i just know this is essentially if   it's a production it'll do a new client and  otherwise during development we will just   pick up our old connection instead of opening up  a new one every single time all right now we can   go ahead and close that and close that and know  that we've got our database situation all working   so the next thing that we'll want to do is  actually create that login round so under our   routes folder we'll create a new folder and this  will be called login and in login the default file   is an index and so whenever we go here  this is whenever we go to slash login   it's going to look for this file and it's  going to pick up this index.tsx file and so   just to make sure that's working we'll go ahead  and say we will export default function login   and we will return <h1> tags of gm and so here we can see if we go  to login boom we've got gm going and just another quick thing to note here you can  actually see it rebuilding every single time that   we're hitting save and then it shows that we did  a git login then we had a request of 200 where   previously you could see that we were getting  404 since we didn't have that connected yet so   the next step is actually to add a button  here so let's go ahead and we can just add   some parentheses here and then  we will wrap this in a <div> so we'll have gm and then we'll have a button and that  button will say connect to metamask actually   sign in with ethereum awesome and so let's go ahead  and get this looking good and there we go oops awesome and so now whenever  we go over here we can say cool and we click on   this button but nothing's actually happening  so let's go ahead and get metamask working   so we'll have we'll call this  getWeb3 so we'll say const getWeb3 cool and from here we're going to use this  window.ethereum object and so if we have   window.ethereum we can start doing a bunch  of stuff otherwise we'll just throw new error   metamask needs to be installed cool and you can  see we're starting to get this typescript marking   at us for this window.ethereum object and so to  get rid of that we're going to declare global   the interface is the Window object  and it can have a key of ethereum   and that could be any awesome and so what  that's E T H E R there we go window.ethereum   and you can see that lets it know that this  window object can in fact have this ethereum   key attached to it so if we have this  window.ethereum object we can go ahead   and create a provider so we'll say const  provider it's equal to a new ethers.providers   and we want a web3 provider and that web3  provider is going to be window.ethereum awesome and so you can see we've got this  ethers package you'll want to go ahead and   install that so we'll say npm install ethers  and that will go ahead and install this ethers   package ethers is a javascript library  that allows you to easily interface with   EVM based blockchains and so we've got our  window.ethereum so we've got our provider here   and the next thing that we're going to want  to do is we're going to send a request here   to the provider and what that's going  to be is a request to get the accounts   so we'll say oh wait provider.send  and this is going to ask for a method   as a string so that method is eth_requestAccount  or sorry it's not account I believe it's accounts   and then an empty array because we don't have  any param parameters to send along with that   method and so the next thing that we'll want to  do here is create a signer so we'll say signer is   provider.getSigner awesome and then we'll  also want an address and we can get that   from our signer so we can do signer dot get  address and then we'll also want a nonce and we'll just do Math.floor and Math.random and we'll take that number and we'll multiply  it times a million or a thousand one two three a   million and that's fine that should give us a nice  number to sign and then we're going to create a   signature and so our signature is going to be a  request for our signer.signMessage and we want   to sign the nonce but it needs to be a string so  we'll do nonce.toString awesome and so with this   address the nonce and the signature if we return  all of those we can send all of that information   to our server and due to the cryptography that is  used for all of these methods I can verify that it   is indeed someone that had control of the wallet  is the person that signed this message and if that   is the case they have control over it and I feel  comfortable creating an account under that address   so we'll just return all of these as an array  so this will be our address our signature   and our nonce awesome and with all of that we  can go ahead and create an onClick function   so we can say onClick and we'll say get web3 and  so we'll just check that this is indeed going to   request a message here so if we click on sign in  with ethereum I would expect metamask to open up   and we can see we've got these six digits that are  asking us to sign and if we sign it says okay cool   and now we're not sending anything to the server  so that's all it's actually gonna do right now   so our next step is actually gonna be  taking all that information that we can   send and we are to send that to the  server and then authenticate the user   and to do that we are going to create a another  file under app so under app we're just going to   say login.ts and this will have our authenticate  function and so we'll just say export const   authenticate and authenticate is going to be  an asynchronous function and it's going to take   an address that's a string it's going to take a  signature that's a string and it's going to take   a nonce that is a number and with all of that we  can then say const actually we're going to say let   authenticated equal false and then we'll say const  decodedAddress and what we're going to do is we're   going to take all of the information that we  have the address the signature and the nonce   and we're going to use a utility function that  the ethers library provides us and so that will be   ethers.utils.signmessage and you can see  it's not auto completing for me because   Iactually need to import it so we'll say  import ethers from ethers and our sign I don't want to sign the message we want to  decode message and so we don't want to decode   the message we want to verify the message  and so what verify message does is it takes   a message and the expected signature that  you get from signing that message and so the   only missing variable there is the address  and we can determine what that address is   so if we pass this the not to string and we pass  in the signature that we're expecting to get   what this is going to return is the address  that it would take for the not and the signature to work together for that address and so  we can know that if our decoded address   is equal to the address that was  passed along from the server we can say we can say authenticate authenticated is true oh I have authenticated there we go I misspelled  that there we go and then regardless we will just   return authenticated and so if this wasn't true  then we will return the false value because we   didn't enter this if statement and if it did we'll  return true because the address was the same and   if that's true then we feel comfortable actually  creating a user and adding that to our database so with that we can go back to our login function  and cool instead of get web3 we're going to   create a function called handleSubmit and in this  handle submit function let's see async function and in this handle submit  function we will go ahead and get   everything from the web3  so we can say await getWeb3 and this will be an array and that will  be the address the signature and the nonce   is equal to the await getWeb3 and from here we can  then pass all of this to the server and to do that   we're going to need to create a virtual form that  we can actually use to submit and so we'll say   const form is equal to a new FormData  actually this will just be formData just for   since it's not the actual form it's  just the data that's going in there   and then we're going to use this append method and  so what this is going to do is this is if you're   imagining an html form the id would be equal to  address and that would be the input field and so   what we're going to submit is this address and  so for each of these we are actually just going   to throw in a little hack throughout this tutorial  we're just going to add some of these for the sake   of speed typically you'll want to verify all  of the data but for the sake of speed in this   tutorial we are just going to ignore these and  know that we are in fact going to be getting them   and from here what we can do with our form data  is we can submit it but we need to grab a function   from remix that lets us do this so you can  import a useSubmit hook that remix exposes to us   and to do that we'll need to go under here  and the login and we'll say const submit   is the useSubmit hook and then what we can  do with all of the appended form data is   we can then submit this form and so to  get that done we can go ahead and grab   this little snippet of code and you can see that  it's going to be using this action and actions are   how remix lets you submit forms via these action  functions and so you'll see here in a minute   when we wire this up that it's going to use the  action from the index.tsx and it's going to be a   post request and this can be the encoding type  and we're going to replace any existing fields   but all of this should be fine and then we'll make  sure that we're handling the submit that's good   and so that's everything we need for this handle  submit function the next thing that we need to do   is create an action so like I said we need to  export const action which is an actionFunction   and that's going to be an asynchronous  function that takes a request as a parameter   and this actionFunction is where we can put all  of the logic for what we actually want to do on   the server once we send it some data and so what  we're going to want to do is I'm going to paste   some code here is we can use this request  parameter and we can request this form data   and so we can then from our form we can grab  the address and grab the signature and we   can grab the nonce from the relevant ids and so  that's matches up to these ids here on the forms   so now that we've got the address the  signature and the nonce we can use our   authenticated function so we'll say const  isAuthenticated and we will just await   the authenticate function and from here we'll  pass in the address the signature and the nonce   and you can see it's going to throw us some errors  here because it's saying that we're expecting a   form data entry value or null is not assignable to  parameter of type string because it doesn't know   all of these are form entry data values or they're  null it doesn't actually know the types of them so   we'll say we'll actually check these so y'all can  see that I'm just not just going to use ts-ignore   for every single typescript error that comes  up to check these we're going to say if there's if there's no address or if the type of  address does not equal a string then we will   return null and we'll do the exact same  thing for the signature and for the nonce and we want to go ahead and make this signature awesome and so you can see now it likes all of  this because we checked to make sure that for   one it wasn't null and then we have the type  of string string and number and so from here   this will be a boolean value and so we can  say if isAuthenticated is true then actually   we don't want to say if it's true we can say if  it's false if isAuthenticated is false then we   will just return no and otherwise we're going to  try to create a user and so we'll say const user is equal to db and so this db is now coming  into play for that database function that we   created in the utils earlier and so this is a  connection to our server or sorry our database   and so down here we can say db.user.create and we want to create a user where the data for the user  is equal to the address   of the address and since this is the only field in  our user that's the only thing that we need to do   if you had like a password or a username or an  email associated with your users you would create   this here but this is the only field that we need  to create here and so the next thing that we'll do   is we can actually return a redirect and this  is another nice thing that remix lets us do   you can see that just imported it there's this  redirect from remix and from here what we can do   is we can return a redirect and the  first thing that it'll take is a path   so we'll say slash login and  we'll say the user dot address I don't want to await that we need to await that there we go   and so whenever this is successful we will  redirect from that login screen to it'll be   login slash the address that the person logged in  with and typically I mean you probably wouldn't   be using the same route this would probably  just be its own thing but I'm gonna show you a   couple other cool things about remix on how you  can extend and create dynamic routes and I think   if we didn't get to all of that we should be good  here we've got the action and that is wired up we   are submitting it correctly and we are handling  the submit so we'll take it for a spin here all right and now if we click sign in with  ethereum and we go ahead and sign it you can   see that it should take us yeah we've got login  slash and then the address that we used to sign it   and we should see over here in the christmas  studio that we in fact created a user   awesome there we go you can see  we've got the address in there   and now for example if we were to go to this  slash login route again that we would be able to   whenever we do this it's going to crash because  okay cool it's going to fail whenever we try to   create the user because this address already  exists and you can see uh that remix does a   pretty nice job at giving you the stack trace as  well for whatever errors that you may encounter   so the next thing to try is actually  getting us to this address that we had we'll just copy this address from here and so  you can see that this isn't around the login   boom you can see we get a 404 and it's not  found you can see really quickly that I did   have to make a change to the code here you can't  do the type of signature equals number so I just   checked if it was a nonce and then I cast the  type to a number and then also added an await   here for some of these addresses and signatures  because I wasn't doing that properly originally and so the next thing that we want to do  with that in mind and all of that working   is actually go ahead and create a dynamic route  and the way that remakes does this is with this   ampersand or not the same percent the dollar  sign and so we'll say dollar sign userId.tsx   and for that what we're going to do is create the  loader function that I was talking about earlier   actions are used to submit data using remix  and loader functions is how we can pull data   from the server and so since we're pulling data  from the database we can go ahead and import db   from the utility server utils db server  and then we can import the loader function   and the useLoaderData and from here what we can  do is same thing as before we want to create   some sort of default function and so we'll just  call this export default function we'll call this   userPage and just to make sure that this is  registering for something we'll say it returns   the <h1> of gm and so now we should be able to see  if we do this that we get gm but the kind of fault   here is that literally anything after the login is  going to respond with this gm with the 200 message   but what we want to do is we want to create  some sort of way to check here or a way to pull   the user data into the page so we'll  say const user equals use loader data   and so just how we had before with the  action we're going to say export const loader   equals async function and this  is going to take a params object and so you can see in the params that it's  declared and for our loader is a loaderFunction   and then we'll want to go ahead  and expect for params you'll see   we're expecting a user id to be here in the  params so what we can do is we're going to   use this library invariant and so to install  that we can do npm install tiny invariant and then we can go ahead and import this  and so you can see we import invariant from tiny  invariant and it looks like something funny here   invariant and so and what invariant is going to  do is we're going to pass in this params.userId   and what's going to happen if this doesn't exist  is it's going to throw an error saying that we   expected the user id to exist and so this way  we're calling this loaderFunction every single   time that we are trying to load any page that  sits within the that slash user id on the params   and so now we can say we can pull in  a user and so we'll say const user   equals await db.user.findUnique where the  address of the user is equal to params.user userId there we go and so you can see  we've got the user id is matching here   so that's what's going to label the params as  and so if we have that we can go ahead and just   if there's no user we can return null  and otherwise we can return the user   and so now over here in this user object we should  be able to see that we've got a user right here so   if instead of returning gm we returned oh  we can we can return a <div> and and here   we can keep the gm for the vibes and we can  also have <p> tag that takes the user.address   and you can see here that it says use those loader  data of any and so we can actually put some types   here that we are expecting a user right here and  where we get this user type from is actually from   prisma so we can go ahead and import user from  the prismaclient and since our our prismaclient   knows the shape of the user we it automatically  creates this type and so we can already see that   there is the user.address so I think this  is actually all we should need right here   to see yeah and you can see already from saving  that we uploaded this and so now it's saying gm   with my address and if I add a bunch of  f's here you can see that we're getting   null properties of reading the address because  this does not exist and so now we can get into   error boundaries and this is a pretty fun  little thing that we can do for remix on any   of our routes just like for any routes we have  the the loader and the action and then we've got   the default function for the actual page I will  put this below we can export this function called   an error boundary and error boundaries will catch  all of these errors and so instead of it showing this we'll see here if we can get a instead of  this application error we should see that we can   actually make this a little bit custom now so what  we're returning is a <div> saying error and then   the error message and the stack trace so instead  of it saying application error we should see now   that if we add some f's here boom we get this  typed out in a really nice way and so now you   have kind of a way to create dynamic routes for  your users and then handle errors appropriately   and with that I think that concludes  the tutorial from this you learned   how to handle errors using remix how to  use these loader functions how to use   actions using remix to submit data to your  server and how to use prisma to interact with   your database and all of that will allow you to  create a user with just one click of a button
Info
Channel: QuickNode
Views: 1,135
Rating: undefined out of 5
Keywords: quicknode
Id: UQYf2wJAb-w
Channel Id: undefined
Length: 31min 40sec (1900 seconds)
Published: Wed Jan 12 2022
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.