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: passkey strategy with resident keys #3656

Closed
wants to merge 43 commits into from
Closed
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5ce38ce
WIP
hperl Oct 30, 2023
95a1c92
WIP
hperl Oct 30, 2023
4a4bad4
WIP
hperl Oct 31, 2023
ef339c9
WIP: passkey registration
hperl Nov 7, 2023
71c76d5
WIP registration
hperl Nov 7, 2023
bb16686
WIP: login
hperl Nov 8, 2023
03c1424
WIP: passwordless login
hperl Nov 8, 2023
309ac30
WIP: settings
hperl Nov 22, 2023
b190bfa
test: update snapshots
hperl Nov 23, 2023
b35a205
fix passkey registration
hperl Nov 23, 2023
f47ceea
test: add registration tests
hperl Dec 4, 2023
82839e1
test: add login tests
hperl Dec 5, 2023
41f28f0
WIP: settings tests
hperl Dec 7, 2023
ace0a57
chore: cleanup
hperl Dec 7, 2023
599ba09
make all tests pass
hperl Dec 11, 2023
3d38b98
make all tests pass
hperl Dec 11, 2023
d8f024c
chore: formatting
hperl Dec 12, 2023
97685a8
Merge remote-tracking branch 'origin/master' into hperl/passwordless-…
hperl Dec 12, 2023
08ef9de
chore: lint
hperl Dec 12, 2023
21ec7cb
Update embedx/config.schema.json
hperl Dec 15, 2023
e25f614
chore: code review (#3679)
aeneasr Jan 8, 2024
4a54fec
test: add Cypress E2E test
hperl Jan 9, 2024
0df44f8
Merge branch 'hperl/passwordless-strategy' of ssh://github.com/ory/kr…
hperl Jan 9, 2024
60a1817
chore: regen sdk
hperl Jan 9, 2024
031d208
fix: address code review comments
hperl Jan 10, 2024
04f8ad2
fix: remove webauthn node attr
hperl Jan 10, 2024
604a4b6
fix: tests and lints
hperl Jan 10, 2024
751da9b
test: add refresh test
hperl Jan 10, 2024
62c6118
Merge remote-tracking branch 'origin/master' into hperl/passwordless-…
hperl Jan 10, 2024
53e5e55
fix: submit form instead of clicking
hperl Jan 12, 2024
96c2164
chore: update snapshots
hperl Jan 12, 2024
4321e75
test: improve coverage
hperl Jan 15, 2024
645e831
test: improve coverage
hperl Jan 15, 2024
97ec49f
test: improve coverage
hperl Jan 15, 2024
27e242d
test: fix
hperl Jan 15, 2024
3261470
fix: coverage
hperl Jan 15, 2024
85d9ce5
fix: mysql tests
hperl Jan 15, 2024
3d2a4cb
fix: identity schema
hperl Jan 17, 2024
3c8fd05
fix: key for test fixture
zepatrik Jan 17, 2024
8a10153
WIP: config
hperl Feb 5, 2024
34f48d2
tweak quickstart
hperl Feb 6, 2024
471157a
test: add e2e test with both webauthn pwless and passkey
hperl Feb 6, 2024
5f58a20
feat: also handle webauthn passwordless in passkey strategy
hperl Feb 8, 2024
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
4 changes: 4 additions & 0 deletions cmd/clidoc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func init() {
"NewInfoSelfServiceSettingsUpdateUnlinkOIDC": text.NewInfoSelfServiceSettingsUpdateUnlinkOIDC("{provider}"),
"NewInfoSelfServiceRegisterWebAuthnDisplayName": text.NewInfoSelfServiceRegisterWebAuthnDisplayName(),
"NewInfoSelfServiceRemoveWebAuthn": text.NewInfoSelfServiceRemoveWebAuthn("{display_name}", aSecondAgo),
"NewInfoSelfServiceRemovePasskey": text.NewInfoSelfServiceRemovePasskey("{display_name}", aSecondAgo),
"NewErrorValidationVerificationFlowExpired": text.NewErrorValidationVerificationFlowExpired(aSecondAgo),
"NewInfoSelfServiceVerificationSuccessful": text.NewInfoSelfServiceVerificationSuccessful(),
"NewVerificationEmailSent": text.NewVerificationEmailSent(),
Expand Down Expand Up @@ -150,9 +151,12 @@ func init() {
"NewInfoNodeLoginAndLinkCredential": text.NewInfoNodeLoginAndLinkCredential(),
"NewInfoNodeLabelContinue": text.NewInfoNodeLabelContinue(),
"NewInfoSelfServiceSettingsRegisterWebAuthn": text.NewInfoSelfServiceSettingsRegisterWebAuthn(),
"NewInfoSelfServiceSettingsRegisterPasskey": text.NewInfoSelfServiceSettingsRegisterPasskey(),
"NewInfoLoginWebAuthnPasswordless": text.NewInfoLoginWebAuthnPasswordless(),
"NewInfoSelfServiceRegistrationRegisterWebAuthn": text.NewInfoSelfServiceRegistrationRegisterWebAuthn(),
"NewInfoSelfServiceContinueLoginWebAuthn": text.NewInfoSelfServiceContinueLoginWebAuthn(),
"NewInfoSelfServiceLoginPasskey": text.NewInfoSelfServiceLoginPasskey(),
"NewInfoSelfServiceRegistrationRegisterPasskey": text.NewInfoSelfServiceRegistrationRegisterPasskey(),
"NewInfoSelfServiceLoginContinue": text.NewInfoSelfServiceLoginContinue(),
"NewErrorValidationSuchNoWebAuthnUser": text.NewErrorValidationSuchNoWebAuthnUser(),
"NewRegistrationEmailWithCodeSent": text.NewRegistrationEmailWithCodeSent(),
Expand Down
53 changes: 53 additions & 0 deletions contrib/quickstart/kratos/passkey/identity.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"traits": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email",
"title": "E-Mail",
"minLength": 3,
"ory.sh/kratos": {
"credentials": {
"password": {
"identifier": true
},
"webauthn": {
"identifier": true
},
"passkey": {
"identifier": true
}
},
"verification": {
"via": "email"
},
"recovery": {
"via": "email"
}
}
},
"name": {
"type": "object",
"properties": {
"first": {
"title": "First Name",
"type": "string"
},
"last": {
"title": "Last Name",
"type": "string"
}
}
}
},
"required": ["email"],
"additionalProperties": false
}
}
}
110 changes: 110 additions & 0 deletions contrib/quickstart/kratos/passkey/kratos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
version: v0.13.0

dsn: memory

serve:
public:
base_url: http://localhost:4433/
cors:
enabled: true
admin:
base_url: http://kratos:4434/

selfservice:
default_browser_return_url: http://localhost:4455/
allowed_return_urls:
- http://localhost:4455

methods:
password:
enabled: true
totp:
config:
issuer: Kratos
enabled: true
lookup_secret:
enabled: true
link:
enabled: true
code:
enabled: true
passkey:
enabled: true
config:
rp:
display_name: Your Application name
# Set 'id' to the top-level domain.
id: localhost
# Set 'origin' to the exact URL of the page that prompts the user to use WebAuthn. You must include the scheme, host, and port.
origins:
- http://localhost:4455

flows:
error:
ui_url: http://localhost:4455/error

settings:
ui_url: http://localhost:4455/settings
privileged_session_max_age: 15m
required_aal: highest_available

recovery:
enabled: true
ui_url: http://localhost:4455/recovery
use: code

verification:
enabled: true
ui_url: http://localhost:4455/verification
use: code
after:
default_browser_return_url: http://localhost:4455/

logout:
after:
default_browser_return_url: http://localhost:4455/login

login:
ui_url: http://localhost:4455/login
lifespan: 10m

registration:
lifespan: 10m
ui_url: http://localhost:4455/registration
after:
passkey:
hooks:
- hook: session
password:
hooks:
- hook: session
- hook: show_verification_ui

log:
level: debug
format: text
leak_sensitive_values: true

secrets:
cookie:
- PLEASE-CHANGE-ME-I-AM-VERY-INSECURE
cipher:
- 32-LONG-SECRET-NOT-SECURE-AT-ALL

ciphers:
algorithm: xchacha20-poly1305

hashers:
algorithm: bcrypt
bcrypt:
cost: 8

identity:
default_schema_id: default
schemas:
- id: default
url: file://contrib/quickstart/kratos/passkey/identity.schema.json

courier:
smtp:
connection_uri: smtps://test:test@mailslurper:1025/?skip_ssl_verify=true
19 changes: 19 additions & 0 deletions driver/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ const (
ViperKeyWebAuthnRPOrigin = "selfservice.methods.webauthn.config.rp.origin"
ViperKeyWebAuthnRPOrigins = "selfservice.methods.webauthn.config.rp.origins"
ViperKeyWebAuthnPasswordless = "selfservice.methods.webauthn.config.passwordless"
ViperKeyPasskeyEnabled = "selfservice.methods.passkey.enabled"
ViperKeyPasskeyRPDisplayName = "selfservice.methods.passkey.config.rp.display_name"
ViperKeyPasskeyRPID = "selfservice.methods.passkey.config.rp.id"
ViperKeyPasskeyRPOrigins = "selfservice.methods.passkey.config.rp.origins"
ViperKeyOAuth2ProviderURL = "oauth2_provider.url"
ViperKeyOAuth2ProviderHeader = "oauth2_provider.headers"
ViperKeyOAuth2ProviderOverrideReturnTo = "oauth2_provider.override_return_to"
Expand Down Expand Up @@ -1406,6 +1410,21 @@ func (p *Config) WebAuthnConfig(ctx context.Context) *webauthn.Config {
}
}

func (p *Config) PasskeyConfig(ctx context.Context) *webauthn.Config {
scheme := p.SelfPublicURL(ctx).Scheme
id := p.GetProvider(ctx).String(ViperKeyPasskeyRPID)
origins := p.GetProvider(ctx).StringsF(ViperKeyPasskeyRPOrigins, []string{scheme + "://" + id})
return &webauthn.Config{
RPDisplayName: p.GetProvider(ctx).String(ViperKeyPasskeyRPDisplayName),
RPID: id,
RPOrigins: origins,
AuthenticatorSelection: protocol.AuthenticatorSelection{
UserVerification: protocol.VerificationDiscouraged,
},
EncodeUserIDAsString: false,
}
}

func (p *Config) HasherPasswordHashingAlgorithm(ctx context.Context) string {
configValue := p.GetProvider(ctx).StringF(ViperKeyHasherAlgorithm, DefaultPasswordHashingAlgorithm)
switch configValue {
Expand Down
2 changes: 2 additions & 0 deletions driver/registry_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/dgraph-io/ristretto"

"github.com/ory/kratos/selfservice/strategy/passkey"
"github.com/ory/x/jwksx"

"github.com/ory/x/contextx"
Expand Down Expand Up @@ -336,6 +337,7 @@ func (m *RegistryDefault) selfServiceStrategies() []any {
totp.NewStrategy(m),
webauthn.NewStrategy(m),
lookup.NewStrategy(m),
passkey.NewStrategy(m),
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions driver/registry_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -851,7 +851,7 @@ func TestDefaultRegistry_AllStrategies(t *testing.T) {
_, reg := internal.NewVeryFastRegistryWithoutDB(t)

t.Run("case=all login strategies", func(t *testing.T) {
expects := []string{"password", "oidc", "code", "totp", "webauthn", "lookup_secret"}
expects := []string{"password", "oidc", "code", "totp", "webauthn", "lookup_secret", "passkey"}
s := reg.AllLoginStrategies()
require.Len(t, s, len(expects))
for k, e := range expects {
Expand All @@ -860,7 +860,7 @@ func TestDefaultRegistry_AllStrategies(t *testing.T) {
})

t.Run("case=all registration strategies", func(t *testing.T) {
expects := []string{"password", "oidc", "code", "webauthn"}
expects := []string{"password", "oidc", "code", "webauthn", "passkey"}
s := reg.AllRegistrationStrategies()
require.Len(t, s, len(expects))
for k, e := range expects {
Expand All @@ -869,7 +869,7 @@ func TestDefaultRegistry_AllStrategies(t *testing.T) {
})

t.Run("case=all settings strategies", func(t *testing.T) {
expects := []string{"password", "oidc", "profile", "totp", "webauthn", "lookup_secret"}
expects := []string{"password", "oidc", "profile", "totp", "webauthn", "lookup_secret", "passkey"}
s := reg.AllSettingsStrategies()
require.Len(t, s, len(expects))
for k, e := range expects {
Expand Down
70 changes: 70 additions & 0 deletions embedx/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,9 @@
"webauthn": {
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
"passkey": {
aeneasr marked this conversation as resolved.
Show resolved Hide resolved
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
"lookup_secret": {
"$ref": "#/definitions/selfServiceAfterSettingsAuthMethod"
},
Expand Down Expand Up @@ -852,6 +855,9 @@
"webauthn": {
"$ref": "#/definitions/selfServiceAfterDefaultLoginMethod"
},
"passkey": {
"$ref": "#/definitions/selfServiceAfterDefaultLoginMethod"
},
"oidc": {
"$ref": "#/definitions/selfServiceAfterOIDCLoginMethod"
},
Expand Down Expand Up @@ -936,6 +942,9 @@
"webauthn": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
"passkey": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
"oidc": {
"$ref": "#/definitions/selfServiceAfterRegistrationMethod"
},
Expand Down Expand Up @@ -1630,6 +1639,67 @@
"required": ["config"]
}
},
"passkey": {
hperl marked this conversation as resolved.
Show resolved Hide resolved
"type": "object",
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean",
"title": "Enables the Passkey method",
"default": false
},
"config": {
"type": "object",
"title": "Passkey Configuration",
"properties": {
"rp": {
"title": "Relying Party (RP) Config",
"properties": {
"display_name": {
"type": "string",
"title": "Relying Party Display Name",
"description": "An name to help the user identify this RP.",
hperl marked this conversation as resolved.
Show resolved Hide resolved
"examples": ["Ory Foundation"]
},
"id": {
"type": "string",
"title": "Relying Party Identifier",
"description": "The id must be a subset of the domain currently in the browser.",
"examples": ["ory.sh"]
},
"origins": {
"type": "array",
"title": "Relying Party Origins",
"description": "A list of explicit RP origins. If left empty, this defaults to either `origin` or `id`, prepended with the current protocol schema (HTTP or HTTPS).",
"items": {
"type": "string",
"format": "uri",
"examples": [
"https://www.ory.sh",
"https://auth.ory.sh"
]
}
}
},
"type": "object",
"required": ["display_name", "id"]
}
},
"additionalProperties": false
}
},
"if": {
"properties": {
"enabled": {
"const": true
}
},
"required": ["enabled"]
},
"then": {
"required": ["config"]
}
},
"oidc": {
"type": "object",
"title": "Specify OpenID Connect and OAuth2 Configuration",
Expand Down
9 changes: 9 additions & 0 deletions embedx/identity_extension.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
}
}
},
"passkey": {
"type": "object",
"additionalProperties": false,
"properties": {
"identifier": {
"type": "boolean"
}
}
},
"totp": {
"type": "object",
"additionalProperties": false,
Expand Down
Loading
Loading