Skip to content

Conversation

@deanyxu
Copy link
Contributor

@deanyxu deanyxu commented Aug 21, 2025

There is a bug in this check:
if a credential in the middle of session.AllowedCredentialIDs does not exist in credentials, credentialsOwned will be set to false, and validateLogin is supposed to return the error "User does not own all credentials from the allowedCredentialList". However, since the outer loop continues, if the last credential in session.AllowedCredentialIDs exist in credentials, credentialsOwned will be overwritten to True.

One option is to fix this, but I'd suggest that we remove this check entirely since

  1. It's not specified in the webauthn specs
  2. It's a bit redundant since both session.AllowedCredentialIDs and credentials are constructed on the server side

@deanyxu deanyxu requested a review from a team as a code owner August 21, 2025 23:51
@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

Moved per-credential ownership validation into the loop over session.AllowedCredentialIDs so ownership is checked per allowed credential; removed the post-loop ownership check. Added an integration-style test verifying FinishLogin errors when a session includes an allowed credential not owned by the user. No exported/public signatures changed.

Changes

Cohort / File(s) Summary of Changes
Login validation logic
webauthn/login.go
Ownership validation for each entry in session.AllowedCredentialIDs is performed inside the outer loop (early-return on mismatch); removed the final post-loop ownership check. Retains check that parsedResponse.RawID is among allowed IDs.
Integration test for failure path
webauthn/login_test.go
Adds TestFinishLoginFailure which constructs a session containing an extra allowed credential not owned by the user, crafts a WebAuthn response for that credential, and asserts FinishLogin returns a bad-request error indicating the user does not own the allowed credential. Adds imports: bytes, encoding/base64, fmt, io/ioutil, net/http.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Browser
  participant App as Relying Party
  participant WA as WebAuthn Service

  Browser->>App: send assertion (RawID + response)
  App->>WA: FinishLogin(request, session)
  note right of WA #DDEBF7: Ownership check moved\ninside loop over AllowedCredentialIDs
  WA->>WA: iterate session.AllowedCredentialIDs
  WA->>WA: verify ownership of current allowed credential
  alt credential not owned
    WA-->>App: error (user does not own allowed credential)
    App-->>Browser: auth failed
  else owned
    WA->>WA: check parsedResponse.RawID ∈ AllowedCredentialIDs
    alt RawID not allowed
      WA-->>App: error "User does not own the credential returned"
      App-->>Browser: auth failed
    else RawID allowed
      WA->>WA: continue assertion verification
      WA-->>App: success/failure result
      App-->>Browser: auth success/failure
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • james-d-elliott

Poem

A twitch, a hop, a nimble check,
Each ID inspected on the deck.
I nibble bugs, I guard the gate,
One-by-one — no bulkly fate.
Carrot earned, the login’s great! 🥕

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Member

@james-d-elliott james-d-elliott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test that fails before the change and passes after the change. Also ensure the check itself remains. The user returned must own all of the credential ID's present in the session's allowed credential ID's. This check mitigates potential vulnerabilities at the implementers end which could allow a login when the user is not correctly matched by domain logic.

@codecov
Copy link

codecov bot commented Aug 22, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 44.63%. Comparing base (a384137) to head (fc3088e).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #487      +/-   ##
==========================================
+ Coverage   43.82%   44.63%   +0.80%     
==========================================
  Files          34       34              
  Lines        3087     3087              
==========================================
+ Hits         1353     1378      +25     
+ Misses       1561     1533      -28     
- Partials      173      176       +3     
Files with missing lines Coverage Δ
webauthn/login.go 36.51% <100.00%> (+10.37%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
webauthn/login_test.go (5)

4-8: Replace deprecated ioutil with io.NopCloser; add errors import for stronger assertions

Use io.NopCloser instead of io/ioutil (deprecated since Go 1.16), and import errors to allow errors.As checks in the test below.

 import (
-	"bytes"
-	"encoding/base64"
-	"fmt"
-	"io/ioutil"
-	"net/http"
+	"bytes"
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
 	"testing"

148-152: Don’t ignore base64 decode errors; validate with require.NoError for deterministic failures

Ignoring decode errors can hide regressions if any test vector changes. Decode explicitly and assert.

-	var (
-		byteUserHandle, _       = base64.RawURLEncoding.DecodeString(userHandle)
-		byteID, _               = base64.RawURLEncoding.DecodeString(credentialID)
-		byteCredentialPubKey, _ = base64.RawURLEncoding.DecodeString("pQMmIAEhWCAoCF-x0dwEhzQo-ABxHIAgr_5WL6cJceREc81oIwFn7iJYIHEHx8ZhBIE42L26-rSC_3l0ZaWEmsHAKyP9rgslApUdAQI")
-		byteAAGUID, _           = base64.RawURLEncoding.DecodeString("rc4AAjW8xgpkiwsl8fBVAw")
-	)
+	byteUserHandle, err := base64.RawURLEncoding.DecodeString(userHandle)
+	require.NoError(t, err)
+	byteID, err := base64.RawURLEncoding.DecodeString(credentialID)
+	require.NoError(t, err)
+	byteCredentialPubKey, err := base64.RawURLEncoding.DecodeString("pQMmIAEhWCAoCF-x0dwEhzQo-ABxHIAgr_5WL6cJceREc81oIwFn7iJYIHEHx8ZhBIE42L26-rSC_3l0ZaWEmsHAKyP9rgslApUdAQI")
+	require.NoError(t, err)
+	byteAAGUID, err := base64.RawURLEncoding.DecodeString("rc4AAjW8xgpkiwsl8fBVAw")
+	require.NoError(t, err)

187-201: Prefer http.NewRequest, set Content-Type, use io.NopCloser, and close the body

Constructing the request via http.NewRequest better mirrors real usage, sets method/URL, and allows setting headers. Also switch to io.NopCloser and ensure the body is closed.

-	// build returned response from authenticator
-	reqBody := ioutil.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
+	// build returned response from authenticator
+	reqBody := io.NopCloser(bytes.NewReader([]byte(fmt.Sprintf(`{
 			"id":"%[1]s",
 			"rawId":"%[1]s",
 			"type":"public-key",
 			"response":{
 				"authenticatorData":"dKbqkhPJnC90siSSsyDPQCYqlMGpUKA5fyklC2CEHvBFXJJiGa3OAAI1vMYKZIsLJfHwVQMANwCOw-atj9C0vhWpfWU-whzNjeQS21Lpxfdk_G-omAtffWztpGoErlNOfuXWRqm9Uj9ANJck1p6lAQIDJiABIVggKAhfsdHcBIc0KPgAcRyAIK_-Vi-nCXHkRHPNaCMBZ-4iWCBxB8fGYQSBONi9uvq0gv95dGWlhJrBwCsj_a4LJQKVHQ",
 				"clientDataJSON":"eyJjaGFsbGVuZ2UiOiJFNFBUY0lIX0hmWDFwQzZTaWdrMVNDOU5BbGdlenROMDQzOXZpOHpfYzlrIiwibmV3X2tleXNfbWF5X2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgiLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwidHlwZSI6IndlYmF1dGhuLmdldCJ9",
 				"signature":"MEUCIBtIVOQxzFYdyWQyxaLR0tik1TnuPhGVhXVSNgFwLmN5AiEAnxXdCq0UeAVGWxOaFcjBZ_mEZoXqNboY5IkQDdlWZYc",
 				"userHandle":"%[2]s"
 			}
 		}`, credentialID, userHandle,
 	))))
-	httpReq := &http.Request{Body: reqBody}
+	httpReq, err := http.NewRequest(http.MethodPost, "https://webauthn.io/login", reqBody)
+	require.NoError(t, err)
+	httpReq.Header.Set("Content-Type", "application/json")
+	defer httpReq.Body.Close()

202-206: Avoid brittle string comparison; assert on protocol.Error type and Details

Comparing err.Error() strings is fragile. Assert the error type and details using errors.As.

-	expectedErr := protocol.ErrBadRequest.WithDetails("User does not own all credentials from the allowedCredentialList")
-	_, err := webauthn.FinishLogin(user, session, httpReq)
-	if err == nil || err.Error() != expectedErr.Error() {
-		t.Fatalf("FinishLogin() expected err=%v, got=%v", expectedErr, err)
-	}
+	_, err = webauthn.FinishLogin(user, session, httpReq)
+	require.Error(t, err)
+	var protoErr *protocol.Error
+	if errors.As(err, &protoErr) {
+		assert.Equal(t, protocol.ErrBadRequest.Type, protoErr.Type)
+		assert.Equal(t, "User does not own all credentials from the allowedCredentialList", protoErr.Details)
+	} else {
+		t.Fatalf("FinishLogin() expected protocol.Error, got %T: %v", err, err)
+	}

187-199: Optional: build the JSON with encoding/json to avoid formatting pitfalls

Using fmt.Sprintf with multi-line JSON is error-prone if fields change. Consider defining a small struct that matches the expected input and json.Marshal it to keep escaping/quoting correct.

If you want, I can provide a follow-up patch that introduces a helper to generate these request bodies from data structs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 73c5084 and fc3088e.

📒 Files selected for processing (2)
  • webauthn/login.go (1 hunks)
  • webauthn/login_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webauthn/login.go
🧰 Additional context used
🧬 Code graph analysis (1)
webauthn/login_test.go (5)
webauthn/credential.go (1)
  • Credential (15-36)
webauthn/authenticator.go (1)
  • Authenticator (7-26)
webauthn/types.go (3)
  • SessionData (209-220)
  • WebAuthn (24-26)
  • Config (29-76)
protocol/errors.go (2)
  • ErrBadRequest (47-50)
  • Error (3-15)
webauthn/registration_test.go (1)
  • TestRegistration_FinishRegistrationFailure (101-120)
🔇 Additional comments (2)
webauthn/login_test.go (2)

171-177: Good negative test shape to catch the original loop bug

Placing a foreign ID ([]byte("test")) before a valid ID in AllowedCredentialIDs mirrors the overwrite bug described in the report and ensures we fail fast on ownership mismatches.


142-147: Clarify intent: ownership check still in place but PR proposes its removal

The test in webauthn/login_test.go (lines 174–205) expects FinishLogin to return ErrBadRequest when session.AllowedCredentialIDs contains an ID not owned by the user, and the implementation in webauthn/login.go (lines 331–346) still enforces this ownership validation. However, the PR title/description indicates that this check will be removed entirely. Please confirm which behavior is intended and adjust accordingly:

  • If we keep the ownership validation:
    • Update the PR title/summary to remove any mention of dropping this check.
  • If we remove the ownership validation:
    • Delete the block in webauthn/login.go (lines 331–346) that returns ErrBadRequest for unowned credentials.
    • Update or remove the corresponding test in webauthn/login_test.go (lines 174–205) so it no longer expects this error.

@deanyxu
Copy link
Contributor Author

deanyxu commented Aug 22, 2025

Please add a test that fails before the change and passes after the change. Also ensure the check itself remains. The user returned must own all of the credential ID's present in the session's allowed credential ID's. This check mitigates potential vulnerabilities at the implementers end which could allow a login when the user is not correctly matched by domain logic.

I have updated the change to only fixing the bug. Also added a test case for this. Before the change, test case would fail

$ go test -v -run 'TestFinishLoginFailure' ./webauthn/...
 INFO  running /usr/local/go/bin/go test -v -run 'TestFinishLoginFailure' ./webauthn/...

=== RUN   TestFinishLoginFailure
    login_test.go:205: FinishLogin() expected err=User does not own all credentials from the allowedCredentialList, got=<nil>
--- FAIL: TestFinishLoginFailure (0.01s)
FAIL
FAIL    github.com/go-webauthn/webauthn/webauthn        0.988s
FAIL

After the change in login.go

$ go test -v -run 'TestFinishLoginFailure' ./webauthn/...
 INFO  running /usr/local/go/bin/go test -v -run 'TestFinishLoginFailure' ./webauthn/...

=== RUN   TestFinishLoginFailure
--- PASS: TestFinishLoginFailure (0.01s)
PASS
ok      github.com/go-webauthn/webauthn/webauthn        0.958s

@deanyxu deanyxu changed the title fix(webauthn): remove a check for the full allowedCredentials in validateLogin fix(webauthn): fix check for the full allowedCredentials in validateLogin Aug 22, 2025
Copy link
Member

@james-d-elliott james-d-elliott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@james-d-elliott james-d-elliott merged commit 9410f91 into go-webauthn:master Aug 24, 2025
12 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Aug 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants