← Back to blogYouTube Video

Published September 24, 2025

Building a Redis clone in Go - Part 10 (AUTH and Passwords)

Part 10 of 21 in Building a Redis Clone in Go

Can't play the video or having issues? Here's the direct link.

Summary

I implement AUTH and password protection for my Go Redis clone by adding a requirepass directive, tracking authentication state per client, and blocking unauthenticated commands. I add requirepass to the config parser and store the password plus a require flag on the config object. Each connection becomes a client struct with net.Conn and an authenticated boolean; AUTH accepts exactly one argument, compares it to the configured password, and flips that flag. Unauthenticated clients can run only safe commands like COMMAND and AUTH, while everything else returns a no-authentication error; I also make invalid commands return an error so clients do not hang. I land on this as a solid basic password layer, with Redis-style ACLs as a more complex possible follow-up.

Key takeaways

  • Add a requirepass config directive and parse it into a require flag plus the password before enforcing anything.
  • Track authentication per client connection, not globally, with a client struct that holds net.Conn and an authenticated boolean.
  • Allow unauthenticated clients to run only a safe-command allowlist such as COMMAND and AUTH; reject every other command with a no-authentication error.
  • Make AUTH accept exactly one password argument and set authenticated true only on a match; set it false on failure.
  • Always flush error responses, including invalid-command errors, or clients can hang waiting for a reply.
  • Keep AUTH as basic password protection; Redis ACLs add users, usernames, and command-level permissions and are a separate, larger feature.

Description

Source Code: https://github.com/hassanaziz0012/go-redis-video

Transcript

Hello everyone, welcome back to this series where we're building a Redis database clone inside Go, right? In the previous part, we implemented checksum protection for the RDB snapshots that we create, right? And the way we did this is we basically just write all of the database encoding stuff to the buffer over here in memory. We then write the buffer to the file, right? And we just match, we just compare the checksums of the buffer in memory and the file contents that we write on disk, and we compare both of their hashes to see if they match, right? So by doing this, we have checksum protection on our RDB snapshots. That's what we built in the previous part of this series. And in this video, we're going to implement the AUTH command, which is basically a Redis command that allows you to authenticate your requests with a password. So the way to do this is, first of all, we go over to the config file over here, and let me just add a comment for AUTH. The command or the configuration directive that we need to add is `requirepass`, and then a password. I don't know what to call this. I guess something random. Dolphins, let's go with dolphins, so the password for this server is dolphins, just because I can't come up with anything else. Now we have this in our Redis configuration. Let's go over to the `config.go` file over here. We're going to add a `requirepass` variable over here, and we're going to add a password as well, which is going to be a string, all right? Then let's go down here, all the way down to the `parseLine` function inside this `config.go` file. Add another case, which will be `requirepass`, and we know that if this configuration directive is present in the config file, that means that a password is required. So we'll say `config.requirepass` equals true, and we'll say that the password is the second argument over here, all right? The second argument. So the first one is just going to be the `requirepass` string, right? The second one is going to be this password, the dolphins string. That's all we need to do to add the password to the config object over here. So now we can actually implement this whole thing and authenticate every single client that we connect to the server. Now, the way to do this is to create a new client struct that you can use to tell whether each client is authenticated or not, right? So if I go back to `main.go` over here and down here, you can see that the way we handle every single connection is we don't even, like, create a new struct for it. We just use the connection that we get, and we just pass it in over here and let our code handle it for us, right? So basically, we don't add any extra information to every connection. Right now, we need to do that. So let's create a client struct over here. Very simple, at least for now. This is going to have a connection, which will be `net.Conn`, and it will have an `authenticated` boolean to tell whether this client is authenticated or not. We'll add a function `newClient`, which will take a connection and return a reference, a pointer reference to a client. So let's say return client, pass in the connection as well, and that's all we need to do. So now that we have this, in the `handleConn` function up here, we should create a new client for every single connection that we get, right? So let's call the `newClient` function over here, pass in the connection, and then in the handler function, which is used to handle every single connection and execute all of the commands, right over here, instead of passing the connection directly, we should pass in the client struct that we created, right? Let's save this and accept the client over here as well. This will be a pointer to the client. There we go. And down here, in the `newWriter` function, just pass in `c.conn` to fix this error. There we go, all good. So now let's implement the AUTH command. Go over here to the handlers list and add the AUTH command over here. Then down here, let's implement this by doing, by saying `auth`. This will take value, state, and return a value pointer, just like that. Let's grab the arguments as well. I believe, how exactly did I get these? Yeah, like this. Awesome. Now, the AUTH command should only accept one argument, which will be the password. So let's make sure that that is the case. If the length of this arguments list is not one, exactly one, then return an error value type error, and the error message will be "ERR invalid number of arguments for 'auth' command". There we go. All right, so assuming we get only one argument, that argument is going to be the, let's say, `args[0].bulk`. This is going to be the password, right? And it's a string. Let's say if the `state.configuration.password` matches this password that we get from the client, right, in this argument, if this is the case, then let's return a value of type string. The string will just be a simple OK success message. Otherwise, we will return another value, which will be an error, and this will just say "ERR invalid password". Now, that's basically all we need to do. But we also have to somehow authenticate the client, right? So if I go back to `main.go`, we created this client struct, and we want to change this `authenticated` field in this client to true, right, if the password matches, right? So in order to do that, in the handler over here, what we need to do is we basically just need to accept a client over here as well. So this is gonna take a bit more boilerplate because we need to add this to every single handler that we create. But, yeah, let's do that. So basically, and up here as well in the handler type over here, let's change this so it accepts a client as well. There we go. And then let's just add this to every single command handler. So the get command, the set command, the delete command exists, keys, save and BG save, DB DB size, and the command command. There we go. And let's solve this reference error as well over here in the BG save command. This C variable is supposed to be the copy of the database, so let's just call this CP. There we go. CP stands for copy, not something else, guys. All right? And the handler function over here is going to accept a client as well. There we go. And down here we have another error. The state.db copy is going to be the CP variable again. This is just a copy, nothing else. All right? Don't, don't, don't read too much into the CP thing. All right? It's nothing, trust me. Okay, so let me just think about everything we just built. So we've created the auth command over here. Now down here, we need to just authenticate the client, right? So let's say c.authenticated equals true. And down here, if the password was wrong, let's say c.authenticated equals false. There we go. And now we have the auth command, right? But there's one more thing we need to do. And by the way, all clients are going to be unauthenticated by default, right? Until they run the auth command and give us the correct password, they're all going to be unauthenticated, which means they should not be able to execute any commands at all, right? But at the same time, there are a bunch of safe commands that even unauthenticated clients can execute, right? One of those is the auth command because even unauthenticated clients need to be able to run the auth command so that they can give us the password and authenticate themselves, right? So what we need to do is we need to go up here to the handle function, and we want to do two things. We want to allow the unauthenticated clients to access safe commands, right, that don't modify the database in any way, and we want to block them from executing any of the other commands that are going to modify our database in any sort of way, right? So let's do that first. Let's create the safe commands list. This is going to be safe commands. There we go. This is going to be a list of strings, basically, and the only safe commands that we have right now are the command command. This is what every single Redis client runs when you start the client, right? So if I close this down and if I run this client again, the very first thing it's going to do behind the scenes is going to run the command command. So we need to allow this as well as the auth command so that these clients can authenticate themselves if they want to, right? So now we have a list of safe commands. Awesome. Now let's say if the state.config.requirepass, so if the configuration actually requires a password and this client is not authenticated, right? This exclamation mark stands for the not symbol. And finally, if this safe commands list contains the command that we just ran, that we just got from the client, then we can basically allow this command to run. So let's say contains safe commands and pass in the command over here. Now contains is not a function, right? This is not some built-in function. We need to actually create this, but let's just do this. And what I'm basically saying over here is if we require a configuration, if we require a password in the configuration, and this client is not connected, and let's add a not symbol over here as well, and the command that this client tried to run is not present in the safe commands list, then we should return an error over here. So let's say writer, and let's move the writer up over here, right? This new writer function call. Let's move it all the way up here and say w.write and pass in a value of type error. The error will be no authentication, authentication required. That's what Redis usually calls this error. Make sure to flush this so it gets written to the network connection, and then return because we don't want to execute any of the handlers, anything else if this, if the password was incorrect or whatever, right? Now let's implement the contains command as well. I'm going to do this in a separate file. We'll call this utils.go. This will have package main, and let's declare the function over here. This will take a slice, which will be of type string list, and it will take an item, which will just be a string. There we go, and it will return a boolean. So let's say for i in range slice, if the item equals i, then return true. Otherwise, at the end over here, just return false. So that's all we need to do. And now we can actually try to run this as well. And by the way, before we do that, in the invalid command handler over here, we should return an error to the client because otherwise, if I run an invalid command, let's just say, right, not only do I not get an error over here, but I can't even execute any future commands because I don't get a reply from the server. So the client just hangs over here, right? And that's obviously not good. So what we can do to fix that We can return an error message by saying `value type error` and just return something like `error: invalid command`. Make sure to flush this. Always flush whatever output you write on a network connection. And yeah, that's all we need to do. So close this down and restart the server. Okay, there's an error over here in the AOF file. Let me just open that up. Let's see. Okay, so the SET command is, yeah, we need to pass in a client as well to this SET command over here so that the AOF sync function can run. I don't want to create a new client for this because we don't have a connection over here. So what we're going to do is just create a blank client, just like we do with the state over here, right? And just say `client`. There we go, and pass that in over here as such. Awesome. Okay, now let's run the server again over here. It's running successfully. Perfect. Let's run the Redis CLI. First of all, let's just see if we can pass in invalid commands, and yes, it returns an error just like we configured over here. So now I can actually, you know, run a bunch of invalid commands, and it's not going to hang the client for me forever, right? It's going to return an error message, so I can, you know, work with that. Now let's try setting the name to, I don't know, Michael or something. It's going to return an error because I'm not authenticated, so I can't run any command, right? I can't run a single command until I actually authenticate myself. So to do that, let's run the AUTH command, and we can, you know, we can try passing in the wrong password as well over here, and it's going to say invalid password. So the correct password is dolphins. Let's run that. It's going to say okay. Now if I try to, you know, GET the name, it's going to return the name value. GET the age, it's going to return that. I can change the name to, again, Michael or something, and there we go, that works as well. So yeah, that's all we need to do to implement the AUTH command and everything in this server. And with that, our Redis clone now has password protection as well. I did some research, and the way Redis actually handles this is they have basically their own cloud infrastructure, and it has something called ACL. I have no idea what any of these things are, but basically they're a lot more complex and have tons of new features to, you know, limit your connections, limit your clients to certain commands, certain access privileges, and so on and so forth, right? And this is kind of a new feature, I guess, but the basic configuration for the AUTH command is just the, you know, the password protection, right? You just pass in a password, and that's about it, right? So I might actually implement this. I don't know how the ACL stuff works right now, but in a future part of this video or this series, I guess, I might actually implement all of the ACL stuff as well, the access control list. And in that scenario, we'll be able to create, you know, these user accounts with a username and the password, and each of these user accounts will have different permissions, different kinds of access to the database. So that'll be cool as well. But yeah, for now, the AUTH command works perfectly fine for our needs, and yeah, we're done with this part of the video. Thank you for watching. Like, subscribe, share, and hopefully you're learning something from this series. Hopefully you're following along and just really learning new things and understanding how databases like Redis work under the hood. I honestly, I love building projects like these. It just, you know, it just teaches me how all of these complicated systems work under the hood. Because, like, you can read a bunch of tutorials, read a bunch of documentation, play around with these tools, and develop a pretty solid understanding of these tools, right, of all of these systems. But when you actually build something like this on your own, right from scratch, that's when you develop the best understanding of all of these systems and how they work under the hood. And, like, it's, it's... That's just so valuable to me. I just love building projects like these where I just build a complicated system from the ground up so I can understand exactly how it works under the hood. Hopefully you're enjoying this as much as I am. I've got like five or six more parts already ready to be filmed. This series is going to continue for quite some time. Right now, I'm enjoying building this, I'm learning so many new things, and I hope you are as well. Thank you for watching, and I will see you in the next video, in which we are going to probably implement the EXPIRE and TTL command, so the time-to-live command. EXPIRE, if I can spell it correctly, and the TTL, time-to-live command. This is super awesome because this will allow us to set keys in our database to automatically expire after some time, all right? So you can say something like, like an authentication token, for instance, right, for your users. You can say that each authentication token should just expire after one day or one hour or something like that, right? And these commands will allow us to do that. So we're going to build these probably in the next part of this series. So stay tuned for that. And again, yeah, thank you for watching this one. Like, share, subscribe, all those wonderful things, and I will see you in the next video. Until then, bye-bye.

Share this article

Hassan Aziz pointing up

Dude, you’re at the bottom of my landing page.

Just book a call already if you’re that interested.

You scrolled all the way here.