@icp-sdk/auth 5.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,25 +40,109 @@ Here's a simple example of how to use the `@icp-sdk/auth` package to authenticat
40
40
  ```typescript
41
41
  import { AuthClient } from '@icp-sdk/auth/client';
42
42
 
43
- const identityProvider = 'https://id.ai/';
43
+ const authClient = new AuthClient();
44
+
45
+ // restore an existing session if there is one, otherwise sign in
46
+ let identity;
47
+ try {
48
+ identity = authClient.isAuthenticated()
49
+ ? await authClient.getIdentity()
50
+ : await authClient.signIn();
51
+ } catch (error) {
52
+ console.error('Sign-in failed:', error);
53
+ throw error;
54
+ }
55
+
56
+ console.log('Identity:', identity.getPrincipal().toString());
44
57
 
45
- const authClient = await AuthClient.create();
46
- const identity = authClient.getIdentity(); // At this point, you'll get a Principal.anonymous()
58
+ // later, to end the session
59
+ await authClient.logout();
60
+ ```
47
61
 
48
- async function onSuccess() {
49
- console.log('Login successful');
62
+ ### One-Click OpenID Sign-In
50
63
 
51
- const identity = authClient.getIdentity(); // At this point, you'll get an authenticated identity
52
- console.log(authClient.isAuthenticated()); // true
64
+ Skip the Internet Identity authentication method screen and offer sign-in options like Google directly in your app:
65
+
66
+ ```typescript
67
+ const authClient = new AuthClient({
68
+ openIdProvider: 'google', // or 'apple' or 'microsoft'
69
+ });
70
+ ```
71
+
72
+ ### Requesting Identity Attributes
73
+
74
+ Internet Identity can provide signed identity attributes (e.g., email) alongside authentication. Your backend canister initiates the flow by issuing a nonce tied to the action — this way, even if an attribute bundle is intercepted, it can't be replayed or used for a different action.
75
+
76
+ Here's a registration flow where the backend needs the user's email:
77
+
78
+ ```typescript
79
+ import { AuthClient } from '@icp-sdk/auth/client';
80
+ import { AttributesIdentity } from '@icp-sdk/core/identity';
81
+ import { HttpAgent, Actor } from '@icp-sdk/core/agent';
82
+
83
+ const authClient = new AuthClient();
84
+
85
+ // the backend issues a nonce scoped to registration —
86
+ // this starts the action and binds the upcoming attributes to it
87
+ const anonymousAgent = await HttpAgent.create();
88
+ const backend = Actor.createActor(backendIdl, { agent: anonymousAgent, canisterId });
89
+ const nonce: Uint8Array = await backend.registerBegin();
90
+
91
+ // sign-in and attribute request happen in parallel — the user sees a single II interaction
92
+ try {
93
+ const signInPromise = authClient.signIn();
94
+ const attributesPromise = authClient.requestAttributes({ keys: ['email'], nonce });
95
+
96
+ await signInPromise;
97
+ const { data, signature } = await attributesPromise;
98
+
99
+ // wrap the identity so the signed attributes are included in the canister call
100
+ const identityWithAttributes = new AttributesIdentity({
101
+ inner: await authClient.getIdentity(),
102
+ attributes: { data, signature },
103
+ signer: { canisterId: Principal.fromText('rdmx6-jaaaa-aaaaa-aaadq-cai') }, // Internet Identity canister ID
104
+ });
105
+ const agent = await HttpAgent.create({ identity: identityWithAttributes });
106
+ const app = Actor.createActor(appIdl, { agent, canisterId });
107
+
108
+ // the backend verifies the nonce, origin, and timestamp, then extracts the email
109
+ await app.registerFinish();
110
+ } catch (error) {
111
+ console.error('Registration failed:', error);
53
112
  }
113
+ ```
114
+
115
+ The signed attribute bundle includes implicit fields that your backend canister should verify:
116
+
117
+ - **`implicit:nonce`** — ties the attributes to a specific canister-initiated action, preventing replay and cross-action reuse. Must originate from the backend, not the frontend.
118
+ - **`implicit:origin`** — the requesting origin, verified by the canister to prevent a malicious dapp from forwarding attribute bundles to your backend.
119
+ - **`implicit:issued_at_timestamp_ns`** — issuance timestamp, allowing the canister to reject stale attributes even if the nonce hasn't expired yet.
120
+
121
+ > Attributes can also be requested after sign-in — for example, when a user later triggers an action like linking an email. The flow is the same: the backend issues a nonce for that action, the frontend calls `requestAttributes`, and the backend verifies the result.
122
+
123
+ #### OpenID-Scoped Attributes
54
124
 
55
- await authClient.login({
56
- identityProvider,
57
- onSuccess,
125
+ When using one-click sign-in, attributes can be scoped to the OpenID provider. Scoped attributes have implicit consent — the user authenticates and shares attributes in a single step without an additional prompt:
126
+
127
+ ```typescript
128
+ import { AuthClient, scopedKeys } from '@icp-sdk/auth/client';
129
+
130
+ const authClient = new AuthClient({
131
+ openIdProvider: 'google',
58
132
  });
59
133
 
60
- // later in your app
61
- await authClient.logout();
134
+ const nonce: Uint8Array = await backend.registerBegin();
135
+ const signInPromise = authClient.signIn();
136
+ // requests name, email, and verified_email from the
137
+ // Google account linked to the user's Internet Identity
138
+ const attributesPromise = authClient.requestAttributes({
139
+ keys: scopedKeys({ openIdProvider: 'google' }),
140
+ nonce,
141
+ });
142
+
143
+ await signInPromise;
144
+ const { data, signature } = await attributesPromise;
145
+ // ... wrap with AttributesIdentity and complete the action as above
62
146
  ```
63
147
 
64
148
  Additional documentation can be found [here](https://js.icp.build/auth/latest/).
@@ -1,177 +1,173 @@
1
- import { Identity, SignIdentity } from '@icp-sdk/core/agent';
2
- import { DelegationChain, PartialIdentity } from '@icp-sdk/core/identity';
3
- import { Principal } from '@icp-sdk/core/principal';
4
- import { IdleManager, IdleManagerOptions } from './idle-manager.ts';
5
- import { AuthClientStorage } from './storage.ts';
1
+ import { type Identity, type SignIdentity } from '@icp-sdk/core/agent';
2
+ import { type PartialIdentity } from '@icp-sdk/core/identity';
3
+ import type { Principal } from '@icp-sdk/core/principal';
4
+ import { IdleManager, type IdleManagerOptions } from './idle-manager.js';
5
+ import { type AuthClientStorage } from './storage.js';
6
6
  declare const ECDSA_KEY_LABEL = "ECDSA";
7
7
  declare const ED25519_KEY_LABEL = "Ed25519";
8
8
  type BaseKeyType = typeof ECDSA_KEY_LABEL | typeof ED25519_KEY_LABEL;
9
- export declare const ERROR_USER_INTERRUPT = "UserInterrupt";
9
+ export type OpenIdProvider = 'google' | 'apple' | 'microsoft';
10
+ export declare const OPENID_PROVIDER_URLS: {
11
+ readonly google: "https://accounts.google.com";
12
+ readonly apple: "https://appleid.apple.com";
13
+ readonly microsoft: "https://login.microsoftonline.com/{tid}/v2.0";
14
+ };
15
+ declare const DEFAULT_OPENID_SCOPE_KEYS: readonly ["name", "email", "verified_email"];
10
16
  /**
11
- * List of options for creating an {@link AuthClient}.
17
+ * Options for creating an {@link AuthClient}.
12
18
  */
13
19
  export interface AuthClientCreateOptions {
14
20
  /**
15
- * An {@link SignIdentity} or {@link PartialIdentity} to authenticate via delegation.
21
+ * An identity to authenticate via delegation.
16
22
  */
17
23
  identity?: SignIdentity | PartialIdentity;
18
24
  /**
19
- * Optional storage with get, set, and remove. Uses {@link IdbStorage} by default.
20
- * @see {@link AuthClientStorage}
25
+ * Persistent storage backend. Defaults to IndexedDB.
26
+ * @default IdbStorage
21
27
  */
22
28
  storage?: AuthClientStorage;
23
29
  /**
24
- * Type to use for the base key.
30
+ * Type of session key to generate on each sign-in.
25
31
  *
26
- * If you are using a custom storage provider that does not support CryptoKey storage,
27
- * you should use `Ed25519` as the key type, as it can serialize to a string.
32
+ * Use `'Ed25519'` when your storage provider does not support `CryptoKey`.
28
33
  * @default 'ECDSA'
29
34
  */
30
35
  keyType?: BaseKeyType;
31
36
  /**
32
- * Options to handle idle timeouts
37
+ * Idle timeout configuration.
33
38
  * @default after 10 minutes, invalidates the identity
34
39
  */
35
40
  idleOptions?: IdleOptions;
36
41
  /**
37
- * Options to handle login, passed to the login method
42
+ * Identity provider URL.
43
+ * @default "https://id.ai/authorize"
38
44
  */
39
- loginOptions?: AuthClientLoginOptions;
45
+ identityProvider?: string | URL;
46
+ /**
47
+ * Derivation origin for the identity provider.
48
+ * @see https://github.com/dfinity/internet-identity/blob/main/docs/internet-identity-spec.adoc
49
+ */
50
+ derivationOrigin?: string | URL;
51
+ /**
52
+ * Window features string for the authentication popup.
53
+ * @example "toolbar=0,location=0,menubar=0,width=500,height=500,left=100,top=100"
54
+ */
55
+ windowOpenerFeatures?: string;
56
+ /**
57
+ * OpenID provider for one-click sign-in. When set, the identity provider
58
+ * URL includes an `openid` search param so the user authenticates via
59
+ * the chosen provider (e.g. Google) instead of seeing Internet Identity directly.
60
+ */
61
+ openIdProvider?: OpenIdProvider;
40
62
  }
41
63
  export interface IdleOptions extends IdleManagerOptions {
42
64
  /**
43
- * Disables idle functionality for {@link IdleManager}
65
+ * Disables idle functionality entirely.
44
66
  * @default false
45
67
  */
46
68
  disableIdle?: boolean;
47
69
  /**
48
- * Disables default idle behavior - call logout & reload window
70
+ * Disables the default idle callback (logout & reload).
49
71
  * @default false
50
72
  */
51
73
  disableDefaultIdleCallback?: boolean;
52
74
  }
53
- export type OnSuccessFunc = (() => void | Promise<void>) | ((message: InternetIdentityAuthResponseSuccess) => void | Promise<void>);
54
- export type OnErrorFunc = (error?: string) => void | Promise<void>;
55
- export interface AuthClientLoginOptions {
56
- /**
57
- * Identity provider
58
- * @default "https://identity.internetcomputer.org"
59
- */
60
- identityProvider?: string | URL;
75
+ /**
76
+ * Options for {@link AuthClient.signIn}.
77
+ */
78
+ export interface AuthClientSignInOptions {
61
79
  /**
62
- * Expiration of the authentication in nanoseconds
63
- * @default BigInt(8) hours * BigInt(3_600_000_000_000) nanoseconds
80
+ * Maximum lifetime of the delegation in nanoseconds.
81
+ * @default 8 hours
64
82
  */
65
83
  maxTimeToLive?: bigint;
66
84
  /**
67
- * If present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS).
85
+ * Restrict the delegation to specific canisters.
68
86
  */
69
- allowPinAuthentication?: boolean;
70
- /**
71
- * Origin for Identity Provider to use while generating the delegated identity. For II, the derivation origin must authorize this origin by setting a record at `<derivation-origin>/.well-known/ii-alternative-origins`.
72
- * @see https://github.com/dfinity/internet-identity/blob/main/docs/internet-identity-spec.adoc
73
- */
74
- derivationOrigin?: string | URL;
87
+ targets?: Principal[];
88
+ }
89
+ export interface SignedAttributes {
90
+ data: Uint8Array;
91
+ signature: Uint8Array;
92
+ }
93
+ /**
94
+ * Manages authentication and identity for Internet Computer web apps.
95
+ *
96
+ * @example
97
+ * const authClient = new AuthClient();
98
+ *
99
+ * const identity = authClient.isAuthenticated()
100
+ * ? await authClient.getIdentity()
101
+ * : await authClient.signIn();
102
+ */
103
+ export declare class AuthClient {
104
+ #private;
105
+ idleManager: IdleManager | undefined;
106
+ constructor(options?: AuthClientCreateOptions);
75
107
  /**
76
- * Auth Window feature config string
77
- * @example "toolbar=0,location=0,menubar=0,width=500,height=500,left=100,top=100"
108
+ * Returns the current identity, restoring a previous session if available.
78
109
  */
79
- windowOpenerFeatures?: string;
110
+ getIdentity(): Promise<Identity>;
80
111
  /**
81
- * Callback once login has completed
112
+ * Checks whether the user has an active, non-expired session.
82
113
  */
83
- onSuccess?: OnSuccessFunc;
114
+ isAuthenticated(): boolean;
84
115
  /**
85
- * Callback in case authentication fails
116
+ * Opens the identity provider, requests a delegation, and returns the authenticated identity.
117
+ *
118
+ * @param options - Sign-in options.
119
+ * @param options.maxTimeToLive - Maximum lifetime of the delegation in nanoseconds.
120
+ * @param options.targets - Restrict the delegation to specific canisters.
121
+ * @returns The authenticated identity.
122
+ * @throws When authentication fails.
123
+ *
124
+ * @example
125
+ * try {
126
+ * const identity = await authClient.signIn();
127
+ * } catch (error) {
128
+ * console.error('Sign-in failed:', error);
129
+ * }
86
130
  */
87
- onError?: OnErrorFunc;
131
+ signIn(options?: AuthClientSignInOptions): Promise<Identity>;
88
132
  /**
89
- * Extra values to be passed in the login request during the authorize-ready phase
133
+ * Requests signed identity attributes from the identity provider.
134
+ *
135
+ * @param params - Request parameters.
136
+ * @param params.keys - Attribute keys to request (e.g. `['email', 'name']`).
137
+ * @param params.nonce - 32-byte nonce issued by the RP canister.
138
+ * @returns Signed attribute data and signature.
139
+ * @throws When the identity provider returns an error or an invalid response.
140
+ */
141
+ requestAttributes(params: {
142
+ keys: string[];
143
+ nonce: Uint8Array;
144
+ }): Promise<SignedAttributes>;
145
+ /**
146
+ * Clears the stored session and resets the client to an anonymous state.
147
+ *
148
+ * @param options - Logout options.
149
+ * @param options.returnTo - URL to navigate to after logout.
90
150
  */
91
- customValues?: Record<string, unknown>;
92
- }
93
- export interface InternetIdentityAuthResponseSuccess {
94
- kind: 'authorize-client-success';
95
- delegations: {
96
- delegation: {
97
- pubkey: Uint8Array;
98
- expiration: bigint;
99
- targets?: Principal[];
100
- };
101
- signature: Uint8Array;
102
- }[];
103
- userPublicKey: Uint8Array;
104
- authnMethod: 'passkey' | 'pin' | 'recovery';
105
- }
106
- /**
107
- * Tool to manage authentication and identity
108
- * @see {@link AuthClient}
109
- */
110
- export declare class AuthClient {
111
- private _identity;
112
- private _key;
113
- private _chain;
114
- private _storage;
115
- idleManager: IdleManager | undefined;
116
- private _createOptions;
117
- private _idpWindow?;
118
- private _eventHandler?;
119
- /**
120
- * Create an AuthClient to manage authentication and identity
121
- * @param {AuthClientCreateOptions} options - Options for creating an {@link AuthClient}
122
- * @see {@link AuthClientCreateOptions}
123
- * @param options.identity Optional Identity to use as the base
124
- * @see {@link SignIdentity}
125
- * @param options.storage Storage mechanism for delegation credentials
126
- * @see {@link AuthClientStorage}
127
- * @param options.keyType Type of key to use for the base key
128
- * @param {IdleOptions} options.idleOptions Configures an {@link IdleManager}
129
- * @see {@link IdleOptions}
130
- * Default behavior is to clear stored identity and reload the page when a user goes idle, unless you set the disableDefaultIdleCallback flag or pass in a custom idle callback.
131
- * @example
132
- * const authClient = await AuthClient.create({
133
- * idleOptions: {
134
- * disableIdle: true
135
- * }
136
- * })
137
- */
138
- static create(options?: AuthClientCreateOptions): Promise<AuthClient>;
139
- protected constructor(_identity: Identity | PartialIdentity, _key: SignIdentity | PartialIdentity, _chain: DelegationChain | null, _storage: AuthClientStorage, idleManager: IdleManager | undefined, _createOptions: AuthClientCreateOptions | undefined, _idpWindow?: Window | undefined, _eventHandler?: ((event: MessageEvent) => void) | undefined);
140
- private _registerDefaultIdleCallback;
141
- private _handleSuccess;
142
- getIdentity(): Identity;
143
- isAuthenticated(): Promise<boolean>;
144
- /**
145
- * AuthClient Login - Opens up a new window to authenticate with Internet Identity
146
- * @param {AuthClientLoginOptions} options - Options for logging in, merged with the options set during creation if any. Note: we only perform a shallow merge for the `customValues` property.
147
- * @param options.identityProvider Identity provider
148
- * @param options.maxTimeToLive Expiration of the authentication in nanoseconds
149
- * @param options.allowPinAuthentication If present, indicates whether or not the Identity Provider should allow the user to authenticate and/or register using a temporary key/PIN identity. Authenticating dapps may want to prevent users from using Temporary keys/PIN identities because Temporary keys/PIN identities are less secure than Passkeys (webauthn credentials) and because Temporary keys/PIN identities generally only live in a browser database (which may get cleared by the browser/OS).
150
- * @param options.derivationOrigin Origin for Identity Provider to use while generating the delegated identity
151
- * @param options.windowOpenerFeatures Configures the opened authentication window
152
- * @param options.onSuccess Callback once login has completed
153
- * @param options.onError Callback in case authentication fails
154
- * @param options.customValues Extra values to be passed in the login request during the authorize-ready phase. Note: we only perform a shallow merge for the `customValues` property.
155
- * @example
156
- * const authClient = await AuthClient.create();
157
- * authClient.login({
158
- * identityProvider: 'http://<canisterID>.127.0.0.1:8000',
159
- * maxTimeToLive: BigInt (7) * BigInt(24) * BigInt(3_600_000_000_000), // 1 week
160
- * windowOpenerFeatures: "toolbar=0,location=0,menubar=0,width=500,height=500,left=100,top=100",
161
- * onSuccess: () => {
162
- * console.log('Login Successful!');
163
- * },
164
- * onError: (error) => {
165
- * console.error('Login Failed: ', error);
166
- * }
167
- * });
168
- */
169
- login(options?: AuthClientLoginOptions): Promise<void>;
170
- private _getEventHandler;
171
- private _handleFailure;
172
- private _removeEventListener;
173
151
  logout(options?: {
174
152
  returnTo?: string;
175
153
  }): Promise<void>;
176
154
  }
155
+ /**
156
+ * Scopes attribute keys to an OpenID provider.
157
+ *
158
+ * When using one-click sign-in, attributes can be scoped to the same provider
159
+ * so the user grants access in a single step without an additional prompt.
160
+ *
161
+ * @param params.openIdProvider - The OpenID provider the keys should be scoped to.
162
+ * @param params.keys - The attribute keys to scope. Defaults to `['name', 'email', 'verified_email']`.
163
+ * @returns The scoped attribute keys as `openid:<provider-url>:<key>`.
164
+ *
165
+ * @example
166
+ * scopedKeys({ openIdProvider: 'google', keys: ['email'] });
167
+ * // ['openid:https://accounts.google.com:email']
168
+ */
169
+ export declare function scopedKeys<P extends keyof typeof OPENID_PROVIDER_URLS, K extends string = (typeof DEFAULT_OPENID_SCOPE_KEYS)[number]>(params: {
170
+ openIdProvider: P;
171
+ keys?: readonly K[];
172
+ }): `openid:${(typeof OPENID_PROVIDER_URLS)[P]}:${K}`[];
177
173
  export {};