Build your own password authentication
Build a password authentication system with Postgres, bcrypt and the blowfish algorithm. No auth provider and no external service. We start with a reactive login form, add a database migration for the users table, then hash and check passwords by hand in Postgres to see how it works. Then we wire it together end to end: the form, the rpc behind it, and the session. We finish by having Claude Code build the whole thing from scratch: a fully functioning authentication system with beautiful sign in and create account pages, and the tests to prove it works.
In this video we'll roll our own authentication from scratch using Postgres one-way hashes, RPC, and reactive HTML. No external services required. At the end, Claude Code will build us a fully functioning, beautiful A system. I'm going to open up the project in Vim.
We'll head over to the home template HTML. I'm going to replace the boilerplate with a very simple login form that we could use to demonstrate these concepts. The HTML has two main parts. If the user's logged in, we show their username and a logout button, and if they're not logged in, we show the login form that you see over on the right.
Let me paste in a couple of functions that we'll fill out to provide the login and logout functionality. Inside the login function, in order to log in a user, you call the login method of session, and you can provide it an object which has a user ID which we'll just hard code to one for now, and a username which we'll set to whatever the parameter form username is set to. For logout, we call the logout method of the session object. These two methods are event handlers.
I do this by convention where I put an on prefix in front of the name, and you'll see why having event handlers can be really useful in a second. For now, I'll just proxy straight through to their corresponding methods, log in and log out. Next I'll come down to the HTML tag and add a constructor attribute that's private called form of type login form. This declares an attribute that's private and available for use inside of the template only.
Next, let's add an event handler to the button such that when the user clicks it, we call the on logout function above. Then on the form tag I'll add the on submit event handler and this time we're gonna call the on login and pass the form attribute that was defined in the constructor attributes above for our HTML tag. I can see in my editor that I have 2 errors, so I'll open up another panel on the right, type elements build, and that will give me a quick snapshot of what's going on. You can see that we have a security error, and it's telling me that you cannot call server code from the browser without going through an RPC function.
The server code that we're calling is logout, and if I were to scroll down on that area, you'll also see that login is not permitted to be called from the browser either. And that's because if any browser code could log in a user, it wouldn't be very secure. It needs to go through the server. In order to fix this problem, we need to annotate these functions with an RPC build tag.
This tells the compiler to ensure that these only run on the server and the browser will call to these functions over the wire using an RPC protocol. When I save the file, the air goes away. Finally, we need to come down to the form and bind our inputs. I'm going to set the value to form.
Username and this is going to two-way bind this value to this input field such that if the UI changes, the data changes and vice versa. Down in the password, I'll do the same thing, but I'll set it to the forum password. Great. Now I should be able to go over to the right and provide a username and a password, hit login and see that the user interface is updated reactively to the logged in state.
If I click log out, it logs me back out and I see the original login form. Next, we'll create an elements migration that adds the user's table such that we can actually try authentication in the database. I'll open up the migration file. I'm going to add two fields to the user's table.
The first one will be the username that'll be of typed text. It'll be unique, and we wanted to make sure that it's not null. And the next one will be password hash. Note, we're not going to store the cleartext password.
We're going to store a hash of that password such that someone looking at it cannot actually tell the password, and that's very important for security. Next, let's add a dummy user into the database so that we can play with it easily. To store the password hash we're going to call the crypt method and I'm gonna pass the password as the first parameter. This is going to be my mother's bank password, so please keep it safe.
And as the second parameter I'm gonna call the generate salt method. I'm gonna use the blowfish algorithm with a. Count of 12. When I press save, my database should be up to date.
Next, let's use the elements DB command to log into PostCress and take a look at the user's table. There's the record we inserted, and note the password hash is not clear text, it's encrypted into a one-way hash. But how do we check whether someone is authenticated? We issue a select query.
For now, I'll just select all fields. We're going to select a row where the username is equal to me and the password hash is going to be equal to the value of calling crypt again, and this time we'll pass a password, password 1234 I think it was, and instead of calling generate salt, we're going to give the name of the column that has the hash. It will find the record because it figures out how to hash this value that was provided as the first parameter using the salt that was stored with the password in our in our hidden column here and we get back a record because it matches. Now if I change this to a bad password.
I'll get 0 records back and that tells me that the user was not logged in effectively. Let's finish up by adding that SQL query to our login RPC function. To do that, we're going to import the SQL method and the AI from the Elements application package. And in the login function, I'm gonna just replace this entire code with one that I wrote off camera.
The first line says to run the SQL query, select ID from users where the username is equal to the one that was provided as a parameter, and the password hash is equal to the crypt forum password using the password hash column. This is similar to what we just did in the DB in the DB shell itself. Now one thing that you might be worried about is providing these values as template parameters is totally fine in elements because the compiler rewrites it to be safe, so the no SQL injection attacks are possible here. Now if the user is null, it means that we did not authenticate properly and would throw an authentication error.
Never tell them whether it was the username or the password, uh, just say it was one or the other, otherwise you're giving the attacker some information. And if we get past that, then we'll log in the user this time with the user ID and with the username that was provided up from the form. Since the login RPC function can throw now, we need to handle that in the client, otherwise we'll just see an uncaught exception in the console if the user is not able to log in. We would normally build a user interface for this, but for the sake of brevity, I'll just paste in a tri-catch block that catches the error and logs to the console that the user was not able to log in.
Notice that this error can be thrown over the wire and it travels over the RPC protocol just like everything else. Let's try to log in with the correct password first and make sure that the state changes. Perfect. When I log out, I'll try a bad password and I should see an error in the console indicating that the login failed.
Now that we've seen the code concepts, let's have Claude Code build us a completely functioning authentication system that looks beautiful. I'll paste in the prompt and press enter and Claude code will get to work. Already we have a sign-in page that's looking a lot more beautiful than the one we created previously. Claude is driving the user interface directly in the browser to ensure that it works.
Fantastic, we have a completely working authentication system with tests and user interface that works in mobile and also on desktop in the terminal on the lower right I'm going to check out that the tests work properly by typing elements test and scrolling down and seeing that they're all passing. This is one of the fantastic benefits of working with elements with an AI agent is that the tests get written automatically and you can see at a glance whether everything is working and if anything has regressed over time.
Comments· 0