I Rebuilt Drogon’s Login Example Using the ORM

Drogon ships with a login example… that uses MD5 password hashing on a single hardcoded user account. MD5? In 2026? My goodness! Even the author warns you not to use it.

Dont use MD5 warning

This is the perfect opportunity to learn how to use databases via Drogon’s ORM. I’m learning Drogon because I want to build a real SaaS backend in C++.

So, I’ve rebuilt the login example to lookup users in a database, and also replaced the hideous MD5 password hashing with something that’s fit for purpose. It can:

  • Create admin accounts from the command line
  • Login, and logout

This is my first real step toward a production-ready SaaS backend. Let me show you how I did it.

Secure Password Hashing

First up, lets heed the warning and replace the MD5 password hash with something that’s secure. For that, I picked libsodium, an easy to use crypto library. And by crypto, I mean cryptography, not Bitcoin. You know: encryption, decryption, password-hashing, etc.

It has two very convenient functions:

  • crypto_pwhash_str() to salt and hash a password
  • crypto_pwhash_str_verify() to verify if a password matches the hash

These functions use the latest recommended algorithm to hash passwords.

Designing the User Database

Now that passwords are secure, we need somewhere to store the user accounts. This is pretty simple: a single users table in a database. Each user has a unique id, username, password hash and salt, and is_admin column.

User database screenshot

I also added created_at and updated_at entries, even though they’re not needed for this simple example. Actually, I went way overboard, and created a full database migrations module. Completely unnecessary for this example… but future-me will be grateful.

One thing I really like about libsodium, is that the password hash string includes all the details needed. Look at the password hash (below), and you’ll see that the password was hashed with the argon 2 hashing algorithm. The algorithm parameters are in there too, along with the password hash and salt. In past projects I had to store all of that in separate database fields. Now they’re all stored in one place.

Libsodium password hash string includes all needed parameters

How to Use Drogon’s Object Relational Mapping (ORM) Module?

That’s the database schema done. How do we use it with Drogon’s ORM? If you’ve used something like SilverStripe, or Ruby on Rails, then you’re probably expecting that you’ll write C++ classes modelling the schema, and Drogon’s ORM will create the database handling code for you, along with the database itself.

  • Not so. Drogon takes the opposite approach:
  • First you create a database, and run SQL queries to set up the tables
  • Next, create a model.json file that points to that database
  • Now run drogon_ctl create model .
  • It’ll generate the C++ classes to match the database schema
  • Add the generated source-files to your build script

It’s now ready for use.

Rewriting the Example Code To Use Our User Database

Two tasks remain. First, real systems need a way to create the first admin account. So, I added a way to create admin users from the command-line.

Second, add the user lookup, and this is done via the this code:

            // Look up the user in the database using the ORM
            auto dbClient = app().getDbClient();
            CoroMapper<Users> coroMapper(dbClient);
            
            try {
                // Query for the user
                auto user = co_await coroMapper.findOne(
                    Criteria(Users::Cols::_username, CompareOperator::EQ, username)
                );
                
                // Verify the password
                const std::string &storedHash = user.getValueOfPasswordSaltedhash();
                if (crypto_pwhash_str_verify(
                        storedHash.c_str(),
                        password.c_str(),
                        password.length()) == 0) {
                    // Password is correct
                    session->insert("loggedIn", true);
                    session->insert("username", username);
                    session->insert("isAdmin", user.getValueOfIsAdmin());
                    
                    LOG_INFO << "User '" << username << "' logged in successfully";

                    // FIXME!: Check if rehash is needed: crypto_pwhash_str_needs_rehash()
                    
                    co_return HttpResponse::newRedirectionResponse("/");
                } else {
                    // Password is incorrect
                    LOG_WARN << "Failed login attempt for user '" << username << "': incorrect password";
                    co_return HttpResponse::newRedirectionResponse("/?error=invalid");
                }
            } catch (const UnexpectedRows &e) {
                // User not found
                LOG_WARN << "Failed login attempt for user '" << username << "': user not found";
                co_return HttpResponse::newRedirectionResponse("/?error=invalid");
            } catch (const std::exception &e) {
                // Other database error
                LOG_ERROR << "Database error during login: " << e.what();
                auto resp = HttpResponse::newHttpResponse();
                resp->setStatusCode(k500InternalServerError);
                resp->setBody("Internal server error");
                co_return resp;
            }

Here’s how it works: We set up a database object mapper for the User’s table, ask it to lookup the user, and then check the password. If the username and password match, then the user is logged in

We now have a working login system. Nice!

Final Comments

Before you go, two things.

First, notice that I’m using the co-routines variant of the Mapper class (CoroMapper). I highly recommend using co-routines for all new Drogon projects. I’ve covered why in a previous video (link).

Second, I completely skipped explaining the database migrations system that I built, and how to use SQLite on a production website. It’s possible, but not straightforward with Drogon. Those will be the topics of future videos. Subscribe to follow along, and I will see you next time.

Leave a Comment

Your email address will not be published. Required fields are marked *

 


Shopping Cart
Scroll to Top