| Summary: | Add passkey support to Koha as an authentication mechanism | ||
|---|---|---|---|
| Product: | Koha | Reporter: | David Nind <david> |
| Component: | Authentication | Assignee: | Paul Derscheid <paul.derscheid> |
| Status: | Failed QA --- | QA Contact: | Testopia <testopia> |
| Severity: | enhancement | ||
| Priority: | P5 - low | CC: | dcook, dpavlin, paul.derscheid |
| Version: | Main | Keywords: | dependency |
| Hardware: | All | ||
| OS: | All | ||
| GIT URL: | Initiative type: | --- | |
| Sponsorship status: | --- | Comma delimited list of Sponsors: | |
| Crowdfunding goal: | 0 | Patch complexity: | --- |
| Documentation contact: | Documentation submission: | ||
| Text to go in the release notes: | Version(s) released in: | ||
| Circulation function: | |||
| Attachments: |
Bug 39601: Atomic update
[DO NOT PUSH] Bug 39601: DBIC changes Bug 39601: Add staff passkeys (WebAuthn) support [DO NOT PUSH] Bug 39601: DBIC changes [DO NOT PUSH] Bug 39601: DBIC changes |
||
|
Description
David Nind
2025-04-10 10:16:59 UTC
See Koha Community Chat thread https://chat.koha-community.org/koha-community/pl/4irec4ocjtyy8pgezeaz9c9nwo Includes "It could also be a gateway to finally moving "password" out of the borrowers table and into a "credentials" table...". This is properly the best guidance on how to incorporate it: https://www.passkeys.com/guide What we also should do beforehand is to decide on how we'd like to do JSON-RPC in Koha :) See bugs: - 30652: RPC style action endpoint for choosing matches - 34784: Add ability to populate empty item call numbers for a record based on the itemcallnumber system preference - 38224: Move populate_empty_callnumbers logic into Koha::Biblio for reusability - 38226: Add RPC endpoint for batch filling empty callnumbers - 38745: Add a proof of concept JSON-RPC driven endpoint for RPC style calls that don't fit REST ideals Can't yet declare one of them as a dependency for this one. An article from Troy Hunt attempting to explain passkeys and why they are needed: Passkeys for Normal People (5 May 2025), https://www.troyhunt.com/passkeys-for-normal-people/ Created attachment 185295 [details] [review] Bug 39601: Atomic update Created attachment 185296 [details] [review] [DO NOT PUSH] Bug 39601: DBIC changes Created attachment 185297 [details] [review] Bug 39601: Add staff passkeys (WebAuthn) support Introduce staff passkey (WebAuthn) registration and authentication in Koha. Provides REST endpoints, persistence, UI hooks, and session integration so staff can register a passkey against a patron record and authenticate on the staff login screen using platform authenticators. Implementation: - New controller Koha::REST::V1::Webauthn with endpoints: - POST /api/v1/webauthn/register/challenge - POST /api/v1/webauthn/register - POST /api/v1/webauthn/authenticate/challenge - POST /api/v1/webauthn/authenticate - Use Authen::WebAuthn validate_registration/validate_assertion - Generate cryptographically secure challenges; store challenge and patron_id in the session - Handle base64url consistently for WebAuthn fields; convert to/from standard base64 at the API boundary - Derive origin and rp_id from StaffClientBaseURL (or the request URL) to enforce correct WebAuthn origins - Persist credentials (credential_id, public_key as raw bytes) in the new webauthn_credentials table; set created_on as DATETIME; update sign_count and last_used after successful authentication - Build allowCredentials from stored credential IDs for authentication challenges - On successful authentication, issue a staff session and set the CGISESSID cookie so the user is logged in to the staff interface API: - Add api/v1/swagger/paths/webauthn.yaml defining the WebAuthn endpoints, request/response schemas (including allowCredentials), and x-koha- authorization requiring the catalogue permission DB: - Add webauthn_credentials table via installer/data/mysql/atomicupdate/ bug_39601_add_passkey_support.pl - Add Koha::Schema::Result::WebauthnCredential and Koha::WebauthnCredential(s) object classes UI: - Staff login: add JS helper (auth-webauthn.inc) to request a challenge, convert base64url to bytes, call navigator.credentials.get, send results with credentials: "same-origin", and redirect to mainpage on success - Patron page: add JS helper (passkey-register.inc) to request a registration challenge, include RS256 in pubKeyCredParams for compatibility, convert base64url to bytes, and submit attestation for storage Tests: - t/db_dependent/api/v1/webauthn.t: verify challenge endpoints accept patron_id and userid; return 404 when no credentials; include a mocked registration negative path; follow Koha testing conventions - t/db_dependent/Koha/WebauthnCredentials.t: cover ORM add/search/update/delete Documentation notes: - StaffClientBaseURL must match the scheme/host used by the browser (typically https). Ensure staff is served over the same origin for WebAuthn. - New routes under /api/v1/webauthn and a new table webauthn_credentials. Test plan: 1) Apply patches 2) Run database updates to create webauthn_credentials (updatedatabase). 3) Set StaffClientBaseURL to your staff URL (e.g., http://<name>-intra.localhost when using ktd_proxy, haven't tested with unproxied ktd) and ensure the staff interface is served over the same origin. 4) As a staff user, open a patron record and click Register Passkey from More. Complete the OS-native passkey dialog. Verify a row is stored in webauthn_credentials. - This worked well in Zen (Firefox under the hood), less so with Chromium. - Unsure whether ungoogled Chromium supports using the system password manager, worked with a browser-based password manager, though. - Best to test with many browsers! 5) Navigate to the staff login page and choose Sign in with passkey. Verify a challenge is returned, the browser prompts, and you are logged into the staff interface (redirect to mainpage). 6) Call authenticate_challenge for a patron without credentials and verify a 404 response. 7) Run: - prove t/db_dependent/Koha/WebauthnCredential.t - prove t/db_dependent/api/v1/webauthn.t 8) Sign off or review and FQA. NOTE: I think the controllers still need refactoring, this is more a POC regarding the backend. Forgot something: you need to install libauthen-authn-perl via apt. (In reply to Paul Derscheid from comment #7) > Forgot something: you need to install libauthen-authn-perl via apt. I think that should be libauthen-webauthn-perl Yes, sorry, wrote that last minute from memory (which failed me 😅) * Apply patches
* sudo apt-get install libauthen-webauthn-perl
* updatedatabase
* yarn build
* restart_all
* ran dbic, but no changes
The QA checks and especially the unit tests failing. Am I missing something else?
prove t/db_dependent/api/v1/webauthn.t
t/db_dependent/api/v1/webauthn.t .. 1/4
# Failed test '404 Not Found'
# at t/db_dependent/api/v1/webauthn.t line 60.
# got: '500'
# expected: '404'
# Failed test '404 Not Found'
# at t/db_dependent/api/v1/webauthn.t line 63.
# got: '500'
# expected: '404'
# Looks like you failed 2 tests of 4.
t/db_dependent/api/v1/webauthn.t .. 2/4
# Failed test 'POST /api/v1/webauthn/authenticate/challenge returns 404 without credentials'
# at t/db_dependent/api/v1/webauthn.t line 67.
t/db_dependent/api/v1/webauthn.t .. 3/4 # Looks like you failed 1 test of 4.
t/db_dependent/api/v1/webauthn.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/4 subtests
Test Summary Report
-------------------
t/db_dependent/api/v1/webauthn.t (Wstat: 256 (exited 1) Tests: 4 Failed: 1)
Failed test: 2
Non-zero exit status: 1
Files=1, Tests=4, 5 wallclock secs ( 0.00 usr 0.00 sys + 5.69 cusr 0.14 csys = 5.83 CPU)
Result: FAIL
For the test plan, can you explain this step a bit more?
6) Call authenticate_challenge for a patron without credentials and verify a
404 response.
Thanks for testing Katrin, maybe something has changed in main. I will take a look this afternoon. Just took a look, applying the DBIC changes failed on my end, so that needs to be updated, but something else is broken as well. Will revisit this when I have an open time slot. Created attachment 190988 [details] [review] [DO NOT PUSH] Bug 39601: DBIC changes Created attachment 190989 [details] [review] [DO NOT PUSH] Bug 39601: DBIC changes I think the DBIC commit was incorrect in the previous iteration. Corrected it, tests pass now and the registration and authentication flows work in Chromium. Please have another look if you want, Katrin. Good news first: the tests are passing now! 1) Failing QA tests: Result: [FAIL] Koha/Auth/WebAuthn.pm FAIL forbidden_patterns forbidden pattern: Use https:// instead of http:// for URLs (line 17) FAIL pod_coverage sub generate_challenge has no pod coverage. sub new has no pod coverage. sub verify_assertion has no pod coverage. sub verify_registration has no pod coverage. [PASS] Koha/REST/V1/Auth.pm [FAIL] Koha/REST/V1/Webauthn.pm FAIL forbidden_patterns forbidden pattern: Use https:// instead of http:// for URLs (line 16) forbidden pattern: Use of DateTime->now should certainly be replaced with dt_from_string (bug 24840) (line 257) forbidden pattern: Use of DateTime->now should certainly be replaced with dt_from_string (bug 24840) (line 378) FAIL pod_coverage sub authenticate has no pod coverage. sub authenticate_challenge has no pod coverage. sub register has no pod coverage. sub register_challenge has no pod coverage. FAIL tidiness File is not tidy, please run `perl misc/devel/tidy.pl Koha/REST/V1/Webauthn.pm` [PASS] Koha/Schema/Result/Borrower.pm [FAIL] Koha/Schema/Result/WebauthnCredential.pm FAIL forbidden_patterns forbidden pattern: The current_timestamp syntax has not been generated correctly (see bug 25040) (line 108) SKIP pod_coverage No POD exists [SKIP] Koha/WebauthnCredential.pm SKIP pod_coverage No POD exists [FAIL] Koha/WebauthnCredentials.pm FAIL pod_coverage sub 'object_class' has no pod coverage. [PASS] api/v1/swagger/paths/webauthn.yaml [PASS] api/v1/swagger/swagger.yaml [FAIL] installer/data/mysql/atomicupdate/bug_39601_add_passkey_support.pl FAIL file_permissions File must have the exec flag [FAIL] koha-tmpl/intranet-tmpl/prog/en/includes/auth-webauthn.inc FAIL valid_template : filter not found [FAIL] koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc FAIL tidiness File is not tidy, please run `perl misc/devel/tidy.pl koha-tmpl/intranet-tmpl/prog/en/includes/members-toolbar.inc` FAIL valid_template : filter not found [FAIL] koha-tmpl/intranet-tmpl/prog/en/includes/passkey-register.inc FAIL tidiness File is not tidy, please run `perl misc/devel/tidy.pl koha-tmpl/intranet-tmpl/prog/en/includes/passkey-register.inc` FAIL valid_template : filter not found [PASS] koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt [FAIL] koha-tmpl/intranet-tmpl/prog/js/passkey-register.js FAIL tidiness File is not tidy, please run `perl misc/devel/tidy.pl koha-tmpl/intranet-tmpl/prog/js/passkey-register.js` [FAIL] koha-tmpl/intranet-tmpl/prog/js/webauthn-login.js FAIL tidiness File is not tidy, please run `perl misc/devel/tidy.pl koha-tmpl/intranet-tmpl/prog/js/webauthn-login.js` [PASS] members/moremember.pl [FAIL] t/db_dependent/Koha/WebauthnCredentials.t FAIL file_permissions File must have the exec flag FAIL test_no_warnings 'use Test::NoWarnings' must be present in this test file. [FAIL] t/db_dependent/api/v1/webauthn.t FAIL file_permissions File must have the exec flag FAIL forbidden_patterns forbidden pattern: Use https:// instead of http:// for URLs (line 73) 2) Code review a) Spelling We have: More > Register Passkey Create passkey (button) Probably should be: Register passkey b) Translatability Leading spaces usually get lost, better to add this to the i element if possible: + <i class="fa fa-key"></i> + <span class="d-none d-sm-inline"> Passkey</span> c) Using text in JS + var userid = usernameText ? usernameText.replace('Username:', '').trim() : null; I am wondering about this line: Where is "Username:" coming from and could this be a translatable strong, so changing? 3) Testing I should admit that I haven't used passkeys so far, so this is a first and I am still trying to figure out how this works. a) Ubuntu, Firefox When I click on "Register Passkey" I see a note at the top of the browser: Touch your security key to continue with localhost. Don't have a security key to touch here, so got stuck at this step. Returning to Koha I gott a non-descriptive error message after hitting the button: Registration error: {msg} Maybe related to a timeout? b) Ubuntu, Chromium When I click on "Register Passkey" I see a poo-up with a QR code displayed. I tried to scan it with Google Authenticator, but the app keeps dying. It will also display the non-descriptive error message: Registration error: {msg} 4) Questions a) Is there a way to continue testing here without any hardware? (Yubikey or similar?) b) For the test plan, can you explain this step a bit more? Call authenticate_challenge for a patron without credentials and verify a 404 response. |