Skip to content

Commit

Permalink
feat(webauthn): passwordless login
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Mar 7, 2022
1 parent 5a62ced commit b4c4fd2
Show file tree
Hide file tree
Showing 26 changed files with 909 additions and 162 deletions.
14 changes: 2 additions & 12 deletions selfservice/strategy/webauthn/.schema/login.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,7 @@
"minLength": 1
}
},
"oneOf": [
{
"required": [
"webauthn_login"
]
},
{
"required": [
"identifier",
"method"
]
}
"required": [
"identifier"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"id": 4000008,
"text": "The provided authentication code is invalid, please try again.",
"type": "error",
"context": {}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"type": "browser",
"ui": {
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"required": true,
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
},
{
"type": "script",
"group": "webauthn",
"attributes": {
"async": true,
"referrerpolicy": "no-referrer",
"crossorigin": "anonymous",
"integrity": "sha512-E3ctShTQEYTkfWrjztRCbP77lN7L0jJC2IOd6j8vqUKslvqhX/Ho3QxlQJIeTI78krzAWUQlDXd9JQ0PZlKhzQ==",
"type": "text/javascript",
"node_type": "script"
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "webauthn",
"attributes": {
"name": "webauthn_login_trigger",
"type": "button",
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {
"label": {
"text": "Continue",
"type": "info"
}
}
},
{
"type": "input",
"group": "webauthn",
"attributes": {
"name": "webauthn_login",
"type": "hidden",
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "default",
"attributes": {
"name": "identifier",
"type": "hidden",
"required": true,
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
}
],
"messages": [
{
"text": "Prepare your WebAuthn device (e.g. security key, biometrics scanner, ...) and press continue.",
"type": "info",
"context": {}
}
]
},
"refresh": false,
"requested_aal": "aal1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"type": "browser",
"ui": {
"method": "POST",
"nodes": [
{
"type": "input",
"group": "default",
"attributes": {
"name": "csrf_token",
"type": "hidden",
"required": true,
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
},
{
"type": "script",
"group": "webauthn",
"attributes": {
"async": true,
"referrerpolicy": "no-referrer",
"crossorigin": "anonymous",
"integrity": "sha512-E3ctShTQEYTkfWrjztRCbP77lN7L0jJC2IOd6j8vqUKslvqhX/Ho3QxlQJIeTI78krzAWUQlDXd9JQ0PZlKhzQ==",
"type": "text/javascript",
"node_type": "script"
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "webauthn",
"attributes": {
"name": "webauthn_login_trigger",
"type": "button",
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {
"label": {
"text": "Continue",
"type": "info"
}
}
},
{
"type": "input",
"group": "webauthn",
"attributes": {
"name": "webauthn_login",
"type": "hidden",
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
},
{
"type": "input",
"group": "default",
"attributes": {
"name": "identifier",
"type": "hidden",
"required": true,
"disabled": false,
"node_type": "input"
},
"messages": [],
"meta": {}
}
],
"messages": [
{
"text": "Prepare your WebAuthn device (e.g. security key, biometrics scanner, ...) and press continue.",
"type": "info",
"context": {}
}
]
},
"refresh": false,
"requested_aal": "aal1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"attributes": {
"disabled": false,
"name": "identifier",
"node_type": "input",
"required": true,
"type": "text",
"value": ""
},
"group": "default",
"messages": [],
"meta": {
"label": {
"id": 1070004,
"text": "ID",
"type": "info"
}
},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "method",
"node_type": "input",
"type": "submit",
"value": "webauthn"
},
"group": "webauthn",
"messages": [],
"meta": {
"label": {
"context": {},
"id": 1010001,
"text": "Sign in with security key",
"type": "info"
}
},
"type": "input"
},
{
"attributes": {
"disabled": false,
"name": "csrf_token",
"node_type": "input",
"required": true,
"type": "hidden"
},
"group": "default",
"messages": [],
"meta": {},
"type": "input"
}
]
14 changes: 10 additions & 4 deletions selfservice/strategy/webauthn/credentials.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package webauthn

import (
"github.com/ory/kratos/identity"
"time"

"github.com/duo-labs/webauthn/webauthn"
Expand Down Expand Up @@ -29,10 +30,15 @@ func CredentialFromWebAuthn(credential *webauthn.Credential, isPasswordless bool
}
}

func (c Credentials) ToWebAuthn() []webauthn.Credential {
result := make([]webauthn.Credential, len(c))
for k := range c {
result[k] = *c[k].ToWebAuthn()
func (c Credentials) ToWebAuthn(aal identity.AuthenticatorAssuranceLevel) (result []webauthn.Credential) {
for k, cc := range c {
if aal == identity.AuthenticatorAssuranceLevel1 && !cc.IsPasswordless {
continue
} else if aal == identity.AuthenticatorAssuranceLevel2 && cc.IsPasswordless {
continue
}

result = append(result, *c[k].ToWebAuthn())
}
return result
}
Expand Down
12 changes: 11 additions & 1 deletion selfservice/strategy/webauthn/credentials_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package webauthn

import (
"github.com/ory/kratos/identity"
"testing"

"github.com/duo-labs/webauthn/webauthn"
Expand All @@ -22,9 +23,18 @@ func TestCredentialConversion(t *testing.T) {
actual := CredentialFromWebAuthn(expected, false).ToWebAuthn()
assert.Equal(t, expected, actual)

actualList := Credentials{*CredentialFromWebAuthn(expected, false)}.ToWebAuthn()
actualList := Credentials{*CredentialFromWebAuthn(expected, false)}.ToWebAuthn(identity.AuthenticatorAssuranceLevel2)
assert.Equal(t, []webauthn.Credential{*expected}, actualList)

actualList = Credentials{*CredentialFromWebAuthn(expected, true)}.ToWebAuthn(identity.AuthenticatorAssuranceLevel1)
assert.Equal(t, []webauthn.Credential{*expected}, actualList)

actualList = Credentials{*CredentialFromWebAuthn(expected, true)}.ToWebAuthn(identity.AuthenticatorAssuranceLevel2)
assert.Len(t, actualList, 0)

actualList = Credentials{*CredentialFromWebAuthn(expected, false)}.ToWebAuthn(identity.AuthenticatorAssuranceLevel1)
assert.Len(t, actualList, 0)

fromWebAuthn := CredentialFromWebAuthn(expected, true)
assert.True(t, fromWebAuthn.IsPasswordless)
fromWebAuthn = CredentialFromWebAuthn(expected, false)
Expand Down
6 changes: 5 additions & 1 deletion selfservice/strategy/webauthn/errors.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package webauthn

import "github.com/ory/jsonschema/v3"
import (
"github.com/ory/jsonschema/v3"
"github.com/pkg/errors"
)

var ErrNotEnoughCredentials = &jsonschema.ValidationError{
Message: "unable to remove this security key because it would lock you out of your account", InstancePtr: "#/webauthn_remove"}
var ErrNoCredentials = errors.New("required credentials not found")
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
"type": "public-key",
"response": {
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
"userHandle": "AAaaaaaaTD6lazfHs42XPg"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"credentials": [
{
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
"attestation_type": "none",
"authenticator": {
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
"sign_count": 3,
"clone_warning": false
},
"display_name": "some-key",
"added_at": "2021-08-17T10:18:55Z",
"is_passwordless": false
}
],
"user_handle": "9dG2o6S7RPeRYfT4d+/prQ=="
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"webauthn_session_data": {
"challenge": "WzZCWULmaq5xTAlA0YYHlqoubqAhe1AWdLRZCIBAMcM",
"user_id": "9dG2o6S7RPeRYfT4d+/prQ==",
"allowed_credentials": [
"OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ=="
],
"userVerification": ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"id": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
"rawId": "OE7fnoAeqaydiBM4-fQsbYMiO-EObq97WYb_c1HuX8Crbsb2777xs-upv7muXE8hOLkm6lQHC1ahegnzw-aIsQ",
"type": "public-key",
"response": {
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAACg",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiV3paQ1dVTG1hcTV4VEFsQTBZWUhscW91YnFBaGUxQVdkTFJaQ0lCQU1jTSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6NDQ1NSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
"signature": "MEQCIHtRzzmLJrTPucNIRpPkstxR8oGJEzrm558LFe2jHTesAiAy2SGuBMDkdVdMJU4WJR2qFSpbAHQUvwG--Gv3vK8vDA",
"userHandle": ""
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"credentials": [
{
"id": "OE7fnoAeqaydiBM4+fQsbYMiO+EObq97WYb/c1HuX8Crbsb2777xs+upv7muXE8hOLkm6lQHC1ahegnzw+aIsQ==",
"public_key": "pQECAyYgASFYIPW2FsD6d/Lc7SU33hMhJUxafOA3JWpsLka8eKO+OPRkIlggkkPt8ocrupQOuvy+8HbQLSLiu899EdchJlWdMPE1tiw=",
"attestation_type": "none",
"authenticator": {
"aaguid": "AAAAAAAAAAAAAAAAAAAAAA==",
"sign_count": 3,
"clone_warning": false
},
"display_name": "some-key",
"added_at": "2021-08-17T10:18:55Z",
"is_passwordless": false
}
],
"user_handle": "RPwiyauuTD6lazfHs42XPg=="
}
Loading

0 comments on commit b4c4fd2

Please sign in to comment.