Skip to content
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

feat: webauthn: support discoverable credentials #601

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

dbarrosop
Copy link
Member

@dbarrosop dbarrosop commented Jan 3, 2025

PR Type

Enhancement


Description

  • Implement discoverable credentials for WebAuthn

  • Simplify WebAuthn sign-in request structure

  • Add support for discoverable login flow

  • Update WebAuthn user structure and handling


Changes walkthrough 📝

Relevant files
Enhancement
6 files
server.gen.go
Update generated server code for WebAuthn changes               
+66/-67 
types.gen.go
Remove UserHandle field from SignInWebauthnRequest             
+0/-122 
post_signin_webauthn.go
Implement discoverable login for WebAuthn                               
+21/-13 
post_signin_webauthn_verify.go
Add support for discoverable credentials verification       
+67/-2   
post_signup_webauthn.go
Update WebAuthn user structure for signup                               
+6/-4     
webauthn.go
Implement discoverable login methods for WebAuthn               
+71/-12 
Tests
2 files
post_signin_webauthn_test.go
Update tests for WebAuthn discoverable login                         
+34/-146
post_signup_webauthn_test.go
Update tests for WebAuthn signup changes                                 
+18/-12 
Documentation
2 files
index.html
Add HTML page for testing WebAuthn flows                                 
+194/-0 
openapi.yaml
Update OpenAPI spec for WebAuthn changes                                 
+0/-12   

💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

Copy link
Contributor

github-actions bot commented Jan 3, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Security Concern

The new implementation for discoverable credentials introduces more complex user handling logic. Careful review is needed to ensure proper authentication and authorization, especially in the PostSigninWebauthnVerifyUserHandle function.

func (ctrl *Controller) PostSigninWebauthnVerifyUserHandle(
	ctx context.Context,
	response *protocol.ParsedCredentialAssertionData,
	logger *slog.Logger,
) webauthn.DiscoverableUserHandler {
	return func(_, userHandle []byte) (webauthn.User, error) {
		// we need to encoide it back because the client treats it as a string including the hyphens
		b, err := json.Marshal(protocol.URLEncodedBase64(userHandle))
		if err != nil {
			return nil, fmt.Errorf("failed to marshal user handle: %w", err)
		}
		userID, err := uuid.Parse(string(b))
		if err != nil {
			return nil, fmt.Errorf("failed to parse user ID: %w", err)
		}

		keys, apiErr := ctrl.wf.GetUserSecurityKeys(ctx, userID, logger)
		if apiErr != nil {
			return nil, apiErr
		}

		creds, apiErr := webauthnCredentials(keys, logger)
		if apiErr != nil {
			return nil, apiErr
		}

		for i, userCreds := range creds {
			if bytes.Equal(response.RawID, userCreds.ID) {
				userCreds.Flags = webauthn.CredentialFlags{
					UserPresent:    response.Response.AuthenticatorData.Flags.UserPresent(),
					UserVerified:   response.Response.AuthenticatorData.Flags.UserVerified(),
					BackupEligible: response.Response.AuthenticatorData.Flags.HasBackupEligible(),
					BackupState:    response.Response.AuthenticatorData.Flags.HasBackupState(),
				}
				creds[i] = userCreds
			}
		}

		return WebauthnUser{
			ID:           userID,
			Name:         "",
			Email:        "",
			Credentials:  creds,
			Discoverable: true,
			UserHandle:   userHandle,
		}, nil
	}
}
Code Complexity

The changes to the WebauthnUser struct and related methods have increased the complexity of the webauthn flow. Ensure that all cases (discoverable and non-discoverable) are properly handled throughout the codebase.

type WebauthnUser struct {
	ID           uuid.UUID
	Name         string
	Email        string
	Credentials  []webauthn.Credential
	UserHandle   []byte
	Discoverable bool
}

func (u WebauthnUser) WebAuthnID() []byte {
	if u.Discoverable {
		return u.UserHandle
	}
	return []byte(u.ID.String())
}

func (u WebauthnUser) WebAuthnName() string {

Copy link
Contributor

github-actions bot commented Jan 3, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Score
Possible issue
Add proper error handling for JSON marshaling operation to prevent potential issues with unhandled errors

Consider adding error handling for the json.Marshal operation in the FinishLogin
function. If the marshaling fails, the current code continues execution without
addressing the error, which could lead to unexpected behavior.

go/controller/webauthn.go [215-222]

 b, err := json.Marshal(protocol.URLEncodedBase64(response.Response.UserHandle))
-if err == nil {
-    potentialUUID, err := uuid.Parse(string(b))
-    if err == nil && bytes.Equal(potentialUUID[:], challenge.User.ID[:]) {
-        response.Response.UserHandle = challenge.User.WebAuthnID()
-    }
+if err != nil {
+    logger.Error("failed to marshal UserHandle", logError(err))
+    return nil, WebauthnUser{}, ErrInternalServerError
+}
+potentialUUID, err := uuid.Parse(string(b))
+if err == nil && bytes.Equal(potentialUUID[:], challenge.User.ID[:]) {
+    response.Response.UserHandle = challenge.User.WebAuthnID()
 }
Suggestion importance[1-10]: 8

Why: The suggestion addresses a potential issue with error handling, which could lead to unexpected behavior. Proper error handling is crucial for robust and reliable code, especially in security-sensitive operations like WebAuthn.

8
Add validation to handle cases where both Email and UserHandle are nil in the request body

Consider adding error handling for the case when both Email and UserHandle are nil
in the request body. This would prevent potential nil pointer dereferences and
improve the robustness of the function.

go/controller/post_signin_webauthn.go [63-65]

+if request.Body.Email == nil && request.Body.UserHandle == nil {
+  return ctrl.sendError(ErrInvalidRequest), nil
+}
 if request.Body.Email == nil {
   return ctrl.postSigninWebauthnDiscoverableLogin(logger)
 }
Suggestion importance[1-10]: 7

Why: This suggestion improves error handling by explicitly checking for invalid input where both Email and UserHandle are nil. It prevents potential issues and improves the function's robustness.

7
Add a check for empty userHandle to prevent potential errors when parsing invalid user handles

Consider adding a check for empty userHandle before attempting to parse it as a
UUID. This would prevent potential panics or errors when dealing with invalid user
handles.

go/controller/post_signin_webauthn_verify.go [28-31]

+if len(userHandle) == 0 {
+  return nil, fmt.Errorf("empty user handle")
+}
 userID, err := uuid.Parse(string(b))
 if err != nil {
   return nil, fmt.Errorf("failed to parse user ID: %w", err)
 }
Suggestion importance[1-10]: 6

Why: This suggestion adds a safety check for empty userHandle before parsing it as a UUID. It helps prevent potential panics or errors when dealing with invalid user handles, improving the function's reliability.

6
General
Check for WebAuthn support in the browser before attempting to use it to prevent errors in unsupported environments

In the startSignin function, consider adding a check for browser support of WebAuthn
before attempting to use it. This can prevent potential errors in unsupported
browsers and provide a better user experience.

index.html [20-30]

 async function startSignin() {
+    if (!window.PublicKeyCredential) {
+        console.error('WebAuthn is not supported in this browser');
+        alert('Your browser does not support WebAuthn. Please use a modern browser.');
+        return;
+    }
     try {
         // First POST request to /signin/webauthn
         const initialResponse = await fetch('http://localhost:4000/signin/webauthn', {
             method: 'POST',
             headers: {
                 'Content-Type': 'application/json'
             },
             body: '{}'
         });
Suggestion importance[1-10]: 7

Why: This suggestion improves user experience by checking for WebAuthn support before attempting to use it. It prevents potential errors in unsupported browsers and provides clear feedback to users, enhancing the overall reliability of the application.

7

@dbarrosop dbarrosop self-assigned this Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant