Skip to content

Commit 270f825

Browse files
committed
remove Webauthn client creation from authentication middleware
1 parent d7644bd commit 270f825

File tree

4 files changed

+126
-128
lines changed

4 files changed

+126
-128
lines changed

fixtures_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,13 @@ func getTestWebauthnUsers(ms *MfaSuite, config baseTestConfig) []WebauthnUser {
6060
apiKey2.Secret = "E286600E-3DBF-4C23-A0DA-9C55D448"
6161

6262
testUser0 := WebauthnUser{
63-
ID: apiKey0.Secret,
64-
Name: "Nancy_NoCredential",
65-
DisplayName: "Nancy NoCredential",
66-
Store: config.Storage,
67-
WebAuthnClient: config.WebAuthnClient,
68-
ApiKey: apiKey0,
69-
ApiKeyValue: apiKey0.Key,
70-
Credentials: []webauthn.Credential{},
63+
ID: apiKey0.Secret,
64+
Name: "Nancy_NoCredential",
65+
DisplayName: "Nancy NoCredential",
66+
Store: config.Storage,
67+
ApiKey: apiKey0,
68+
ApiKeyValue: apiKey0.Key,
69+
Credentials: []webauthn.Credential{},
7170
}
7271

7372
testUser1 := testUser0

user.go

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,23 +53,21 @@ type WebauthnUser struct {
5353
Credentials []webauthn.Credential `dynamodbav:"-" json:"-"`
5454
EncryptedCredentials []byte `dynamodbav:"EncryptedCredentials" json:"EncryptedCredentials,omitempty"`
5555

56-
WebAuthnClient *webauthn.WebAuthn `dynamodbav:"-" json:"-"`
57-
Name string `dynamodbav:"-" json:"-"`
58-
DisplayName string `dynamodbav:"-" json:"-"`
59-
Icon string `dynamodbav:"-" json:"-"`
56+
Name string `dynamodbav:"-" json:"-"`
57+
DisplayName string `dynamodbav:"-" json:"-"`
58+
Icon string `dynamodbav:"-" json:"-"`
6059
}
6160

6261
// NewWebauthnUser creates a new WebauthnUser from API input data, a storage client and a Webauthn client.
63-
func NewWebauthnUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey, webAuthnClient *webauthn.WebAuthn) WebauthnUser {
62+
func NewWebauthnUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey) WebauthnUser {
6463
u := WebauthnUser{
65-
ID: apiConfig.UserUUID,
66-
Name: apiConfig.Username,
67-
DisplayName: apiConfig.UserDisplayName,
68-
Icon: apiConfig.UserIcon,
69-
Store: storage,
70-
WebAuthnClient: webAuthnClient,
71-
ApiKey: apiKey,
72-
ApiKeyValue: apiKey.Key,
64+
ID: apiConfig.UserUUID,
65+
Name: apiConfig.Username,
66+
DisplayName: apiConfig.UserDisplayName,
67+
Icon: apiConfig.UserIcon,
68+
Store: storage,
69+
ApiKey: apiKey,
70+
ApiKeyValue: apiKey.Key,
7371
}
7472

7573
if u.ID == "" {
@@ -268,18 +266,14 @@ func (u *WebauthnUser) Delete() error {
268266

269267
// BeginRegistration processes the first half of the Webauthn Registration flow for the user and returns the
270268
// CredentialCreation data to pass back to the client. User session data is saved in the database.
271-
func (u *WebauthnUser) BeginRegistration() (*protocol.CredentialCreation, error) {
272-
if u.WebAuthnClient == nil {
273-
return nil, fmt.Errorf("webauthnUser, %s, missing WebAuthClient in BeginRegistration", u.Name)
274-
}
275-
269+
func (u *WebauthnUser) BeginRegistration(client *webauthn.WebAuthn) (*protocol.CredentialCreation, error) {
276270
rrk := false
277271
authSelection := protocol.AuthenticatorSelection{
278272
RequireResidentKey: &rrk,
279273
UserVerification: protocol.VerificationDiscouraged,
280274
}
281275

282-
options, sessionData, err := u.WebAuthnClient.BeginRegistration(u, webauthn.WithAuthenticatorSelection(authSelection))
276+
options, sessionData, err := client.BeginRegistration(u, webauthn.WithAuthenticatorSelection(authSelection))
283277
if err != nil {
284278
return &protocol.CredentialCreation{}, fmt.Errorf("failed to begin registration: %w", err)
285279
}
@@ -295,7 +289,7 @@ func (u *WebauthnUser) BeginRegistration() (*protocol.CredentialCreation, error)
295289
// FinishRegistration processes the last half of the Webauthn Registration flow for the user and returns the
296290
// key_handle_hash to pass back to the client. The client should store this value for later use. User session data is
297291
// cleared from the database.
298-
func (u *WebauthnUser) FinishRegistration(r *http.Request) (string, error) {
292+
func (u *WebauthnUser) FinishRegistration(r *http.Request, client *webauthn.WebAuthn) (string, error) {
299293
if r.Body == nil {
300294
return "", fmt.Errorf("request Body may not be nil in FinishRegistration")
301295
}
@@ -312,7 +306,7 @@ func (u *WebauthnUser) FinishRegistration(r *http.Request) (string, error) {
312306
return "", fmt.Errorf("unable to parse credential creation response body: %w", err)
313307
}
314308

315-
credential, err := u.WebAuthnClient.CreateCredential(u, u.SessionData, parsedResponse)
309+
credential, err := client.CreateCredential(u, u.SessionData, parsedResponse)
316310
if err != nil {
317311
logProtocolError("unable to create credential", err)
318312
return "", fmt.Errorf("unable to create credential: %w", err)
@@ -330,7 +324,7 @@ func (u *WebauthnUser) FinishRegistration(r *http.Request) (string, error) {
330324

331325
// BeginLogin processes the first half of the Webauthn Authentication flow for the user and returns the
332326
// CredentialAssertion data to pass back to the client. User session data is saved in the database.
333-
func (u *WebauthnUser) BeginLogin() (*protocol.CredentialAssertion, error) {
327+
func (u *WebauthnUser) BeginLogin(client *webauthn.WebAuthn) (*protocol.CredentialAssertion, error) {
334328
extensions := protocol.AuthenticationExtensions{}
335329
if u.EncryptedAppId != "" {
336330
appid, err := u.ApiKey.DecryptLegacy([]byte(u.EncryptedAppId))
@@ -340,7 +334,7 @@ func (u *WebauthnUser) BeginLogin() (*protocol.CredentialAssertion, error) {
340334
extensions["appid"] = string(appid)
341335
}
342336

343-
options, sessionData, err := u.WebAuthnClient.BeginLogin(u, webauthn.WithAssertionExtensions(extensions), webauthn.WithUserVerification(protocol.VerificationDiscouraged))
337+
options, sessionData, err := client.BeginLogin(u, webauthn.WithAssertionExtensions(extensions), webauthn.WithUserVerification(protocol.VerificationDiscouraged))
344338
if err != nil {
345339
return &protocol.CredentialAssertion{}, err
346340
}
@@ -356,7 +350,7 @@ func (u *WebauthnUser) BeginLogin() (*protocol.CredentialAssertion, error) {
356350

357351
// FinishLogin processes the last half of the Webauthn Authentication flow for the user and returns the
358352
// Credential data to pass back to the client. User session data is untouched by this function.
359-
func (u *WebauthnUser) FinishLogin(r *http.Request) (*webauthn.Credential, error) {
353+
func (u *WebauthnUser) FinishLogin(r *http.Request, client *webauthn.WebAuthn) (*webauthn.Credential, error) {
360354
if r.Body == nil {
361355
return nil, fmt.Errorf("request Body may not be nil in FinishLogin")
362356
}
@@ -383,14 +377,14 @@ func (u *WebauthnUser) FinishLogin(r *http.Request) (*webauthn.Credential, error
383377
}
384378

385379
appIdHash := sha256.Sum256(appid)
386-
rpIdHash := sha256.Sum256([]byte(u.WebAuthnClient.Config.RPID))
380+
rpIdHash := sha256.Sum256([]byte(client.Config.RPID))
387381

388382
if fmt.Sprintf("%x", parsedResponse.Response.AuthenticatorData.RPIDHash) == fmt.Sprintf("%x", appIdHash) {
389383
parsedResponse.Response.AuthenticatorData.RPIDHash = rpIdHash[:]
390384
}
391385
}
392386

393-
credential, err := u.WebAuthnClient.ValidateLogin(u, u.SessionData, parsedResponse)
387+
credential, err := client.ValidateLogin(u, u.SessionData, parsedResponse)
394388
if err != nil {
395389
logProtocolError("failed to validate login", err)
396390
return &webauthn.Credential{}, fmt.Errorf("failed to validate login: %w", err)

webauthn.go

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,13 @@ func BeginRegistration(w http.ResponseWriter, r *http.Request) {
6262
user.ID = uuid.NewV4().String()
6363
}
6464

65-
options, err := user.BeginRegistration()
65+
client, err := getWebauthnClient(r)
66+
if err != nil {
67+
jsonResponse(w, err, http.StatusInternalServerError)
68+
return
69+
}
70+
71+
options, err := user.BeginRegistration(client)
6672
if err != nil {
6773
jsonResponse(w, fmt.Sprintf("failed to begin registration: %s", err), http.StatusBadRequest)
6874
return
@@ -85,7 +91,12 @@ func FinishRegistration(w http.ResponseWriter, r *http.Request) {
8591
return
8692
}
8793

88-
keyHandleHash, err := user.FinishRegistration(r)
94+
client, err := getWebauthnClient(r)
95+
if err != nil {
96+
jsonResponse(w, err, http.StatusInternalServerError)
97+
}
98+
99+
keyHandleHash, err := user.FinishRegistration(r, client)
89100
if err != nil {
90101
jsonResponse(w, err, http.StatusBadRequest)
91102
return
@@ -108,7 +119,12 @@ func BeginLogin(w http.ResponseWriter, r *http.Request) {
108119
return
109120
}
110121

111-
options, err := user.BeginLogin()
122+
client, err := getWebauthnClient(r)
123+
if err != nil {
124+
jsonResponse(w, err, http.StatusInternalServerError)
125+
return
126+
}
127+
options, err := user.BeginLogin(client)
112128
if err != nil {
113129
jsonResponse(w, err, http.StatusBadRequest)
114130
log.Printf("error beginning user login: %s\n", err)
@@ -128,7 +144,12 @@ func FinishLogin(w http.ResponseWriter, r *http.Request) {
128144
return
129145
}
130146

131-
credential, err := user.FinishLogin(r)
147+
client, err := getWebauthnClient(r)
148+
if err != nil {
149+
jsonResponse(w, err, http.StatusInternalServerError)
150+
}
151+
152+
credential, err := user.FinishLogin(r, client)
132153
if err != nil {
133154
jsonResponse(w, err, http.StatusBadRequest)
134155
log.Printf("error finishing user login : %s\n", err)
@@ -245,8 +266,23 @@ func fixEncoding(content []byte) io.Reader {
245266
return bytes.NewReader([]byte(fixStringEncoding(allStr)))
246267
}
247268

248-
// getWebAuthnFromApiMeta creates a new WebAuthn object from the metadata provided in a web request. Typically used in
249-
// the API authentication phase, early in the handler or in a middleware.
269+
// getWebauthnClient creates a new webauthn client from the request metadata
270+
func getWebauthnClient(r *http.Request) (*webauthn.WebAuthn, error) {
271+
// apiMeta includes info about the user and webauthn config
272+
apiMeta, err := getApiMetaFromRequest(r)
273+
if err != nil {
274+
return nil, fmt.Errorf("unable to retrieve API meta information from request: %w", err)
275+
}
276+
277+
webAuthnClient, err := getWebAuthnFromApiMeta(apiMeta)
278+
if err != nil {
279+
return nil, fmt.Errorf("unable to create webauthn client from api meta config: %w", err)
280+
}
281+
282+
return webAuthnClient, nil
283+
}
284+
285+
// getWebAuthnFromApiMeta creates a new WebAuthn object from the metadata provided in a web request.
250286
func getWebAuthnFromApiMeta(meta ApiMeta) (*webauthn.WebAuthn, error) {
251287
web, err := webauthn.New(&webauthn.Config{
252288
RPDisplayName: meta.RPDisplayName, // Display Name for your site
@@ -261,8 +297,7 @@ func getWebAuthnFromApiMeta(meta ApiMeta) (*webauthn.WebAuthn, error) {
261297
return web, nil
262298
}
263299

264-
// getApiMetaFromRequest creates an ApiMeta object from request headers, including basic validation checks. Used during
265-
// API authentication.
300+
// getApiMetaFromRequest creates an ApiMeta object from request headers, including basic validation checks.
266301
func getApiMetaFromRequest(r *http.Request) (ApiMeta, error) {
267302
meta := ApiMeta{
268303
RPDisplayName: r.Header.Get("x-mfa-RPDisplayName"),
@@ -356,12 +391,7 @@ func AuthenticateRequest(r *http.Request) (*WebauthnUser, error) {
356391
return nil, fmt.Errorf("unable to retrieve API meta information from request: %w", err)
357392
}
358393

359-
webAuthnClient, err := getWebAuthnFromApiMeta(apiMeta)
360-
if err != nil {
361-
return nil, fmt.Errorf("unable to create webauthn client from api meta config: %w", err)
362-
}
363-
364-
user := NewWebauthnUser(apiMeta, localStorage, apiKey, webAuthnClient)
394+
user := NewWebauthnUser(apiMeta, localStorage, apiKey)
365395

366396
// If this user exists (api key value is not empty), make sure the calling API Key owns the user and is allowed to operate on it
367397
if user.ApiKeyValue != "" && user.ApiKeyValue != apiKey.Key {

0 commit comments

Comments
 (0)