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: discoverable login type #1

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions webauthn/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,50 @@ import (
// LoginOption is used to provide parameters that modify the default Credential Assertion Payload that is sent to the user.
type LoginOption func(*protocol.PublicKeyCredentialRequestOptions)

type DiscoverableUserHandler func(userHandle []byte) (user *User, err error)

// Creates the CredentialAssertion data payload that should be sent to the user agent for beginning the
// login/assertion process. The format of this data can be seen in §5.5 of the WebAuthn specification
// (https://www.w3.org/TR/webauthn/#assertion-options). These default values can be amended by providing
// additional LoginOption parameters. This function also returns sessionData, that must be stored by the
// RP in a secure manner and then provided to the FinishLogin function. This data helps us verify the
// ownership of the credential being retreived.
func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.beginLogin(&user, opts...)
}

// BeginDiscoverableLogin begins a client-side discoverable login, previously known as Resident Key logins.
func (webauthn *WebAuthn) BeginDiscoverableLogin(opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
return webauthn.beginLogin(nil, opts...)
}

func (webauthn *WebAuthn) beginLogin(u *User, opts ...LoginOption) (*protocol.CredentialAssertion, *SessionData, error) {
challenge, err := protocol.CreateChallenge()
if err != nil {
return nil, nil, err
}

credentials := user.WebAuthnCredentials()
var allowedCredentials []protocol.CredentialDescriptor
var userID []byte
if u != nil {
user := *u

if len(credentials) == 0 { // If the user does not have any credentials, we cannot do login
return nil, nil, protocol.ErrBadRequest.WithDetails("Found no credentials for user")
}
credentials := user.WebAuthnCredentials()

if len(credentials) == 0 { // If the user does not have any credentials, we cannot do login
return nil, nil, protocol.ErrBadRequest.WithDetails("Found no credentials for user")
}

var allowedCredentials = make([]protocol.CredentialDescriptor, len(credentials))
allowedCredentials = make([]protocol.CredentialDescriptor, len(credentials))

for i, credential := range credentials {
var credentialDescriptor protocol.CredentialDescriptor
credentialDescriptor.CredentialID = credential.ID
credentialDescriptor.Type = protocol.PublicKeyCredentialType
allowedCredentials[i] = credentialDescriptor
for i, credential := range credentials {
var credentialDescriptor protocol.CredentialDescriptor
credentialDescriptor.CredentialID = credential.ID
credentialDescriptor.Type = protocol.PublicKeyCredentialType
allowedCredentials[i] = credentialDescriptor
}

userID = user.WebAuthnID()
}

requestOptions := protocol.PublicKeyCredentialRequestOptions{
Expand All @@ -56,13 +75,13 @@ func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.

newSessionData := SessionData{
Challenge: base64.RawURLEncoding.EncodeToString(challenge),
UserID: user.WebAuthnID(),
UserID: userID,
AllowedCredentialIDs: requestOptions.GetAllowedCredentialIDs(),
UserVerification: requestOptions.UserVerification,
Extensions: requestOptions.Extensions,
}

response := protocol.CredentialAssertion{requestOptions}
response := protocol.CredentialAssertion{Response: requestOptions}

return &response, &newSessionData, nil
}
Expand Down Expand Up @@ -99,6 +118,19 @@ func (webauthn *WebAuthn) FinishLogin(user User, session SessionData, response *
return webauthn.ValidateLogin(user, session, parsedResponse)
}

func (webauthn *WebAuthn) ValidateDiscoverableLogin(handler DiscoverableUserHandler, session SessionData, parsedResponse *protocol.ParsedCredentialAssertionData) (*Credential, error) {
if parsedResponse.Response.UserHandle == nil {
return nil, protocol.ErrBadRequest.WithDetails("Client-side Discoverable Assertion was attempted with a blank User Handle")
}

user, err := handler(parsedResponse.Response.UserHandle)
if err != nil {
return nil, protocol.ErrBadRequest.WithDetails("Failed to lookup Client-side Discoverable Credential")
}

return webauthn.ValidateLogin(*user, session, parsedResponse)
}

// ValidateLogin takes a parsed response and validates it against the user credentials and session data
func (webauthn *WebAuthn) ValidateLogin(user User, session SessionData, parsedResponse *protocol.ParsedCredentialAssertionData) (*Credential, error) {
if !bytes.Equal(user.WebAuthnID(), session.UserID) {
Expand Down