@@ -15,21 +15,19 @@ import (
15
15
// LoginOption is used to provide parameters that modify the default Credential Assertion Payload that is sent to the user.
16
16
type LoginOption func (* protocol.PublicKeyCredentialRequestOptions )
17
17
18
+ // DiscoverableUserHandler returns a *User given the provided userHandle.
19
+ type DiscoverableUserHandler func (rawID , userHandle []byte ) (user User , err error )
20
+
18
21
// Creates the CredentialAssertion data payload that should be sent to the user agent for beginning the
19
22
// login/assertion process. The format of this data can be seen in §5.5 of the WebAuthn specification
20
23
// (https://www.w3.org/TR/webauthn/#assertion-options). These default values can be amended by providing
21
24
// additional LoginOption parameters. This function also returns sessionData, that must be stored by the
22
25
// RP in a secure manner and then provided to the FinishLogin function. This data helps us verify the
23
26
// ownership of the credential being retreived.
24
27
func (webauthn * WebAuthn ) BeginLogin (user User , opts ... LoginOption ) (* protocol.CredentialAssertion , * SessionData , error ) {
25
- challenge , err := protocol .CreateChallenge ()
26
- if err != nil {
27
- return nil , nil , err
28
- }
29
-
30
28
credentials := user .WebAuthnCredentials ()
31
29
32
- if len (credentials ) == 0 { // If the user does not have any credentials, we cannot do login
30
+ if len (credentials ) == 0 { // If the user does not have any credentials, we cannot perform an assertion.
33
31
return nil , nil , protocol .ErrBadRequest .WithDetails ("Found no credentials for user" )
34
32
}
35
33
@@ -42,6 +40,20 @@ func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.
42
40
allowedCredentials [i ] = credentialDescriptor
43
41
}
44
42
43
+ return webauthn .beginLogin (user .WebAuthnID (), allowedCredentials , opts ... )
44
+ }
45
+
46
+ // BeginDiscoverableLogin begins a client-side discoverable login, previously known as Resident Key logins.
47
+ func (webauthn * WebAuthn ) BeginDiscoverableLogin (opts ... LoginOption ) (* protocol.CredentialAssertion , * SessionData , error ) {
48
+ return webauthn .beginLogin (nil , nil , opts ... )
49
+ }
50
+
51
+ func (webauthn * WebAuthn ) beginLogin (userID []byte , allowedCredentials []protocol.CredentialDescriptor , opts ... LoginOption ) (* protocol.CredentialAssertion , * SessionData , error ) {
52
+ challenge , err := protocol .CreateChallenge ()
53
+ if err != nil {
54
+ return nil , nil , err
55
+ }
56
+
45
57
requestOptions := protocol.PublicKeyCredentialRequestOptions {
46
58
Challenge : challenge ,
47
59
Timeout : webauthn .Config .Timeout ,
@@ -56,13 +68,13 @@ func (webauthn *WebAuthn) BeginLogin(user User, opts ...LoginOption) (*protocol.
56
68
57
69
newSessionData := SessionData {
58
70
Challenge : base64 .RawURLEncoding .EncodeToString (challenge ),
59
- UserID : user . WebAuthnID () ,
71
+ UserID : userID ,
60
72
AllowedCredentialIDs : requestOptions .GetAllowedCredentialIDs (),
61
73
UserVerification : requestOptions .UserVerification ,
62
74
Extensions : requestOptions .Extensions ,
63
75
}
64
76
65
- response := protocol.CredentialAssertion {requestOptions }
77
+ response := protocol.CredentialAssertion {Response : requestOptions }
66
78
67
79
return & response , & newSessionData , nil
68
80
}
@@ -105,6 +117,29 @@ func (webauthn *WebAuthn) ValidateLogin(user User, session SessionData, parsedRe
105
117
return nil , protocol .ErrBadRequest .WithDetails ("ID mismatch for User and Session" )
106
118
}
107
119
120
+ return webauthn .validateLogin (user , session , parsedResponse )
121
+ }
122
+
123
+ // ValidateDiscoverableLogin is an overloaded version of ValidateLogin that allows for discoverable credentials.
124
+ func (webauthn * WebAuthn ) ValidateDiscoverableLogin (handler DiscoverableUserHandler , session SessionData , parsedResponse * protocol.ParsedCredentialAssertionData ) (* Credential , error ) {
125
+ if session .UserID != nil {
126
+ return nil , protocol .ErrBadRequest .WithDetails ("Session was not initiated as a client-side discoverable login" )
127
+ }
128
+
129
+ if parsedResponse .Response .UserHandle == nil {
130
+ return nil , protocol .ErrBadRequest .WithDetails ("Client-side Discoverable Assertion was attempted with a blank User Handle" )
131
+ }
132
+
133
+ user , err := handler (parsedResponse .RawID , parsedResponse .Response .UserHandle )
134
+ if err != nil {
135
+ return nil , protocol .ErrBadRequest .WithDetails ("Failed to lookup Client-side Discoverable Credential" )
136
+ }
137
+
138
+ return webauthn .validateLogin (user , session , parsedResponse )
139
+ }
140
+
141
+ // validateLogin takes a parsed response and validates it against the user credentials and session data
142
+ func (webauthn * WebAuthn ) validateLogin (user User , session SessionData , parsedResponse * protocol.ParsedCredentialAssertionData ) (* Credential , error ) {
108
143
// Step 1. If the allowCredentials option was given when this authentication ceremony was initiated,
109
144
// verify that credential.id identifies one of the public key credentials that were listed in
110
145
// allowCredentials.
@@ -143,7 +178,7 @@ func (webauthn *WebAuthn) ValidateLogin(user User, session SessionData, parsedRe
143
178
// This is in part handled by our Step 1
144
179
145
180
userHandle := parsedResponse .Response .UserHandle
146
- if userHandle != nil && len (userHandle ) > 0 {
181
+ if len (userHandle ) > 0 {
147
182
if ! bytes .Equal (userHandle , user .WebAuthnID ()) {
148
183
return nil , protocol .ErrBadRequest .WithDetails ("userHandle and User ID do not match" )
149
184
}
0 commit comments