|
@@ -1,6 +1,5 @@
|
|
|
-This is a very basic demo of passwordless authentication in the web, also known as "WebAuthn".
|
|
|
|
|
-
|
|
|
|
|
-# Overview
|
|
|
|
|
|
|
+# Overview
|
|
|
|
|
+This is a very basic demo of passwordless authentication in the web, also known as "WebAuthn".
|
|
|
|
|
|
|
|
# Technical Details
|
|
# Technical Details
|
|
|
Note that for convenience, the client always sends the base64URL encoded user name from the UI to
|
|
Note that for convenience, the client always sends the base64URL encoded user name from the UI to
|
|
@@ -133,8 +132,66 @@ persisted in a file "[username].json".
|
|
|
|
|
|
|
|
## Login
|
|
## Login
|
|
|
|
|
|
|
|
|
|
+### Assertion Options from Login
|
|
|
|
|
+For the login, we create a `AssertionOptions` instance from a registered login name (if that login
|
|
|
|
|
+actually exists) which looks like this:
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "challenge": "VlXKhoI7x3T5LxsKP_WPnw",
|
|
|
|
|
+ "timeout": 60000,
|
|
|
|
|
+ "rpId": "demo.larcanum.net",
|
|
|
|
|
+ "allowCredentials": [
|
|
|
|
|
+ {
|
|
|
|
|
+ "type": "public-key",
|
|
|
|
|
+ "id": "vHJFHVmoY37hrUHFObgkw7QkBB1p44uhYlLc4i9r1BHde5XEVObMB8QOKWKpcf4eWEODN2kK5x84pRlNn8etlQ"
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ "userVerification": "discouraged",
|
|
|
|
|
+ "extensions": {
|
|
|
|
|
+ "exts": true,
|
|
|
|
|
+ "uvm": false
|
|
|
|
|
+ },
|
|
|
|
|
+ "status": "ok",
|
|
|
|
|
+ "errorMessage": ""
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+The options again need to be post-processed on the client before they can be used with
|
|
|
|
|
+`navigator.credentials.get` by converting base64URL encoded strings to byte arrays (`Uint8Array`).
|
|
|
|
|
+
|
|
|
|
|
+### Verifying the Credentials
|
|
|
|
|
+The result of `navigator.credentials.get` looks very similar to the one from
|
|
|
|
|
+`navigator.credentials.create` but with the addition of a "signature"
|
|
|
|
|
+
|
|
|
|
|
+- the `signature` field contains the signed challenge from the request object and is the critical
|
|
|
|
|
+ piece of information that the server needs to verify
|
|
|
|
|
+
|
|
|
|
|
+The raw verification request looks like this:
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "id": "vHJFHVmoY37hrUHFObgkw7QkBB1p44uhYlLc4i9r1BHde5XEVObMB8QOKWKpcf4eWEODN2kK5x84pRlNn8etlQ",
|
|
|
|
|
+ "rawId": "vHJFHVmoY37hrUHFObgkw7QkBB1p44uhYlLc4i9r1BHde5XEVObMB8QOKWKpcf4eWEODN2kK5x84pRlNn8etlQ==",
|
|
|
|
|
+ "type": "public-key",
|
|
|
|
|
+ "extensions": {},
|
|
|
|
|
+ "response": {
|
|
|
|
|
+ "authenticatorData": "oRsUvdxlEqdT6Qnbe1LQ/aqF8rHBT3MzNqBH6nFOcf0BAAAACA==",
|
|
|
|
|
+ "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVmxYS2hvSTd4M1Q1THhzS1BfV1BudyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby5sYXJjYW51bS5uZXQiLCJjcm9zc09yaWdpbiI6ZmFsc2UsIm90aGVyX2tleXNfY2FuX2JlX2FkZGVkX2hlcmUiOiJkbyBub3QgY29tcGFyZSBjbGllbnREYXRhSlNPTiBhZ2FpbnN0IGEgdGVtcGxhdGUuIFNlZSBodHRwczovL2dvby5nbC95YWJQZXgifQ==",
|
|
|
|
|
+ "signature": "MEUCIBanHNIJS6ozJe0Nzf0fMJDEeqr/R2J33izif54zuGH1AiEAyOxPUhYfd/7vWnIDh1a4Dind3dTPPjE5O02olEhLbUE="
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Once the credentials have been verified on the server, it generates a JWT for those credentials
|
|
|
|
|
+which can be used to access a simple API protected with JWT bearer authentication.
|
|
|
|
|
+
|
|
|
## Stored Data
|
|
## Stored Data
|
|
|
Currently, we directly store the `AttestationVerificationSuccess` that the client sends to complete
|
|
Currently, we directly store the `AttestationVerificationSuccess` that the client sends to complete
|
|
|
the registration process.
|
|
the registration process.
|
|
|
|
|
|
|
|
-[my username.json](./data/my username.json)
|
|
|
|
|
|
|
+[my username.json](./data/my username.json)
|
|
|
|
|
+
|
|
|
|
|
+## Notes
|
|
|
|
|
+- Credentials are **bound** to a specific domain. If the credentials presented to the passkey are
|
|
|
|
|
+ from a different domain, then the message "This security key doesn't look familiar. Please try
|
|
|
|
|
+ a different one" appears in the passkey selection window.
|