@@ -11,17 +11,15 @@ import (
1111 "github.com/koesie10/webauthn/protocol"
1212)
1313
14- // StartLogin is a HTTP request handler which writes the options to be passed to navigator.credentials.get()
15- // to the http.ResponseWriter. The user argument is optional and can be nil, in which case the allowCredentials
16- // option will not be set and AuthenticatorStore.GetAuthenticators will not be called.
17- func (w * WebAuthn ) StartLogin (r * http.Request , rw http.ResponseWriter , user User , session Session ) {
14+ // GetLoginOptions will return the options that need to be passed to navigator.credentials.get(). This should
15+ // be returned to the user via e.g. JSON over HTTP. For convenience, use StartLogin.
16+ func (w * WebAuthn ) GetLoginOptions (user User , session Session ) (* protocol.CredentialRequestOptions , error ) {
1817 chal , err := protocol .NewChallenge ()
1918 if err != nil {
20- w .writeError (r , rw , err )
21- return
19+ return nil , err
2220 }
2321
24- options := protocol.CredentialRequestOptions {
22+ options := & protocol.CredentialRequestOptions {
2523 PublicKey : protocol.PublicKeyCredentialRequestOptions {
2624 Challenge : chal ,
2725 Timeout : w .Config .Timeout ,
@@ -31,8 +29,7 @@ func (w *WebAuthn) StartLogin(r *http.Request, rw http.ResponseWriter, user User
3129 if user != nil {
3230 authenticators , err := w .Config .AuthenticatorStore .GetAuthenticators (user )
3331 if err != nil {
34- w .writeError (r , rw , err )
35- return
32+ return nil , err
3633 }
3734
3835 allowCredentials := make ([]protocol.PublicKeyCredentialDescriptor , len (authenticators ))
@@ -48,56 +45,53 @@ func (w *WebAuthn) StartLogin(r *http.Request, rw http.ResponseWriter, user User
4845 }
4946
5047 if err := session .Set (w .Config .SessionKeyPrefixChallenge + ".login" , []byte (chal )); err != nil {
48+ return nil , err
49+ }
50+
51+ return options , nil
52+ }
53+
54+ // StartLogin is a HTTP request handler which writes the options to be passed to navigator.credentials.get()
55+ // to the http.ResponseWriter. The user argument is optional and can be nil, in which case the allowCredentials
56+ // option will not be set and AuthenticatorStore.GetAuthenticators will not be called.
57+ func (w * WebAuthn ) StartLogin (r * http.Request , rw http.ResponseWriter , user User , session Session ) {
58+ options , err := w .GetLoginOptions (user , session )
59+ if err != nil {
5160 w .writeError (r , rw , err )
5261 return
5362 }
5463
5564 w .write (r , rw , options )
5665}
5766
58- // FinishLogin is a HTTP request handler which should receive the response of navigator.credentials.get(). If
67+ // ParseAndFinishLogin should receive the response of navigator.credentials.get(). If
5968// user is non-nil, it will be checked that the authenticator is owned by that user. If the request is valid,
60- // the authenticator will be returned and nothing will have been written to http.ResponseWriter. If authenticator is
61- // nil, an error has been written to http.ResponseWriter and should be returned as-is.
62- func (w * WebAuthn ) FinishLogin (r * http.Request , rw http.ResponseWriter , user User , session Session ) Authenticator {
69+ // the authenticator will be returned. For convenience, use FinishLogin.
70+ func (w * WebAuthn ) ParseAndFinishLogin (assertionResponse protocol.AssertionResponse , user User , session Session ) (Authenticator , error ) {
6371 rawChal , err := session .Get (w .Config .SessionKeyPrefixChallenge + ".login" )
6472 if err != nil {
65- w .writeErrorCode (r , rw , http .StatusBadRequest , err )
66- return nil
73+ return nil , protocol .ErrInvalidRequest .WithDebug ("missing challenge in session" )
6774 }
6875 chal , ok := rawChal .([]byte )
6976 if ! ok {
70- w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug ("invalid challenge session value" ))
71- return nil
77+ return nil , protocol .ErrInvalidRequest .WithDebug ("invalid challenge session value" )
7278 }
7379
7480 if err := session .Delete (w .Config .SessionKeyPrefixChallenge + ".login" ); err != nil {
75- w .writeError (r , rw , err )
76- return nil
77- }
78-
79- var assertionResponse protocol.AssertionResponse
80-
81- d := json .NewDecoder (r .Body )
82- d .DisallowUnknownFields ()
83- if err := d .Decode (& assertionResponse ); err != nil {
84- w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug (err .Error ()))
85- return nil
81+ return nil , err
8682 }
8783
8884 p , err := protocol .ParseAssertionResponse (assertionResponse )
8985 if err != nil {
90- w .writeError (r , rw , err )
91- return nil
86+ return nil , err
9287 }
9388
9489 // 1. If the allowCredentials option was given when this authentication ceremony was initiated, verify that
9590 // credential.id identifies one of the public key credentials that were listed in allowCredentials.
9691 if user != nil {
9792 authenticators , err := w .Config .AuthenticatorStore .GetAuthenticators (user )
9893 if err != nil {
99- w .writeError (r , rw , err )
100- return nil
94+ return nil , err
10195 }
10296
10397 var authrFound bool
@@ -109,7 +103,7 @@ func (w *WebAuthn) FinishLogin(r *http.Request, rw http.ResponseWriter, user Use
109103 }
110104
111105 if ! authrFound {
112- w . writeError ( r , rw , protocol .ErrInvalidRequest .WithDebug ("authenticator is not owned by user" ) )
106+ return nil , protocol .ErrInvalidRequest .WithDebug ("authenticator is not owned by user" )
113107 }
114108 }
115109
@@ -118,14 +112,12 @@ func (w *WebAuthn) FinishLogin(r *http.Request, rw http.ResponseWriter, user Use
118112 if p .Response .UserHandle != nil && len (p .Response .UserHandle ) > 0 {
119113 if user != nil {
120114 if ! bytes .Equal (p .Response .UserHandle , user .WebAuthID ()) {
121- w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug ("authenticator's user handle does not equal user ID" ))
122- return nil
115+ return nil , protocol .ErrInvalidRequest .WithDebug ("authenticator's user handle does not equal user ID" )
123116 }
124117 } else {
125118 authenticators , err := w .Config .AuthenticatorStore .GetAuthenticators (& defaultUser {id : p .Response .UserHandle })
126119 if err != nil {
127- w .writeError (r , rw , err )
128- return nil
120+ return nil , err
129121 }
130122
131123 var authrFound bool
@@ -137,8 +129,7 @@ func (w *WebAuthn) FinishLogin(r *http.Request, rw http.ResponseWriter, user Use
137129 }
138130
139131 if ! authrFound {
140- w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug ("authenticator is not owned by user" ))
141- return nil
132+ return nil , protocol .ErrInvalidRequest .WithDebug ("authenticator is not owned by user" )
142133 }
143134 }
144135 }
@@ -147,32 +138,50 @@ func (w *WebAuthn) FinishLogin(r *http.Request, rw http.ResponseWriter, user Use
147138 // case), look up the corresponding credential public key.
148139 authr , err := w .Config .AuthenticatorStore .GetAuthenticator (p .RawID )
149140 if err != nil {
150- w .writeError (r , rw , err )
151- return nil
141+ return nil , err
152142 }
153143
154144 block , _ := pem .Decode (authr .WebAuthPublicKey ())
155145 if block == nil {
156- w .writeError (r , rw , fmt .Errorf ("invalid stored public key, unable to decode" ))
157- return nil
146+ return nil , fmt .Errorf ("invalid stored public key, unable to decode" )
158147 }
159148
160149 cert , err := x509 .ParsePKIXPublicKey (block .Bytes )
161150 if err != nil {
162- w .writeError (r , rw , err )
163- return nil
151+ return nil , err
164152 }
165153
166154 valid , err := protocol .IsValidAssertion (p , chal , w .Config .RelyingPartyID , w .Config .RelyingPartyOrigin , & x509.Certificate {
167155 PublicKey : cert ,
168156 })
169157 if err != nil {
170- w .writeError (r , rw , err )
171- return nil
158+ return nil , err
172159 }
173160
174161 if ! valid {
175- w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug ("invalid login" ))
162+ return nil , protocol .ErrInvalidRequest .WithDebug ("invalid login" )
163+ }
164+
165+ return authr , nil
166+ }
167+
168+ // FinishLogin is a HTTP request handler which should receive the response of navigator.credentials.get(). If
169+ // user is non-nil, it will be checked that the authenticator is owned by that user. If the request is valid,
170+ // the authenticator will be returned and nothing will have been written to http.ResponseWriter. If authenticator is
171+ // nil, an error has been written to http.ResponseWriter and should be returned as-is.
172+ func (w * WebAuthn ) FinishLogin (r * http.Request , rw http.ResponseWriter , user User , session Session ) Authenticator {
173+ var assertionResponse protocol.AssertionResponse
174+
175+ d := json .NewDecoder (r .Body )
176+ d .DisallowUnknownFields ()
177+ if err := d .Decode (& assertionResponse ); err != nil {
178+ w .writeError (r , rw , protocol .ErrInvalidRequest .WithDebug (err .Error ()))
179+ return nil
180+ }
181+
182+ authr , err := w .ParseAndFinishLogin (assertionResponse , user , session )
183+ if err != nil {
184+ w .writeError (r , rw , err )
176185 return nil
177186 }
178187
0 commit comments