-
Notifications
You must be signed in to change notification settings - Fork 265
Username-less passkey authentication #1268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request aims to introduce usernameless (discoverable credential) passkey authentication support. The changes include updates to the service layer, WebAuthn interface, validation logic, authentication flow definitions, and test coverage.
Changes:
- Added new WebAuthn service methods (
BeginDiscoverableLoginandValidatePasskeyLogin) to support discoverable credentials - Modified the passkey service to detect empty userID and handle usernameless authentication flows, including userID resolution from userHandle
- Removed userID validation requirement to allow empty userID for usernameless authentication
- Added prerequisite skipping logic for optional prerequisites in flow execution
- Added usernameless passkey authentication flow definition
- Updated tests to cover usernameless authentication scenarios
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/cmd/server/bootstrap/flows/authentication/auth_flow_usernameless_passkey.json | New authentication flow definition for usernameless passkey authentication |
| backend/internal/authn/passkey/model.go | Added omitempty JSON tag to UserID field to support optional userID |
| backend/internal/authn/passkey/service.go | Added usernameless flow detection and handling in StartAuthentication and FinishAuthentication methods |
| backend/internal/authn/passkey/webauthn_service.go | Added BeginDiscoverableLogin and ValidatePasskeyLogin methods to support discoverable credentials |
| backend/internal/authn/passkey/utils.go | Removed userID validation requirement from authentication start request validation |
| backend/internal/authn/passkey/service_test.go | Updated test to expect usernameless authentication to succeed with empty userID |
| backend/internal/flow/core/executor.go | Added logic to skip optional prerequisites during validation |
| backend/internal/flow/executor/passkey_executor_test.go | Updated test to verify usernameless authentication succeeds when userID is not provided |
| tests/integration/authn/passkey_auth_test.go | Updated integration test to verify usernameless authentication with empty userID |
| userHandler := func(rawID, userHandle []byte) (webauthnUserInterface, error) { | ||
| // The user has already been resolved and validated above | ||
| // Just return the webAuthnUser we created | ||
| return webAuthnUser, nil | ||
| } |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The userHandler implementation for usernameless authentication does not correctly use the rawID and userHandle parameters provided by the WebAuthn library. Instead, it returns a pre-resolved webAuthnUser that was created earlier (lines 439-467). This means the library cannot validate that the credential's userHandle matches the authenticated user, creating a potential security vulnerability.
The userHandler should use the rawID and userHandle parameters to look up the user from the database and return a webAuthnUser based on those credentials. The current implementation defeats the purpose of ValidatePasskeyLogin, which is designed to resolve the user from the credential itself.
| func (suite *PasskeyAuthExecutorTestSuite) TestExecuteChallenge_MissingUserID() { | ||
| ctx := createPasskeyNodeContext(passkeyExecutorModeChallenge, common.FlowTypeAuthentication) | ||
| // Not setting userID in RuntimeData | ||
| // Not setting userID in RuntimeData - this triggers usernameless flow | ||
|
|
||
| expectedStartData := &passkey.PasskeyAuthenticationStartData{ | ||
| SessionToken: testSessionToken, | ||
| PublicKeyCredentialRequestOptions: passkey.PublicKeyCredentialRequestOptions{ | ||
| Challenge: "dGVzdC1jaGFsbGVuZ2U=", | ||
| }, | ||
| } | ||
|
|
||
| // Mock passkey service for usernameless authentication (empty UserID) | ||
| suite.mockPasskeyService.On("StartAuthentication", mock.MatchedBy( | ||
| func(req *passkey.PasskeyAuthenticationStartRequest) bool { | ||
| return req.UserID == "" && req.RelyingPartyID == testRelyingPartyID | ||
| })).Return(expectedStartData, nil) | ||
|
|
||
| resp, err := suite.executor.Execute(ctx) | ||
|
|
||
| // Usernameless flow should succeed | ||
| assert.NoError(suite.T(), err) | ||
| assert.NotNil(suite.T(), resp) | ||
| assert.Equal(suite.T(), common.ExecFailure, resp.Status) | ||
| assert.Contains(suite.T(), resp.FailureReason, "User ID is required") | ||
| assert.Equal(suite.T(), common.ExecComplete, resp.Status) | ||
| assert.Equal(suite.T(), testSessionToken, resp.RuntimeData[runtimePasskeySessionToken]) | ||
| assert.NotEmpty(suite.T(), resp.AdditionalData[runtimePasskeyChallenge]) | ||
| } |
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test mocks ValidatePrerequisites to always return true (see line 121 in the test setup), which bypasses the actual prerequisite validation logic. This means the test doesn't verify whether usernameless authentication would work with the real prerequisite validation. In production, the passkey executor has a userID prerequisite marked as Required: true, which would cause the validation to fail when userID is empty. The test should either:
- Test with the real ValidatePrerequisites logic to ensure the prerequisite is properly marked as optional, or
- Mock the prerequisite configuration to mark userID as optional for usernameless flow testing.
Without this, the test gives false confidence that usernameless authentication works when it may fail in production.
| // Skip optional prerequisites | ||
| if !prerequisite.Required { | ||
| continue | ||
| } | ||
|
|
Copilot
AI
Feb 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description states "These changes collectively enable usernameless passkey authentication" and the tests suggest it works, but critical code in the executor layer (passkey_executor.go) was not updated to support usernameless flow:
- The userID prerequisite is still marked as
Required: truein the executor, which will cause prerequisite validation to fail when userID is empty - The
executeChallengemethod still explicitly checks for empty userID and returns a failure
These issues are not visible in the test results because the tests mock ValidatePrerequisites to always return true, bypassing the real validation logic. While the service layer properly supports usernameless authentication, the flow execution path will fail.
For usernameless authentication to work through flow execution, the executor code needs to be updated to mark the userID prerequisite as optional and remove the userID validation check in executeChallenge.
a8bbe42 to
d81c8dd
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1268 +/- ##
==========================================
- Coverage 89.12% 88.81% -0.31%
==========================================
Files 622 622
Lines 40357 40441 +84
Branches 2371 2371
==========================================
- Hits 35967 35918 -49
- Misses 2328 2396 +68
- Partials 2062 2127 +65
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Purpose
This pull request introduces support for usernameless (discoverable credential) passkey authentication, allowing users to authenticate without providing a username or user ID. The changes include updates to the authentication flow, service logic, validation, and comprehensive test coverage to ensure usernameless authentication works correctly alongside traditional username-based flows.
Usernameless Passkey Authentication Support
authentication/auth_flow_usernameless_passkey.json).passkeyServicelogic to detect when the user ID is empty and initiate usernameless authentication using discoverable credentials, including handling both the challenge and verification steps. [1] [2] [3] [4]BeginDiscoverableLoginandValidatePasskeyLoginmethods. [1] [2] [3]Validation and Flow Improvements
validateAuthenticationStartRequest.Testing and Test Coverage
Other Notable Changes
These changes collectively enable usernameless passkey authentication, improve the flexibility of authentication flows, and ensure robust test coverage for both usernameless and traditional username-based authentication.
Related Issues
Related PRs
Checklist
breaking changelabel added.Security checks