Bug 39601

Summary: Add passkey support to Koha as an authentication mechanism
Product: Koha Reporter: David Nind <david>
Component: AuthenticationAssignee: Paul Derscheid <paul.derscheid>
Status: Needs Signoff --- QA Contact: Testopia <testopia>
Severity: enhancement    
Priority: P5 - low CC: dcook, dpavlin, paul.derscheid
Version: Main   
Hardware: All   
OS: All   
GIT URL: Change sponsored?: ---
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

Description David Nind 2025-04-10 10:16:59 UTC
Consider adding passkey support to Koha as an authentication mechanism.

I have seen this method as an option more frequently now with all the various online services I use.

Some articles:
1. Bitwarden article - What are passkeys? https://bitwarden.com/blog/what-are-passkeys-and-passkey-login/
2. tom's guide - What are passkeys? Everything you need to know about the death of passwords https://www.tomsguide.com/news/what-are-passkeys
Comment 1 David Nind 2025-04-10 10:19:10 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...".
Comment 2 Paul Derscheid 2025-04-10 10:22:43 UTC
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.
Comment 3 David Nind 2025-05-11 17:44:51 UTC
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/
Comment 4 Paul Derscheid 2025-08-10 16:23:06 UTC
Created attachment 185295 [details] [review]
Bug 39601: Atomic update
Comment 5 Paul Derscheid 2025-08-10 16:23:08 UTC
Created attachment 185296 [details] [review]
[DO NOT PUSH] Bug 39601: DBIC changes
Comment 6 Paul Derscheid 2025-08-10 16:23:10 UTC
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.
Comment 7 Paul Derscheid 2025-08-10 16:30:57 UTC
Forgot something: you need to install libauthen-authn-perl via apt.
Comment 8 David Cook 2025-08-10 23:13:57 UTC
(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
Comment 9 Paul Derscheid 2025-08-11 06:28:02 UTC
Yes, sorry, wrote that last minute from memory (which failed me 😅)