Ruby
Summarized using AI

Rodauth 2.0

by Jeremy Evans

In the Pivorak Conf 5.0 online edition, Jeremy Evans presents Rodauth 2.0, Ruby's advanced authentication framework suitable for any Rack application. With enhancements like WebAuthn support for multi-factor authentication (MFA), global logout through active sessions, and audit logging capabilities, Rodauth stands out for its security and flexibility.

Key Points Discussed:
- Background of Rodauth: Created in 2015 due to the diverse authentication needs across multiple applications with various back-end requirements. Jeremy aimed to provide a flexible and secure solution tailored for non-Rails applications.
- Comparison with Other Frameworks: Rodauth encompasses features found in popular frameworks like Devise, such as logins, password resets, and account verification, but adds additional security features like password reuse restrictions and support for multi-factor authentication.
- Security Features: Rodauth prioritizes security with best practices for password storage, including separate tables and controlled access via database functions, which minimize risk in case of SQL injection vulnerabilities.
- Simplicity and Configuration: Unlike other frameworks that require changes across multiple files, Rodauth allows centralized configuration, supporting over a thousand configuration methods for customization, including request-specific behaviors.
- Integration with Different Frameworks: Rodauth can be used with various Rack applications, including Sinatra and Rails, aided by a middleware plugin.
- Concurrent User Support: The framework allows for unique configurations for different user types (e.g., admins vs. regular users) and supports OAuth integration via an external gem.
- Recent Developments: The recent v2.0 release enhances these features while maintaining backward compatibility.

Conclusions:
- Rodauth is positioned as a robust and flexible authentication solution for developers looking for granularity in security and ease of use. The consideration of security in design, plus its adaptable configuration, positions Rodauth as a strong candidate for future projects requiring authentication frameworks. Jeremy encourages exploring Rodauth through its demo site and comprehensive documentation to understand its capabilities better.

00:00:08.639 So I'm ready, and I am thrilled to introduce the first speaker, Jeremy Evans. He's been using Ruby since 2004. He’s a Ruby committer and the OpenBSD Ruby ports maintainer. He is also the lead developer of Sequel, Roda, and Rodauth. By the way, I like his Twitter account, which showcases a changelog with lists of releases. He has worked in a government department that has utilized Ruby for more than 14 years. Imagine that in Ukraine, for example! For fun, he spends time with his wife and kids, playing ice hockey and video games. I bet there are attendees who share similar hobbies, so let's give a round of applause in the chat for our special guest from Sacramento, California.
00:01:29.960 I tell everyone this is my first time doing an online conference, so bear with me. I'll try to get my screen sharing set up. Alright, so hopefully, all of you can see this awesome art. Good afternoon or evening. I believe it’s evening in Ukraine. This is actually my second time presenting to an audience in Ukraine. The last time was in 2014 when I presented in Kiev at RubyC. That was a really great experience for me. Today, I’ll be talking about Rodauth, Ruby’s most advanced authentication framework.
00:02:12.730 My name is Jeremy Evans. As I mentioned, I’m the author of Rodauth. I’m also a Ruby committer and an OpenBSD committer, as well as the maintainer of the Sequel database library and the Roda web toolkit, along with many other gems. When I’m not busy working on open-source projects, I manage the IT operations for a small government department. I lead a programming team, but I should mention that the team really consists of just me and one other programmer.
00:02:32.920 First, let me go over the agenda for this presentation. I’ll discuss the history of Rodauth, some differences between Rodauth and other Ruby authentication frameworks, the goals of Rodauth, and provide some code examples to give you a good idea of how Rodauth is configured and how to use it. Let’s start with a brief history of Rodauth.
00:03:04.299 Rodauth was created in 2015. At that time, I was maintaining over ten separate applications, all of which had ad hoc approaches to authentication. The authentication requirements differed significantly between the applications. Multiple applications used LDAP for authentication, while others stored password hashes in the database. All the Ruby authentication frameworks available then were specific to Rails, and since none of my applications were Rails applications, I had a crazy idea to write my own authentication framework. It had to be flexible enough to handle the very different requirements of each application.
00:03:39.520 After reviewing other authentication frameworks, I wanted this framework to take a different and stronger approach to password hash storage security, since that approach was already in use in a few of my applications. The tagline of Rodauth is 'Ruby's most advanced authentication framework,' which is a bold claim considering there are older and more popular authentication frameworks like Devise, Sorcery, and Clearance. Hopefully, by the end of this presentation, I’ll have convinced you that Rodauth really is Ruby’s most advanced authentication framework. If I do a good job, maybe you'll even want to consider using it in one of your future projects.
00:04:15.390 Now, let me discuss the features. I’ll compare the features of Devise and Rodauth. Clearance, Authlogic, and Sorcery have features that are basically a subset of Devise's features, so my analysis will also apply to them. Rodauth handles pretty much all of the same features Devise offers, including logging in and out, creating accounts, verifying email addresses, handling password resets, blocking accounts after a certain number of failed password attempts, remembering users via cookies, logging authentication actions, expiring sessions, validating logins and passwords, and allowing users to manage their account.
00:05:01.390 However, it doesn’t stop there. Rodauth also handles many additional security-related features. It supports disabling the reuse of passwords, disabling common passwords, forcing a password or account expiration, enforcing a single session for each user, and allowing global logout of all sessions for a user. It ships with support for multiple multi-factor authentication methods, including WebAuthn and TOTP (Time-based One-Time Codes), and you can also set up multi-factor authentication using SMS or generate account recovery codes in case a user loses access to their usual multi-factor authentication device.
00:05:48.480 Rodauth supports multiple passwordless authentication methods, enabling authentication using email links or a WebAuthn security key instead of a password. It also includes a JSON API for all features, allowing soaring authentication information in JSON Web Tokens. That's right; every feature can be accessed with standard HTML forms and also via the JSON API. The JSON API supports cross-origin resource sharing, as well as separate access and refresh tokens.
00:06:29.270 Now, one might ask, can't you get the same features using third-party extensions for Devise? In some cases, yes; there are extensions for adding JSON API, TOTP support, and WebAuthn support to Devise. However, if you need to support TOTP or WebAuthn via the JSON API, you'll likely have to write those extensions yourselves. The main benefit of using Rodauth is that you don’t need to worry about an update breaking any feature you are using, unlike when relying on third-party Devise extensions.
00:06:59.718 Hopefully, I’ve demonstrated that Rodauth ships with virtually all the authentication features that your application might need. One thing that Rodauth doesn't ship with, however, is OAuth support, which is intentional as Rodauth is designed for cases where you control the authentication process. In contrast, OAuth is set up for cases where a third party handles authentication. Many of Rodauth's features, such as changing passwords or locking out user accounts, would make no sense in an OAuth context. However, it’s entirely possible to use both Rodauth and OAuth in the same application if you want to support both authentication flows, keeping them separate, so they don’t conflict.
00:08:40.230 If you want to integrate OAuth support with Rodauth, there's a feature shipped as an external gem, currently named Rhoda OAuth, which will be rebranded to Rodauth OAuth in the next release. Next, I’m going to dive into security. Rodauth has a stronger security approach than other authentication frameworks, especially regarding password hash storage. The biggest problems with passwords are that people tend to choose weak ones and often reuse the same password across multiple sites.
00:09:26.990 This means that any popular site storing passwords becomes a target for hackers. Even if a site doesn’t hold other valuable information, if hackers gain access to stored password hashes, they can attempt to crack those hashes and then use the same email address and cracked password to access other higher-value websites. Most other authentication frameworks store password hashes in a column in the users table. With just one SQL injection vulnerability, hackers can gain access to the password hashes of all users, which they can then attempt to crack offline using dedicated password-cracking machines.
00:10:11.840 Rodauth, however, takes a different approach. By default, it stores password hashes in a separate table and restricts the application from reading that table directly. To verify a password, Rodauth calls a database function to retrieve the password hash salt for the account. It then uses that salt and the provided password to compute a password hash, and it calls another database function to check whether the computed hash matches the stored password hash. This methodology ensures that access is controlled via defined database functions, preventing opportunistic attacks.
00:10:51.250 Rodauth uses a technique called 'security definer' for its database functions, which is employed by default on PostgreSQL, MySQL, and Microsoft SQL Server. For compatibility reasons, Rodauth can also fully function in scenarios where database functions are impractical. Unlike other authentication frameworks that might use purely random tokens for tasks like password resets and account unlocks, Rodauth tokens incorporate the account identifier within them. This prevents brute-force attacks on all tokens for all accounts at once—since, with Rodauth, an attacker can only attempt to brute-force the token for a single account at a time. This token can also be compared using a constant-time string comparison.
00:11:43.170 Other frameworks typically use a database index for token lookups, which could be vulnerable to timing attacks. Rodauth supports and recommends the use of HMAC for enhanced security with tokens. This means that even if an attacker exploits an SQL injection vulnerability and accesses all tokens, those tokens will be unusable without knowledge of a separate secret that isn’t stored in the database. Rodauth centers its development around three goals: security, simplicity, and flexibility. I’ve covered the security aspects, so let’s move on to simplicity.
00:12:37.360 With most other authentication frameworks, configuring them requires making changes in several different places, including models, controllers, views, and routes—all of which are located separately. In contrast, Rodauth consolidates all authentication configuration within the same block in your application. When you load Rodauth, you provide a block used to configure all of Rodauth's features. Note that all the features I discussed earlier are optional—every feature that you want to use must be enabled explicitly. You enable Rodauth features using the 'config.enable' method within the block.
00:13:03.580 This simplicity makes configuration straightforward. For example, you might want to change the table name that stores password reset tokens or customize the subject line of the password reset email. After loading the password reset feature, you can call methods to override the default values for these settings. Rodauth comes with over a thousand such configuration methods, allowing you to customize virtually every aspect of every supported feature.
00:13:42.619 However, Rodauth does not stop there; it allows you to alter various parts of every supported feature to be request-specific. All of Rodauth’s configuration methods accept a block, enabling request-specific behaviors. For instance, if the accounts table has a column specifying whether an account is an admin, this feature permits using a distinct email subject that includes the IP address of the password reset request.
00:14:12.519 I want to emphasize the fact that all of Rodauth’s configuration methods support this feature, meaning that all configurations can be made request-specific. This flexibility empowers you to choose which features to activate while also overriding any aspect of the framework in a request-specific manner. If we look back at previous examples, you may have noticed that Rodauth was loaded using the 'plugin' method.
00:14:56.520 This is because Rodauth is implemented as a plugin for the Roda web toolkit. To this point, I’ve only illustrated how Rodauth is configured, but I haven't shown you how to actually use it yet. So let’s talk about that now. Roda is a web toolkit that uses a tree structure for routing requests, and with Roda, you create blocks for handling various routes. When a request is received, the route block is called, yielding the request object. The first line of the block calls the rodauth method on the request object, which handles all the routes added by Rodauth.
00:15:37.310 The second line calls the require_authentication method on the Rodauth object, which will redirect users to the login page if their account hasn’t been authenticated. This setup ensures that the entirety of the application is only accessible to sessions that have been authenticated. Although Roda is the fastest Ruby web framework with significant usage, it is still much less famous than Rails or Sinatra. Therefore limiting Rodauth to just Roda applications would be a shame. Thankfully, Roda includes a middleware plugin, allowing application framework compatibility with any other Rack application, including Sinatra or Rails.
00:16:18.300 When using Rodauth as middleware, it's advisable to set the Rodauth instance in the Rack environment so that the application can access the Rodauth instance. With Sinatra, integration can be very straightforward, while Rails used to involve more complexity. Thankfully, a maintainer of Roda recently released 'Rodauth Rails,' which integrates Rodauth with Rails. Thus, for Rails applications that require authentication options, incorporating Rodauth is now a similar level of difficulty as adding any other authentication framework.
00:17:04.780 In my biased opinion, it’s worth considering if you need features that other frameworks don't provide or if you value the added security and flexibility that Rodauth offers. In the previous example, we required authentication for all actions, but what if only certain sections of your application need authentication? Perhaps your main application is open, but only the admin section requires authentication. This is where Rodauth's routing tree shines. You can simply move the require_authentication call from the main route block to the admin-specific routing block.
00:17:47.510 This way, authentication will not be enforced for the branches of the routing tree that don’t require it. Additionally, what if you need different types of authentication across your application? For instance, you might want administrators to use WebAuthn security keys, while regular users have the option of either WebAuthn or TOTP for authentication. Let’s explore how to set this up in Rodauth. The configuration for regular users remains largely the same, but in this scenario, we enable both the WebAuthn and TOTP features. For the admin configuration, we must enable the WebAuthn feature separately.
00:18:38.649 In the admin configuration, we will use the prefix configuration method to set up that the routes for the admin configuration will be served under the 'admin' branch. It’s important to have multiple Rodauth configurations with different prefixes to avoid routing conflicts. Additionally, we will utilize a different session key for the admin configuration, ensuring that a regular user logging in won’t inadvertently gain access to the admin panel.
00:19:26.710 Within the admin routing tree, we first call the Rodauth routes for the admin configuration. Then, we require authentication for those routes. Since we’ve loaded a multi-factor authentication feature, calling require_authentication performs a dual check: if the user isn’t logged in, they will be redirected to the login page; if they have logged in but haven't authenticated using a second factor, they’ll be redirected to the multi-factor authentication page. This ensures that you enforce multi-factor authentication when it is enabled by the user.
00:20:01.700 After handling multi-factor authentication, we invoke require_two_factor_setup to redirect any users who haven’t set up their multi-factor authentication device yet, thereby preventing them from accessing the admin panel until they have completed the setup. For all other sections of the app apart from the admin section, we maintain the previous code to handle login redirects and multi-factor authentication checks but without enforcing multi-factor authentication on standard users.
00:20:54.330 I hope I’ve convinced you that Rodauth is Ruby’s most advanced authentication framework, and perhaps now you'll consider utilizing Rodauth for your authentication needs in future applications. If you’d like to experiment with Rodauth, there’s a demo site graciously hosted by Heroku. It may not be visually striking, but it will provide you a solid understanding of how Rodauth operates from the user's perspective. For further information, please visit the Rodauth website, where you’ll find comprehensive documentation, including details for every configuration method supported by Rodauth.
00:21:41.880 If you’re interested in getting involved with Rodauth, you can view the source code on GitHub and submit any issue reports for bugs you encounter or pull requests to fix bugs or introduce new features. That concludes my presentation. I’d like to thank you all very much for listening, and if you have any questions, I hope to answer them now. I’ll also be available to respond in the chat.
00:23:16.280 Yeah, I mean, the government department I work for heavily relies on Sequel, Roda, and Rodauth for all of our applications, allowing me to work on them during business hours. Most of the maintenance occurs during normal working hours, but I also spend a good portion of my personal time monitoring new bugs, issues, and requests—often checking in every few hours. It might sound like I'm not sleeping, but it doesn’t actually take that much of my time. I focus primarily on fixing bugs, with adding features as a secondary priority. Bug reports, after all, take precedence for everything I work on.
00:23:56.590 Thank you for answering that question. The next one is: do you know of any examples of large Rails applications that have transitioned to Rodauth from other libraries or solutions? Unfortunately, I don’t have specific examples, as Rodauth Rails has only recently been released—it came out just last month—I’m not aware of any developers making that switch yet. DJango, however, is personally working on a considerable Rails application that will be using Rodauth for all its authentication, which was among the reasons he created the Rodauth Rails integration.
00:24:44.569 The next question is: has your handling of Rodauth ever completely changed? Have there been significant evolutions in your views on solutions for certain problems? Regarding breaking changes—I'd say there hasn't been a complete 180-degree change, more like 60 degrees. There were significant shifts mostly before version 1.0, as Rodauth initially launched in 2015, while version 1.0 came out in 2016. Version 2.0 was released last month, and there were breaking changes, but mostly, those changes were related to new features like WebAuthn.
00:25:39.040 I believe the most substantial change in 1.0 was related to how password authentication was managed. In earlier releases, it had been executed entirely using database functions. However, with certain concerns—like password logging in databases—improvements were made in Rodauth 1.0 so that the password hash computation took place in the application without direct access to the hash itself. Consequently, 1.0 introduced two separate database functions: one for extracting the salt and another for validating the password hash.
00:26:12.040 In version 2.0, the changes did not break existing features but added enhancements. In summary, while there were breaking changes, they were manageable, and certainly not a radical overhaul. Thank you for that.
00:26:54.739 Lastly, the second most frequently asked question is: how do you maintain your personal interest in overseeing such a significant number of repositories over time? The truth is, I actively use the majority of the libraries I work on in production. Hence, that’s really the best way to sustain personal interest—being invested in what you create. I’ve also worked on libraries that I eventually stopped maintaining because I migrated to alternative solutions, but most of what I develop is tied to ongoing projects. Most of the software I work on has been in production since before its public release on RubyGems; I designed them around the needs of my applications.
00:27:37.940 I've been utilizing Sequel since 2008, and I have been using both Rodauth and Roda in production even before they were launched. Ultimately, it’s essential to have a personal or business need for the project, and in my situation, I have both.
00:27:51.860 Thank you for addressing all the inquiries! Sadly, we are out of time for questions. Thank you once more for your time.
00:28:09.620 Thank you, Jeremy! It's a great honor for us.
Explore all talks recorded at Pivorak Conf 5.0 - Online Edition