Skip to content

Commit 89a44ca

Browse files
docs: authentication with Passkeys (#507)
* docs: authentication with Passkeys Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * docs: review domain based Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * docs: sign-up Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: sign-in Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: customization Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: review Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: remove allowPin - which I really dislike and should not be used - and add derivationOrigin to NFID, seems supported Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * chore: redo Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: links and wording Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * 📄 Update LLMs.txt snapshot for PR review * docs: choose provider Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> * docs: review Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> --------- Signed-off-by: David Dal Busco <david.dalbusco@outlook.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 9794418 commit 89a44ca

4 files changed

Lines changed: 433 additions & 142 deletions

File tree

.llms-snapshots/llms-full.txt

Lines changed: 147 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -733,121 +733,218 @@ Here are some customization options to tailor your sign-in flow and handle sessi
733733

734734
---
735735

736-
## Sign-In Providers
736+
## Sign Context
737737

738-
Juno supports Internet Identity and NFID, which also offers additional authentication methods like Google and email.
738+
Some options apply to both sign-up and sign-in flows.
739739

740-
**Note:**
741-
742-
You can implement the `signIn` function in your application as many times as you wish, with various configurations. It is also perfectly acceptable to use both Internet Identity and NFID within the same project.
740+
| Option | Type | Default | Description |
741+
| --- | --- | --- | --- |
742+
| `windowGuard` | `boolean` | `true` | Prevents the user from closing the current window/tab while the flow is in progress. Disabling it is discouraged. |
743743

744744
---
745745

746-
### Internet Identity
746+
## Session Expiration
747747

748-
Internet Identity is available at two different URLs: `internetcomputer.org` and `ic0.app`.
748+
To proactively detect when a session duration expires, you can use the pre-bundled Web Worker provided by Juno's SDK.
749749

750-
By default, the SDK uses `internetcomputer.org`.
750+
To do so, you can follow these steps:
751+
752+
1. Copy the worker file provided by Juno's SDK to your app's static folder. For example, to your `public` folder with a NPM `postinstall` script:
753+
754+
```
755+
{ "scripts": { "postinstall": "node -e \"require('fs').cpSync('node_modules/@junobuild/core/dist/workers/', './static/workers', {recursive: true});\"" }}
756+
```
757+
758+
Once configured, run `npm run postinstall` manually to trigger the initial copy. Every time you run `npm ci`, the post-install target will execute, ensuring the worker is copied.
759+
760+
2. Enable the option when you initialize Juno:
761+
762+
```
763+
import { initSatellite } from "@junobuild/core";await initSatellite({ workers: { auth: true }});
764+
```
765+
766+
The `auth` option can accept either `true`, which will default to using a worker located at [https://yourapp/workers/auth.worker.js](https://yourapp/workers/auth.worker.js), or a custom `string` to provide your own URL.
767+
768+
When the session expires, it will automatically be terminated with a standard [sign-out](/docs/build/authentication/development.md#sign-out). Additionally, an event called `junoSignOutAuthTimer` will be thrown at the `document` level. This event can be used, for example, to display a warning to your users or if you wish to reload the window.
751769

752770
```
753-
import { signIn, InternetIdentityProvider } from "@junobuild/core";// Default domain is 'internetcomputer.org'await signIn({ provider: new InternetIdentityProvider({})});
771+
document.addEventListener( "junoSignOutAuthTimer", () => { // Display an information to your users }, { passive: true });
754772
```
755773

756-
You can switch to `ic0.app` by setting the domain option accordingly.
774+
The worker also emits an event named `junoDelegationRemainingTime`, which provides the remaining duration in milliseconds of the authentication delegation. This can be useful if you want to display to your users how much time remains in their active session.
757775

758776
```
759-
import { signIn, InternetIdentityProvider } from "@junobuild/core";await signIn({ provider: new InternetIdentityProvider({ domain: "ic0.app" })});
777+
document.addEventListener( "junoDelegationRemainingTime", ({ detail: remainingTime }) => { // Display the remaining session duration to your users }, { passive: true });
760778
```
761779

762-
We use the former by default because we believe it offers a better user experience and branding.
780+
# Development
781+
782+
This page provides an overview of how to integrate authentication features with the Juno SDK, including sign-in, sign-out, and user session subscription within your app.
763783

764784
**Note:**
765785

766-
It is worth mentioning that your users will be able to sign in to your app with Internet Identity, regardless of which of those two domains they originally created their identity on.
786+
The Juno SDK must be [installed](/docs/setup-the-sdk.md) and initialized in your app to use the authentication features.
767787

768788
---
769789

770-
### NFID
790+
## Sign-up
791+
792+
If your app provides features that require authentication, your users need to sign up to create an identity that grants them access to your application.
793+
794+
### Passkeys
771795

772-
To set up NFID, you need to configure the corresponding provider and provide your application name and a link to your logo.
796+
With Passkeys, sign-up creates a digital key that lives on the user's device — for example in the browser, iCloud Keychain, Google Password Manager, etc.
797+
798+
During sign-up, the user will be asked to use their authenticator twice: once to generate the passkey, and once more to sign their new identity which is then used to interact securely with your satellite.
773799

774800
```
775-
import { signIn, NFIDProvider } from "@junobuild/core";await signIn({ provider: new NFIDProvider({ appName: "Your app name", logoUrl: "https://somewhere.com/your_logo.png" })});
801+
import { signUp } from "@junobuild/core";await signUp({ webauthn: {}});
776802
```
777803

778-
---
804+
**Note:**
779805

780-
## Session Expiration
806+
Returning users don't need to go through sign-up again. They can simply use ([sign-in](#passkeys-1)) with their existing passkey to authenticate.
781807

782-
To proactively detect when a session duration expires, you can use the pre-bundled Web Worker provided by Juno's SDK.
808+
#### Options
783809

784-
To do so, you can follow these steps:
810+
Passkey sign-up can be customized with a handful of options. These let you control how long a session lasts, how the passkey is displayed to the user, and whether you want to track progress in your own UI.
785811

786-
1. Copy the worker file provided by Juno's SDK to your app's static folder. For example, to your `public` folder with a NPM `postinstall` script:
812+
| Option | Type | Default | Description |
813+
| --- | --- | --- | --- |
814+
| `maxTimeToLiveInMilliseconds` | `number` | **4 hours** | Maximum lifetime of the user's session in **milliseconds**. Once expired, the session cannot be extended. |
815+
| `onProgress` | `(progress) => void` | | Callback fired at each step of the sign-up flow (e.g., creating credential, validating, signing). Useful if you want to show progress indicators in your UI. |
816+
| `passkey` | `CreatePasskeyOptions` | | Options for how the passkey should be created. |
817+
818+
The `passkey` option accepts the following fields:
819+
820+
| Option | Type | Default | Description |
821+
| --- | --- | --- | --- |
822+
| `appId.id` | `string` | Current URL `hostname` | Domain your passkeys are tied to (e.g., `example.com` or `login.example.com`). Subdomains are supported. |
823+
| `user.displayName` | `string` | `document.title` | Friendly name for the account (e.g., `"Maria Sanchez"`). Helps the user recognize which passkey belongs to them. |
824+
| `user.name` | `string` | `displayName` | User-recognizable account identifier (e.g., email, username, or phone number). Distinguishes between accounts. |
825+
826+
Example with options:
787827

788828
```
789-
{ "scripts": { "postinstall": "node -e \"require('fs').cpSync('node_modules/@junobuild/core/dist/workers/', './static/workers', {recursive: true});\"" }}
829+
import { signUp } from "@junobuild/core";await signUp({ webauthn: { options: { maxTimeToLiveInMilliseconds: 1000 * 60 * 60, // 1 hour onProgress: ({ step, state }) => { console.log("Step:", step, "State:", state); }, passkey: { displayName: "My Cool App" // or user input } } }});
790830
```
791831

792-
Once configured, run `npm run postinstall` manually to trigger the initial copy. Every time you run `npm ci`, the post-install target will execute, ensuring the worker is copied.
832+
**Tip:**
793833

794-
2. Enable the option when you initialize Juno:
834+
It's common to let the user choose a nickname during sign-up.
835+
836+
This nickname can be passed as the `displayName` in the `passkey` option so the passkey is easy to recognize the next time they sign in (e.g. in iCloud Keychain or Google Password Manager).
837+
838+
#### Checking Availability
839+
840+
Not every browser or device supports Passkeys. You can check availability before showing a sign-up button with:
795841

796842
```
797-
import { initSatellite } from "@junobuild/core";await initSatellite({ workers: { auth: true }});
843+
import { isWebAuthnAvailable } from "@junobuild/core";if (await isWebAuthnAvailable()) { // Show Passkey sign-up option}
798844
```
799845

800-
The `auth` option can accept either `true`, which will default to using a worker located at [https://yourapp/workers/auth.worker.js](https://yourapp/workers/auth.worker.js), or a custom `string` to provide your own URL.
846+
### Internet Identity / NFID
801847

802-
When the session expires, it will automatically be terminated with a standard [sign-out](/docs/build/authentication/development.md#sign-out). Additionally, an event called `junoSignOutAuthTimer` will be thrown at the `document` level. This event can be used, for example, to display a warning to your users or if you wish to reload the window.
848+
With Internet Identity and NFID there is no separate sign-up step. Users always go through sign-in, and if it's their first time, that flow will automatically create their identity.
849+
850+
In practice, your UI could simply show a button like "Continue with Internet Identity" or "Continue with NFID".
851+
852+
---
853+
854+
## Sign-in
855+
856+
If your app provides features that require authentication, your users need to sign in to access their identity and continue using your application securely.
857+
858+
### Passkeys
859+
860+
With Passkeys, returning users sign in using the digital key previously created on their device — for example in the browser, iCloud Keychain, Google Password Manager, etc.
861+
862+
The user will be asked to use their authenticator to prove possession of the passkey and re-establish a valid session with your satellite.
803863

804864
```
805-
document.addEventListener( "junoSignOutAuthTimer", () => { // Display an information to your users }, { passive: true });
865+
import { signIn } from "@junobuild/core";await signIn({ webauthn: {}});
806866
```
807867

808-
The worker also emits an event named `junoDelegationRemainingTime`, which provides the remaining duration in milliseconds of the authentication delegation. This can be useful if you want to display to your users how much time remains in their active session.
868+
**Note:**
869+
870+
New users must first go through ([sign-up](#passkeys)) to create a passkey before they can sign in.
871+
872+
#### Options
873+
874+
Passkey sign-in can also be customized with options similar to sign-up. These let you control how long a session lasts and whether you want to track progress in your own UI.
875+
876+
| Option | Type | Default | Description |
877+
| --- | --- | --- | --- |
878+
| `maxTimeToLiveInMilliseconds` | `number` | **4 hours** | Maximum lifetime of the user's session in **milliseconds**. Once expired, the session cannot be extended. |
879+
| `onProgress` | `(progress) => void` | | Callback fired at each step of the sign-up flow (e.g., fetching credential, validating, signing). Useful to customize your UI. |
880+
881+
Example with options:
809882

810883
```
811-
document.addEventListener( "junoDelegationRemainingTime", ({ detail: remainingTime }) => { // Display the remaining session duration to your users }, { passive: true });
884+
import { signIn } from "@junobuild/core";await signIn({ webauthn: { options: { maxTimeToLiveInMilliseconds: 1000 * 60 * 60, // 1 hour onProgress: ({ step, state }) => { console.log("Step:", step, "State:", state); } } }});
812885
```
813886

814-
# Development
887+
### Internet Identity
815888

816-
This page provides an overview of how to integrate authentication features with the Juno SDK, including sign-in, sign-out, and user session subscription within your app.
889+
When a user signs in with Internet Identity, they log in with the provider to confirm their identity. If successful, a session is created and the user can interact with your satellite. There's no separate sign-up step — the account in your satellite is created automatically the first time they sign in, so the user can access your services right away.
817890

818-
**Note:**
891+
```
892+
import { signIn } from "@junobuild/core";await signIn({ ii: {}});
893+
```
819894

820-
The Juno SDK must be [installed](/docs/setup-the-sdk.md) and initialized in your app to use the authentication features.
895+
#### Options
821896

822-
---
897+
Internet Identity sign-in can be customized with options that let you control session lifetime, provider configuration, or track progress during the flow.
823898

824-
## Sign-in
899+
| Option | Type | Default | Description |
900+
| --- | --- | --- | --- |
901+
| `maxTimeToLiveInNanoseconds` | `BigInt(4 * 60 * 60 * 1000 * 1000 * 1000)` | **4 hours** | Maximum lifetime of the user's session in **nanoseconds**. Once expired, the session cannot be extended. |
902+
| `windowed` | `boolean` | `true` | By default, the authentication flow is presented in a popup window on desktop that is automatically centered on the browser. This behavior can be turned off by setting the option to `false`, causing the authentication flow to happen in a separate tab instead. |
903+
| `derivationOrigin` | `string` or `URL` | | The main domain to be used to ensure your users are identified with the same public ID, regardless of which of your satellite's URLs they use to access your application. |
904+
| `onProgress` | `(progress) => void` | | Callback for provider sign-in and user creation/loading. |
905+
| `domain` | `internetcomputer.org` or `ic0.app` | `internetcomputer.org` | The domain on which to open Internet Identity. |
825906

826-
You can authorize an existing or new user with the identity provider using `signIn`.
907+
Example with options:
827908

828909
```
829-
import { signIn } from "@junobuild/core";await signIn();
910+
await signIn({ ii: { options: { maxTimeToLiveInNanoseconds: BigInt(24 * 60 * 60 * 1000 * 1000 * 1000), // 1 day onProgress: ({ step, state }) => { console.log("Step:", step, "State:", state); }, derivationOrigin: "https://myapp.com" } }});
830911
```
831912

832-
The sign-in feature supports following customization options:
913+
#### Handling Errors
833914

834-
| Option | Default Value | Description |
835-
| --- | --- | --- |
836-
| `maxTimeToLive` | `BigInt(4 * 60 * 60 * 1000 * 1000 * 1000)` | Specifies the duration for the session (defaults to **4 hours**). It's **important** to note that this duration remains constant, whether the users are active or inactive. |
837-
| `windowed` | `true` | By default, the authentication flow is presented in a popup window on desktop that is automatically centered on the browser. This behavior can be turned off by setting the option to `false`, causing the authentication flow to happen in a separate tab instead. |
838-
| `derivationOrigin` | — | The main domain to be used to ensure your users are identified with the same public ID, regardless of which of your satellite’s URLs they use to access your application. |
839-
| `allowPin` | `false` | We consider the specific PIN authentication method of [Internet Identity](https://internetcomputer.org/docs/current/references/ii-spec#client-authentication-protocol) as "insecure" because users can easily lose their login information if they do not register a passphrase, particularly as Safari clears the browser cache every two weeks in cases of inactivity. This is why we **disable** it by default. |
915+
If the sign-in flow encounters an error, an exception will be thrown.
840916

841-
You can configure the default sign-in flow that uses Internet Identity. You can also set NFID as a provider. Check out the [advanced Sign-in guidelines](/docs/build/authentication/customization.md#sign-in-providers) for more details.
917+
When a user cancels sign-in with Internet Identity (e.g., by closing the modal), the library throws a `SignInUserInterruptError`. This error indicates that the user intentionally interrupted the sign-in process, and it's generally best practice to ignore it rather than showing an error message.
842918

843-
### Handling Errors
919+
```
920+
import { signIn } from "@junobuild/core";try { await signIn({ ii: {} });} catch (error: unknown) { if (error instanceof SignInUserInterruptError) { // User canceled sign-in, no need to show an error return; } // Handle other errors console.error("Sign-in failed:", error);}
921+
```
844922

845-
If the sign-in flow encounters an error, an exception will be thrown.
923+
### NFID
924+
925+
NFID flows follow a similar pattern to authentication with Internet Identity. The user signs in with the provider, and if successful, a session is created so they can interact with your satellite. If it's their first time, the account in your satellite is created automatically.
926+
927+
```
928+
import { signIn } from "@junobuild/core";await signIn({ nfid: {}});
929+
```
930+
931+
#### Options
932+
933+
NFID sign-in can be customized with following options:
934+
935+
| Option | Default Value | Default | Description |
936+
| --- | --- | --- | --- |
937+
| `maxTimeToLiveInNanoseconds` | `BigInt(4 * 60 * 60 * 1000 * 1000 * 1000)` | **4 hours** | Maximum lifetime of the user's session in **nanoseconds**. Once expired, the session cannot be extended. |
938+
| `windowed` | `boolean` | `true` | By default, the authentication flow is presented in a popup window on desktop that is automatically centered on the browser. This behavior can be turned off by setting the option to `false`, causing the authentication flow to happen in a separate tab instead. |
939+
| `derivationOrigin` | `string` or `URL` | | The main domain to be used to ensure your users are identified with the same public ID, regardless of which of your satellite's URLs they use to access your application. |
940+
| `onProgress` | `(progress) => void` | | Callback for provider sign-in and user creation/loading. |
941+
| `appName` | `string` | | The name of your application, shown to the user during sign-in. |
942+
| `logoUrl` | `string` | | URL of your application's logo, shown to the user during sign-in. |
846943

847-
When a user cancels sign-in (e.g., by closing the modal), the library throws a `SignInUserInterruptError`. This error indicates that the user intentionally interrupted the sign-in process, and it's generally best practice to ignore it rather than showing an error message.
944+
Example with options:
848945

849946
```
850-
import { signIn } from "@junobuild/core";try { await signIn();} catch (error: unknown) { if (error instanceof SignInUserInterruptError) { // User canceled sign-in, no need to show an error return; } // Handle other errors console.error("Sign-in failed:", error);}
947+
import { signIn } from "@junobuild/core";await signIn({ nfid: { options: { maxTimeToLiveInNanoseconds: BigInt(24 * 60 * 60 * 1000 * 1000 * 1000), // 1 day onProgress: ({ step, state }) => { console.log("Step:", step, "State:", state); }, appName: "My Cool App", logoUrl: "https://myapp.com/logo.png" } }});
851948
```
852949

853950
---

0 commit comments

Comments
 (0)