|
@@ -1,6 +1,14 @@
|
|
|
# Passkeys on Ente
|
|
|
|
|
|
-Passkeys is a colloquial term for a relatively new authentication standard called [WebAuthn](https://en.wikipedia.org/wiki/WebAuthn). Now rolled out to all major browsers and operating systems, it uses asymmetric cryptography to authenticate the user with a server using replay-attack resistant signatures. These processes are usually abstracted from the user through biometric prompts, such as Touch ID/Face ID/Optic ID, Fingerprint Unlock and Windows Hello. These passkeys can also be securely synced by major password managers, such as Bitwarden and 1Password, although the syncing experience can greatly vary due to some operating system restrictions.
|
|
|
+Passkeys is a colloquial term for a relatively new authentication standard
|
|
|
+called [WebAuthn](https://en.wikipedia.org/wiki/WebAuthn). Now rolled out to all
|
|
|
+major browsers and operating systems, it uses asymmetric cryptography to
|
|
|
+authenticate the user with a server using replay-attack resistant signatures.
|
|
|
+These processes are usually abstracted from the user through biometric prompts,
|
|
|
+such as Touch ID/Face ID/Optic ID, Fingerprint Unlock and Windows Hello. These
|
|
|
+passkeys can also be securely synced by major password managers, such as
|
|
|
+Bitwarden and 1Password, although the syncing experience can greatly vary due to
|
|
|
+some operating system restrictions.
|
|
|
|
|
|
## Terms
|
|
|
|
|
@@ -13,13 +21,20 @@ Passkeys is a colloquial term for a relatively new authentication standard calle
|
|
|
|
|
|
## Getting to the passkeys manager
|
|
|
|
|
|
-As of Feb 2024, Ente clients have a button to navigate to a WebView of Ente Accounts. Ente Accounts allows users to add and manage their registered passkeys.
|
|
|
+As of Feb 2024, Ente clients have a button to navigate to a WebView of Ente
|
|
|
+Accounts. Ente Accounts allows users to add and manage their registered
|
|
|
+passkeys.
|
|
|
|
|
|
-❗ Your WebView MUST invoke the operating-system's default browser, or an equivalent browser with matching API parity. Otherwise, the user will not be able to register or use registered WebAuthn credentials.
|
|
|
+❗ Your WebView MUST invoke the operating-system's default browser, or an
|
|
|
+equivalent browser with matching API parity. Otherwise, the user will not be
|
|
|
+able to register or use registered WebAuthn credentials.
|
|
|
|
|
|
### Accounts-Specific Session Token
|
|
|
|
|
|
-When a user clicks this button, the client sends a request for an Accounts-specific JWT session token as shown below. **The Ente Accounts API is restricted to this type of session token, so the user session token cannot be used.** This restriction is a byproduct of the enablement for automatic login.
|
|
|
+When a user clicks this button, the client sends a request for an
|
|
|
+Accounts-specific JWT session token as shown below. **The Ente Accounts API is
|
|
|
+restricted to this type of session token, so the user session token cannot be
|
|
|
+used.** This restriction is a byproduct of the enablement for automatic login.
|
|
|
|
|
|
#### GET /users/accounts-token
|
|
|
|
|
@@ -37,17 +52,33 @@ When a user clicks this button, the client sends a request for an Accounts-speci
|
|
|
|
|
|
### Automatically logging into Accounts
|
|
|
|
|
|
-Clients open a WebView with the URL `https://accounts.ente.io/accounts-handoff?token=<accountsToken>&package=<app package name>`. This page will appear like a normal loading screen to the user, but in the background, the app parses the token and package for usage in subsequent Accounts-related API calls.
|
|
|
+Clients open a WebView with the URL
|
|
|
+`https://accounts.ente.io/accounts-handoff?token=<accountsToken>&package=<app package name>`.
|
|
|
+This page will appear like a normal loading screen to the user, but in the
|
|
|
+background, the app parses the token and package for usage in subsequent
|
|
|
+Accounts-related API calls.
|
|
|
|
|
|
-If valid, the user will be automatically redirected to the passkeys management page. Otherwise, they will be required to login with their Ente credentials.
|
|
|
+If valid, the user will be automatically redirected to the passkeys management
|
|
|
+page. Otherwise, they will be required to login with their Ente credentials.
|
|
|
|
|
|
## Registering a WebAuthn credential
|
|
|
|
|
|
### Requesting publicKey options (begin)
|
|
|
|
|
|
-The registration ceremony starts in the browser. When the user clicks the "Add new passkey" button, a request is sent to the server for "public key" creation options. Although named "public key" options, they actually define customizable parameters for the entire credential creation process. They're like an instructional sheet that defines exactly what we want. As of the creation of this document, the plan is to restrict user authenticators to cross-platform ones, like hardware keys. Platform authenticators, such as TPM, are not portable and are prone to loss.
|
|
|
-
|
|
|
-On the server side, the WebAuthn library generates this information based on data provided from a `webauthn.User` interface. As a result, we satisfy this interface by creating a type with methods returning information from the database. Information stored in the database about credentials are all pre-processed using base64 where necessary.
|
|
|
+The registration ceremony starts in the browser. When the user clicks the "Add
|
|
|
+new passkey" button, a request is sent to the server for "public key" creation
|
|
|
+options. Although named "public key" options, they actually define customizable
|
|
|
+parameters for the entire credential creation process. They're like an
|
|
|
+instructional sheet that defines exactly what we want. As of the creation of
|
|
|
+this document, the plan is to restrict user authenticators to cross-platform
|
|
|
+ones, like hardware keys. Platform authenticators, such as TPM, are not portable
|
|
|
+and are prone to loss.
|
|
|
+
|
|
|
+On the server side, the WebAuthn library generates this information based on
|
|
|
+data provided from a `webauthn.User` interface. As a result, we satisfy this
|
|
|
+interface by creating a type with methods returning information from the
|
|
|
+database. Information stored in the database about credentials are all
|
|
|
+pre-processed using base64 where necessary.
|
|
|
|
|
|
```go
|
|
|
type PasskeyUser struct {
|
|
@@ -162,24 +193,26 @@ func (u *PasskeyUser) WebAuthnCredentials() []webauthn.Credential {
|
|
|
|
|
|
### Pre-processing the options before registration
|
|
|
|
|
|
-Even though the server generates these options, the browser still doesn't understand them. For interoperability, the server's WebAuthn library returns binary data in base64, like IDs and the challenge. However, the browser requires this data back in binary.
|
|
|
+Even though the server generates these options, the browser still doesn't
|
|
|
+understand them. For interoperability, the server's WebAuthn library returns
|
|
|
+binary data in base64, like IDs and the challenge. However, the browser requires
|
|
|
+this data back in binary.
|
|
|
|
|
|
We just have to decode the base64 fields back into `Uint8Array`.
|
|
|
|
|
|
```ts
|
|
|
const options = response.options;
|
|
|
|
|
|
-options.publicKey.challenge = _sodium.from_base64(
|
|
|
- options.publicKey.challenge,
|
|
|
-);
|
|
|
-options.publicKey.user.id = _sodium.from_base64(
|
|
|
- options.publicKey.user.id,
|
|
|
-);
|
|
|
+options.publicKey.challenge = _sodium.from_base64(options.publicKey.challenge);
|
|
|
+options.publicKey.user.id = _sodium.from_base64(options.publicKey.user.id);
|
|
|
```
|
|
|
|
|
|
### Creating the credential
|
|
|
|
|
|
-We use `navigator.credentials.create` with these options to generate the credential. At this point, the user will see a prompt to decide where to save this credential, and probably a biometric authentication gate depending on the platform.
|
|
|
+We use `navigator.credentials.create` with these options to generate the
|
|
|
+credential. At this point, the user will see a prompt to decide where to save
|
|
|
+this credential, and probably a biometric authentication gate depending on the
|
|
|
+platform.
|
|
|
|
|
|
```ts
|
|
|
const newCredential = await navigator.credentials.create(options);
|
|
@@ -187,7 +220,8 @@ const newCredential = await navigator.credentials.create(options);
|
|
|
|
|
|
### Sending the public key to the server (finish)
|
|
|
|
|
|
-The browser returns the newly created credential with a bunch of binary fields, so we have to encode them into base64 for transport to the server.
|
|
|
+The browser returns the newly created credential with a bunch of binary fields,
|
|
|
+so we have to encode them into base64 for transport to the server.
|
|
|
|
|
|
```ts
|
|
|
const attestationObjectB64 = _sodium.to_base64(
|
|
@@ -199,13 +233,18 @@ const clientDataJSONB64 = _sodium.to_base64(
|
|
|
_sodium.base64_variants.URLSAFE_NO_PADDING
|
|
|
```
|
|
|
|
|
|
-Attestation object contains information about the nature of the credential, like what device it was generated on. Client data JSON contains metadata about the credential, like where it is registered to.
|
|
|
+Attestation object contains information about the nature of the credential, like
|
|
|
+what device it was generated on. Client data JSON contains metadata about the
|
|
|
+credential, like where it is registered to.
|
|
|
|
|
|
-After pre-processing, the client sends the public key to the server so it can verify future signatures during authentication.
|
|
|
+After pre-processing, the client sends the public key to the server so it can
|
|
|
+verify future signatures during authentication.
|
|
|
|
|
|
#### POST /passkeys/registration/finish
|
|
|
|
|
|
-When the server receives the new public key credential, it pre-processes the JSON objects so they can fit within the database. This includes base64 encoding `[]byte` slices and their encompassing arrays or objects.
|
|
|
+When the server receives the new public key credential, it pre-processes the
|
|
|
+JSON objects so they can fit within the database. This includes base64 encoding
|
|
|
+`[]byte` slices and their encompassing arrays or objects.
|
|
|
|
|
|
```go
|
|
|
// Convert the PublicKey to base64
|
|
@@ -280,7 +319,11 @@ On retrieval, this process is effectively the opposite.
|
|
|
|
|
|
## Authenticating with a credential
|
|
|
|
|
|
-Passkeys have been integrated into the existing two-factor ceremony. When logging in via SRP or verifying an email OTT, the server checks if the user has any number of credentials setup or has 2FA TOTP enabled. If the user has setup at least one credential, they will be served a `passkeySessionID` which will initiate the authentication ceremony.
|
|
|
+Passkeys have been integrated into the existing two-factor ceremony. When
|
|
|
+logging in via SRP or verifying an email OTT, the server checks if the user has
|
|
|
+any number of credentials setup or has 2FA TOTP enabled. If the user has setup
|
|
|
+at least one credential, they will be served a `passkeySessionID` which will
|
|
|
+initiate the authentication ceremony.
|
|
|
|
|
|
```tsx
|
|
|
const {
|
|
@@ -294,7 +337,9 @@ if (passkeySessionID) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-The client should redirect the user to Accounts with this session ID to prompt credential authentication. We use Accounts as the central WebAuthn hub because credentials are locked to an FQDN.
|
|
|
+The client should redirect the user to Accounts with this session ID to prompt
|
|
|
+credential authentication. We use Accounts as the central WebAuthn hub because
|
|
|
+credentials are locked to an FQDN.
|
|
|
|
|
|
```tsx
|
|
|
window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${passkeySessionID}&redirect=${
|
|
@@ -344,7 +389,8 @@ window.location.href = `${getAccountsURL()}/passkeys/flow?passkeySessionID=${pas
|
|
|
|
|
|
### Pre-processing the options before retrieval
|
|
|
|
|
|
-The browser requires `Uint8Array` versions of the `options` challenge and credential IDs.
|
|
|
+The browser requires `Uint8Array` versions of the `options` challenge and
|
|
|
+credential IDs.
|
|
|
|
|
|
```ts
|
|
|
publicKey.challenge = _sodium.from_base64(
|
|
@@ -369,7 +415,8 @@ const credential = await navigator.credentials.get({
|
|
|
|
|
|
### Pre-processing the credential metadata and signature before authentication
|
|
|
|
|
|
-Before sending the public key and signature to the server, their outputs must be encoded into Base64.
|
|
|
+Before sending the public key and signature to the server, their outputs must be
|
|
|
+encoded into Base64.
|
|
|
|
|
|
```ts
|
|
|
authenticatorData: _sodium.to_base64(
|