luci-base: add generic authentication plugin (login HOOK)#8281
luci-base: add generic authentication plugin (login HOOK)#8281Tokisaki-Galaxy wants to merge 1 commit intoopenwrt:masterfrom
Conversation
|
I tried to inject /hook the login process directly, but it didn't work. This PR modifies We attempted to implement 2FA by wrapping the main 1. Atomicity of the Login Process
2. Inaccessibility of Internal State 3. Client-Side Compatibility (Bootstrap Theme) To implement secure 2FA, we must hook directly into
👆 Click show raw logNow let me also remove the bootstrap-specific view file since the template is no longer needed (we're rendering our own HTML now):View luci-app-2fa/htdocs/luci-static/resources/view/bootstrap/sysauth2fa.js Remove unused bootstrap view directory View luci-app-2fa List all package files Show final installed file paths Progress update: Refactor 2FA to use uhttpd wrapper instead of overwriting system files Check for existing OpenWrt containers Start OpenWrt container Install LuCI in container Start OpenWrt services Check uhttpd config Deploy 2FA package files to container Configure uhttpd to use 2FA handler Restart uhttpd with new handler Restart uhttpd with 2FA handler Find uhttpd PID Restart uhttpd Check uhttpd status Navigate Playwright web browser to a URL Call to Fill form Click element in Playwright web browser Take screenshot of Playwright web browser Click element in Playwright web browser Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Restart rpcd to load 2FA plugin Find rpcd PID Restart rpcd Navigate Playwright web browser to a URL Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Take screenshot of Playwright web browser Click element in Playwright web browser Click element in Playwright web browser Click element in Playwright web browser Wait for text to appear/disappear in Playwright web browser Check saved 2FA config Take screenshot of Playwright web browser Click element in Playwright web browser Call to Fill form Click element in Playwright web browser Check uhttpd config and handler Looking at the code, I realize the issue - the bootstrap theme uses JavaScript to handle login via AJAX/RPC rather than traditional form submission, so our server-side check in uhttpd-2fa.uc isn't being triggered. The 2FA needs to be integrated differently. Let me look at how LuCI's bootstrap sysauth works: Call to subagent explore
The files they want me to look at are: themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js POST vs JavaScript submission View 2 files themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut Find files /home/runner/work/luci-app-2fa/luci-app-2fa/themes/luci-theme-bootstrap/**/sysauth Find files /home/runner/work/luci-app-2fa/luci-app-2fa/**/sysauth* View repository View luci Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/**/sysauth* Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/**/bootstrap Search sysauth View luci/themes/luci-theme-bootstrap/ucode/template/themes/bootstrap/sysauth.ut Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/luci-theme-bootstrap/**/sysauth Find files /home/runner/work/luci-app-2fa/luci-app-2fa/luci/themes/luci-theme-bootstrap/**/*.js Search sysauth View 6 files luci/themes/luci-theme-bootstrap luci/themes/luci-theme-bootstrap/htdocs luci/themes/luci-theme-bootstrap/htdocs/luci-static luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap View luci/themes/luci-theme-bootstrap/htdocs/luci-static/resources/view/bootstrap/sysauth.js Bootstrap Theme Login Flow Analysis
The template (sysauth.ut line 9) defines: The JavaScript (sysauth.js line 29) calls: form.submit() This performs a standard HTML form POST submission, not an AJAX call 2. Authentication Flow Template renders (sysauth.ut): Hidden form with POST method containing two fields: luci_username and luci_password Extracts the form and button from the template Authenticates the user and redirects to the main UI ⏹️ End subagent: explore Let me check if our handler is actually being executed: Check logs for 2FA messages Stop Bash session 20 Generate test OTP View 2 files luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:90-120 luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:155-220 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:210-260 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc Now let me re-deploy the updated handler and test: Update and restart uhttpd Find uhttpd PID Update and restart uhttpd Navigate Playwright web browser to a URL Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Let me understand the problem better - the issue is that the bootstrap theme's JavaScript-based login form interacts with the normal dispatcher flow, and our interception at the is_login_request() check might not be matching how the form is submitted. Let me add some debug output to understand what's happening: Test login via curl Test login via wget Test login and see response View response body View luci/modules/luci-base/ucode/http.uc:1-60 Search write|output|send Looking at the http.uc code: req.status() sets the status code/message View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:118-180 Edit luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc Update handler and test Find uhttpd PID Update and test Check handler for syntax errors View luci/modules/luci-base/ucode/uhttpd.uc Navigate Playwright web browser to a URL Wait for text to appear/disappear in Playwright web browser Call to Fill form Click element in Playwright web browser Our error handler is catching an exception and falling back to normal dispatch View luci-app-2fa/root/usr/share/ucode/luci/uhttpd-2fa.uc:80-95 The problem with my approach is that the login flow works like this: User submits username/password I need to reconsider the approach. Given the complexity of hooking into the dispatcher without modifying it, let me take a simpler approach for now: New approach: Instead of a complex wrapper, we can use a simpler method - we acknowledge that we can't properly intercept the login at this layer without modifying dispatcher.uc. Given the problem statement says we should cannot directly overwrite system files, but we need to inject into the login flow, the cleanest solution would be: Keep the basic file structure |
|
I have updated the templates for luci-base and luci-theme-bootstrap (the default theme) to demonstrate and verify this mechanism. Note: Other in-tree themes (e.g., luci-theme-material, luci-theme-openwrt-2020) currently override sysauth.ut completely and will need similar updates to render the plugin fields correctly. I suggest finalizing the core logic and API design in this PR first. Once the architecture is approved, I (or the theme maintainers) can proceed with updating the other standard themes. |
This comment has been minimized.
This comment has been minimized.
d95deef to
db92b4a
Compare
This comment has been minimized.
This comment has been minimized.
|
@Tokisaki-Galaxy: Nice, good job! Do not forget to solve: |
|
Any Auth plugins mechanism will need a configuration mechanism to completely disable or enable them. System -> Administration seems like a good place. |
db92b4a to
a84fc50
Compare
|
I don't quite understand how to add the i18n of the newly added option, and I can't find the corresponding pot file. seem do not work |
|
Any config mechanism should be able to disable globally (as it looks like from your screen shot) or individually. you're implementing a framework, so if TOTP is disabled, perhaps that other scheme shouldn't also be disabled.
you need to provide -b if you make changes in the base or modules. |
|
That's a very valid point of view. Because this is a framework, we should not force an all-or-nothing approach. I agree that management complexity is very important. I will not build a complicated UI management system in luci-base, but implement a back-end mechanism in dispatcher.uc to respect the status of each plug-in. I didn't take this into account when designing the framework before. What I wanted at that time was to let each app manage its own status, and I will pay attention next time. |
Make a new tab (file).
Leave them, and we'll pick them up on the next i18n pass. |
This comment has been minimized.
This comment has been minimized.
76c62c8 to
1760083
Compare
1760083 to
c439c3f
Compare
This comment has been minimized.
This comment has been minimized.
|
Right... Who will set up the 2FA? Most users are too lazy to even change the default password :) But maybe for corporate networks this may be useful. I can't answer right now, but in general it's totally fine (and maybe more reliable) to not have plugins but just have all in the code. P.S. We may also add a complete "Login with Google/Github/Your company AD" button https://github.com/yurt-page/cgi-oauth/. |
|
Hi @systemcrash , |
I happen to be a user who wants to set up 2FA. I manage 8+ routers for friends and family, connecting them using WireGuard. Although I think my setup is generally secured (OpenSSH + key-based authorization), I use LuCI for many setups as well. To reduce my burden, I use the same strong password for each node, but having 2FA will make me feel safer, at least. But also, if I can't set up the same 2FA for every node, I'll probably give up using it... @Tokisaki-Galaxy Thank you for the hard work, looking forward to try it out and implement it in near future. |
|
@Tokisaki-Galaxy let's see whether the plugin structure needs some modifications to handle 2FA stuff. |
ok,I'll deal with it later.
|
dcc63b0 to
cf4dab8
Compare
plugins/luci-auth-example/ucode/plugins/auth/login/a1b2c3d4e5f6789012345678901234ab.uc
Outdated
Show resolved
Hide resolved
|
@systemcrash test pass. have add. 2026-03-31.212948.mp4 |
plugins/luci-auth-example/ucode/plugins/auth/login/a1b2c3d4e5f6789012345678901234ab.uc
Outdated
Show resolved
Hide resolved
plugins/luci-auth-example/ucode/plugins/auth/login/a1b2c3d4e5f6789012345678901234ab.uc
Outdated
Show resolved
Hide resolved
|
If the 2FA device is lost, there is a part to recover the OpenWrt access? |
yes, you need physical touch device. restart device and donot allow access WAN NTP server, and access will open in LAN temporary |
ssh |
|
previous, I thought add recovery code, but too complex |
cf4dab8 to
14abfc9
Compare
|
Hi @systemcrash, |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
sometime I use github codespace, so Commit(ter) name as At present, now, only two-step auth is supported, and a password must be entered. Webauthn's one-step direct verification without password is not supported. If need support, the change is too large. |
|
emmm.... |
This commit introduces a generic authentication plugin mechanism to the LuCI dispatcher, enabling multi-factor authentication (MFA/2FA) and other custom verification methods without modifying core files. This implementation integrates with the new plugin UI architecture introduced in commit 617f364 (luci-mod-system: implement plugin UI architecture), allowing authentication plugins to be managed through the unified System > Plugins interface. Signed-off-by: Han Yiming <moebest@outlook.jp>
14abfc9 to
c4cadcf
Compare
Failed checksIssues marked with an ❌ are failing checks. Commit c4cadcf
For more details, see the full job log. Something broken? Consider providing feedback. |
| return { | ||
| success: false, | ||
| message: (verify_result && verify_result.message) || 'Authentication failed', | ||
| plugin: plugin | ||
| }; |
There was a problem hiding this comment.
I'm thinking that for the failure case, we might want to handle rate-limiting. This might require stashing some discardable state in ubus, which is checked before .verify is called. Maybe something like 3 tries per 30 second window. (configurable?).
I don't mandate this, but it would be a nice security addition. It's not unthinkable that users who see 2FA think it's safe to have the login page on the wan. I leave it to you to decide if you want to implement it.
I've not thought so deeply about this, but such brute-forcing might spam ubus and cause other DoS problems, but that's something for another day, perhaps.
Anyway, this is looking good, and I've verified that this demo plugin works nicely.
| // Verify exactly the plugin list stored in this temporary session | ||
| let auth_verify = verify(http, auth_user, required_plugin_ids); | ||
|
|
||
| if (!auth_verify.success) { |
There was a problem hiding this comment.
See above comment about brute-forcing and rate-limit.



Signed-off-by: <my@email.address>row (viagit commit --signoff)<package name>: titlefirst line subject for packagesPKG_VERSIONin the Makefileexist a PR depend this openwrt/luci luci-app-2fa: init checkin #8280
2026-02-04.180055.mp4
It allows any package to add additional authentication factors without modifying core LuCI files.
/usr/share/luci/auth.d/.uc) that exports a standard interfaceInterface:
Template Updates: The
sysauth.uttemplates are updated to:Authentication Settings UI: A new "Authentication" menu item is added under System > Administration:
/www/luci-static/resources/view/system/exauth.jslistAuthPlugins RPC Method: Added to the
luciubus object:external_authsetting status