Skip to content

Commit

Permalink
feat(webauthn): make webauthn params configurable
Browse files Browse the repository at this point in the history
* add attachment, attestation preference and resident key requirement params to create and update tenant endpoints
* add fallback values when params are not provided
* add migrations for new fields in webauthn config in database
* reflect changes in README.md and admin spec

Closes: #45
  • Loading branch information
Stefan Jacobi committed Feb 26, 2024
1 parent 090c631 commit ccdac94
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 25 deletions.
16 changes: 12 additions & 4 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ curl --location 'http://<YOUR DOMAIN>:8001/tenants' \
]
},
"timeout": 60000,
"user_verification": "preferred"
"user_verification": "preferred",
"attestation_preference": "none",
"resident_key_requirement": "required"
},
"create_api_key": true
}
Expand Down Expand Up @@ -225,7 +227,9 @@ the [WebAuthn Relying Party](https://www.w3.org/TR/webauthn-2/#webauthn-relying-
]
},
"timeout": 60000,
"user_verification": "preferred"
"user_verification": "preferred",
"attestation_preference": "none",
"resident_key_requirement": "required"
}
}
}
Expand All @@ -252,7 +256,9 @@ As an example: If the login should be available at `https://login.example.com` i
]
},
"timeout": 60000,
"user_verification": "preferred"
"user_verification": "preferred",
"attestation_preference": "none",
"resident_key_requirement": "required"
}
}
}
Expand All @@ -275,7 +281,9 @@ point. Then the WebAuthn config would look like this:
]
},
"timeout": 60000,
"user_verification": "preferred"
"user_verification": "preferred",
"attestation_preference": "none",
"resident_key_requirement": "required"
}
}
}
Expand Down
22 changes: 19 additions & 3 deletions server/api/dto/admin/request/webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
)

type CreateWebauthnDto struct {
RelyingParty CreateRelyingPartyDto `json:"relying_party" validate:"required"`
Timeout int `json:"timeout" validate:"required,number"`
UserVerification protocol.UserVerificationRequirement `json:"user_verification" validate:"required,oneof=required preferred discouraged"`
RelyingParty CreateRelyingPartyDto `json:"relying_party" validate:"required"`
Timeout int `json:"timeout" validate:"required,number"`
UserVerification protocol.UserVerificationRequirement `json:"user_verification" validate:"required,oneof=required preferred discouraged"`
Attachment *protocol.AuthenticatorAttachment `json:"attachment" validate:"omitempty,oneof=platform cross-platform"`
AttestationPreference *protocol.ConveyancePreference `json:"attestation_preference" validate:"omitempty,oneof=none indirect direct enterprise"`
ResidentKeyRequirement *protocol.ResidentKeyRequirement `json:"resident_key_requirement" validate:"omitempty,oneof=discouraged preferred required"`
}

func (dto *CreateWebauthnDto) ToModel(configModel models.Config) models.WebauthnConfig {
Expand All @@ -24,6 +27,19 @@ func (dto *CreateWebauthnDto) ToModel(configModel models.Config) models.Webauthn
CreatedAt: now,
UpdatedAt: now,
UserVerification: dto.UserVerification,
Attachment: dto.Attachment,
}

if dto.AttestationPreference == nil {
webauthnConfig.AttestationPreference = protocol.PreferNoAttestation
} else {
webauthnConfig.AttestationPreference = *dto.AttestationPreference
}

if dto.ResidentKeyRequirement == nil {
webauthnConfig.ResidentKeyRequirement = protocol.ResidentKeyRequirementRequired
} else {
webauthnConfig.ResidentKeyRequirement = *dto.ResidentKeyRequirement
}

return webauthnConfig
Expand Down
6 changes: 3 additions & 3 deletions server/api/middleware/webauthn.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ func WebauthnMiddleware() echo.MiddlewareFunc {
RPDisplayName: cfg.WebauthnConfig.RelyingParty.DisplayName,
RPID: cfg.WebauthnConfig.RelyingParty.RPId,
RPOrigins: origins,
AttestationPreference: protocol.PreferNoAttestation,
AttestationPreference: cfg.WebauthnConfig.AttestationPreference,
AuthenticatorSelection: protocol.AuthenticatorSelection{
RequireResidentKey: &f,
ResidentKey: protocol.ResidentKeyRequirementDiscouraged,
UserVerification: protocol.VerificationRequired,
ResidentKey: cfg.WebauthnConfig.ResidentKeyRequirement,
UserVerification: cfg.WebauthnConfig.UserVerification,
},
Debug: false,
Timeouts: webauthn.TimeoutsConfig{
Expand Down
18 changes: 12 additions & 6 deletions server/api/services/registration_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,20 @@ func (rs *registrationService) Initialize(user *models.WebauthnUser) (*protocol.
}

t := true
authSelection := protocol.AuthenticatorSelection{
RequireResidentKey: &t,
ResidentKey: rs.tenant.Config.WebauthnConfig.ResidentKeyRequirement,
UserVerification: rs.tenant.Config.WebauthnConfig.UserVerification,
}

if rs.tenant.Config.WebauthnConfig.Attachment != nil {
authSelection.AuthenticatorAttachment = *rs.tenant.Config.WebauthnConfig.Attachment
}

credentialCreation, sessionData, err := rs.webauthnClient.BeginRegistration(
internalUser,
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
RequireResidentKey: &t,
ResidentKey: protocol.ResidentKeyRequirementRequired,
UserVerification: rs.tenant.Config.WebauthnConfig.UserVerification,
}),
webauthn.WithConveyancePreference(protocol.PreferNoAttestation),
webauthn.WithAuthenticatorSelection(authSelection),
webauthn.WithConveyancePreference(rs.tenant.Config.WebauthnConfig.AttestationPreference),
)
if err != nil {
return nil, internalUser.UserId, err
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
drop_column("webauthn_configs", "resident_key_requirement")
drop_column("webauthn_configs", "attestation_preference")
drop_column("webauthn_configs", "attachment")
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
add_column("webauthn_configs", "attachment", "string", { "null": true })
add_column("webauthn_configs", "attestation_preference", "string", { default: "none" })
add_column("webauthn_configs", "resident_key_requirement", "string", { default: "required" })
23 changes: 14 additions & 9 deletions server/persistence/models/webauthn_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,28 @@ import (

// WebauthnConfig is used by pop to map your webauthn_configs database table to your go code.
type WebauthnConfig struct {
ID uuid.UUID `json:"id" db:"id"`
Config *Config `json:"config" belongs_to:"configs"`
ConfigID uuid.UUID `json:"config_id" db:"config_id"`
RelyingParty RelyingParty `json:"relying_party" has_one:"relying_parties"`
Timeout int `json:"timeout" db:"timeout"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
UserVerification protocol.UserVerificationRequirement `json:"user_verification" db:"user_verification"`
ID uuid.UUID `json:"id" db:"id"`
Config *Config `json:"config" belongs_to:"configs"`
ConfigID uuid.UUID `json:"config_id" db:"config_id"`
RelyingParty RelyingParty `json:"relying_party" has_one:"relying_parties"`
Timeout int `json:"timeout" db:"timeout"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
UserVerification protocol.UserVerificationRequirement `json:"user_verification" db:"user_verification"`
Attachment *protocol.AuthenticatorAttachment `json:"attachment" db:"attachment"`
AttestationPreference protocol.ConveyancePreference `json:"attestation_preference" db:"attestation_preference"`
ResidentKeyRequirement protocol.ResidentKeyRequirement `json:"resident_key_requirement" db:"resident_key_requirement"`
}

// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method.
// This method is not required and may be deleted.
func (webauthn *WebauthnConfig) Validate(tx *pop.Connection) (*validate.Errors, error) {
func (webauthn *WebauthnConfig) Validate(_ *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.UUIDIsPresent{Name: "ID", Field: webauthn.ID},
&validators.IntIsPresent{Name: "Timeout", Field: webauthn.Timeout},
&validators.StringIsPresent{Name: "UserVerification", Field: string(webauthn.UserVerification)},
&validators.StringIsPresent{Name: "AttestationPreference", Field: string(webauthn.AttestationPreference)},
&validators.StringIsPresent{Name: "ResidentKeyRequirement", Field: string(webauthn.ResidentKeyRequirement)},
&validators.TimeIsPresent{Name: "UpdatedAt", Field: webauthn.UpdatedAt},
&validators.TimeIsPresent{Name: "CreatedAt", Field: webauthn.CreatedAt},
), nil
Expand Down
23 changes: 23 additions & 0 deletions spec/passkey-server-admin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ components:
maxLength: 36
api_key:
$ref: '#/components/schemas/secret'
description: omitted when `create_api_key`is omitted or set to `false`
required:
- id
secret:
Expand Down Expand Up @@ -658,10 +659,32 @@ components:
examples:
- 60000
user_verification:
type: string
enum:
- required
- preferred
- discouraged
attachment:
type: string
enum:
- platform
- cross-platform
description: uses all authenticator attachments when omitted
attestation_preference:
type: string
enum:
- none
- indirect
- direct
- enterprise
description: defaults to `none` when omitted
resident_key_requirement:
type: string
enum:
- discouraged
- preferred
- required
description: defaults to `required` when omitted
required:
- relying_party
- timeout
Expand Down

0 comments on commit ccdac94

Please sign in to comment.