@logto/client 2.2.1 → 2.2.3
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 +0 -2
- package/lib/adapter/types.d.ts +30 -1
- package/lib/errors.cjs +2 -0
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +2 -0
- package/lib/index.cjs +107 -9
- package/lib/index.d.ts +93 -1
- package/lib/index.js +107 -9
- package/lib/types/index.d.ts +36 -0
- package/lib/utils/requester.cjs +7 -0
- package/lib/utils/requester.d.ts +7 -0
- package/lib/utils/requester.js +7 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
The Logto JavaScript Client SDK written in TypeScript. Check out our [docs](https://docs.logto.io/sdk/JavaScript/client/) for more information.
|
|
7
7
|
|
|
8
|
-
We also provide [文档](https://docs.logto.io/zh-cn/sdk/JavaScript/sdk/client/) in Simplified Chinese.
|
|
9
|
-
|
|
10
8
|
## Installation
|
|
11
9
|
|
|
12
10
|
### Using npm
|
package/lib/adapter/types.d.ts
CHANGED
|
@@ -22,13 +22,23 @@ export declare enum CacheKey {
|
|
|
22
22
|
*/
|
|
23
23
|
Jwks = "jwks"
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The storage object that allows the client to persist data.
|
|
27
|
+
*
|
|
28
|
+
* It's compatible with the `localStorage` API.
|
|
29
|
+
*/
|
|
25
30
|
export type Storage<Keys extends string> = {
|
|
26
31
|
getItem(key: Keys): Promise<Nullable<string>>;
|
|
27
32
|
setItem(key: Keys, value: string): Promise<void>;
|
|
28
33
|
removeItem(key: Keys): Promise<void>;
|
|
29
34
|
};
|
|
30
35
|
export type InferStorageKey<S> = S extends Storage<infer Key> ? Key : never;
|
|
31
|
-
|
|
36
|
+
/** The navigation function that redirects the user to the specified URL. */
|
|
37
|
+
export type Navigate = (url: string) => void | Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* The adapter object that allows the customizations of the client behavior
|
|
40
|
+
* for different environments.
|
|
41
|
+
*/
|
|
32
42
|
export type ClientAdapter = {
|
|
33
43
|
requester: Requester;
|
|
34
44
|
storage: Storage<StorageKey | PersistKey>;
|
|
@@ -39,7 +49,26 @@ export type ClientAdapter = {
|
|
|
39
49
|
*/
|
|
40
50
|
unstable_cache?: Storage<CacheKey>;
|
|
41
51
|
navigate: Navigate;
|
|
52
|
+
/**
|
|
53
|
+
* The function that generates a random state string.
|
|
54
|
+
*
|
|
55
|
+
* @returns The state string.
|
|
56
|
+
*/
|
|
42
57
|
generateState: () => string;
|
|
58
|
+
/**
|
|
59
|
+
* The function that generates a random code verifier string for PKCE.
|
|
60
|
+
*
|
|
61
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc7636.html| RFC 7636}
|
|
62
|
+
* @returns The code verifier string.
|
|
63
|
+
*/
|
|
43
64
|
generateCodeVerifier: () => string;
|
|
65
|
+
/**
|
|
66
|
+
* The function that generates a code challenge string based on the code verifier
|
|
67
|
+
* for PKCE.
|
|
68
|
+
*
|
|
69
|
+
* @see {@link https://www.rfc-editor.org/rfc/rfc7636.html| RFC 7636}
|
|
70
|
+
* @param codeVerifier The code verifier string.
|
|
71
|
+
* @returns The code challenge string.
|
|
72
|
+
*/
|
|
44
73
|
generateCodeChallenge: (codeVerifier: string) => Promise<string>;
|
|
45
74
|
};
|
package/lib/errors.cjs
CHANGED
|
@@ -5,10 +5,12 @@ const logtoClientErrorCodes = Object.freeze({
|
|
|
5
5
|
'sign_in_session.not_found': 'Sign-in session not found.',
|
|
6
6
|
not_authenticated: 'Not authenticated.',
|
|
7
7
|
fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
|
|
8
|
+
user_cancelled: 'The user cancelled the action.',
|
|
8
9
|
});
|
|
9
10
|
class LogtoClientError extends Error {
|
|
10
11
|
constructor(code, data) {
|
|
11
12
|
super(logtoClientErrorCodes[code]);
|
|
13
|
+
this.name = 'LogtoClientError';
|
|
12
14
|
this.code = code;
|
|
13
15
|
this.data = data;
|
|
14
16
|
}
|
package/lib/errors.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ declare const logtoClientErrorCodes: Readonly<{
|
|
|
3
3
|
'sign_in_session.not_found': "Sign-in session not found.";
|
|
4
4
|
not_authenticated: "Not authenticated.";
|
|
5
5
|
fetch_user_info_failed: "Unable to fetch user info. The access token may be invalid.";
|
|
6
|
+
user_cancelled: "The user cancelled the action.";
|
|
6
7
|
}>;
|
|
7
8
|
export type LogtoClientErrorCode = keyof typeof logtoClientErrorCodes;
|
|
8
9
|
export declare class LogtoClientError extends Error {
|
|
10
|
+
name: string;
|
|
9
11
|
code: LogtoClientErrorCode;
|
|
10
12
|
data: unknown;
|
|
11
13
|
constructor(code: LogtoClientErrorCode, data?: unknown);
|
package/lib/errors.js
CHANGED
|
@@ -3,10 +3,12 @@ const logtoClientErrorCodes = Object.freeze({
|
|
|
3
3
|
'sign_in_session.not_found': 'Sign-in session not found.',
|
|
4
4
|
not_authenticated: 'Not authenticated.',
|
|
5
5
|
fetch_user_info_failed: 'Unable to fetch user info. The access token may be invalid.',
|
|
6
|
+
user_cancelled: 'The user cancelled the action.',
|
|
6
7
|
});
|
|
7
8
|
class LogtoClientError extends Error {
|
|
8
9
|
constructor(code, data) {
|
|
9
10
|
super(logtoClientErrorCodes[code]);
|
|
11
|
+
this.name = 'LogtoClientError';
|
|
10
12
|
this.code = code;
|
|
11
13
|
this.data = data;
|
|
12
14
|
}
|
package/lib/index.cjs
CHANGED
|
@@ -14,6 +14,13 @@ var once = require('./utils/once.cjs');
|
|
|
14
14
|
var types = require('./adapter/types.cjs');
|
|
15
15
|
var requester = require('./utils/requester.cjs');
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The Logto base client class that provides the essential methods for
|
|
19
|
+
* interacting with the Logto server.
|
|
20
|
+
*
|
|
21
|
+
* It also provides an adapter object that allows the customizations of the
|
|
22
|
+
* client behavior for different environments.
|
|
23
|
+
*/
|
|
17
24
|
class LogtoClient {
|
|
18
25
|
constructor(logtoConfig, adapter) {
|
|
19
26
|
this.getOidcConfig = memoize.memoize(this.#getOidcConfig);
|
|
@@ -27,15 +34,37 @@ class LogtoClient {
|
|
|
27
34
|
this.adapter = new index$1.ClientAdapterInstance(adapter);
|
|
28
35
|
void this.loadAccessTokenMap();
|
|
29
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Check if the user is authenticated by checking if the ID token exists.
|
|
39
|
+
*/
|
|
30
40
|
async isAuthenticated() {
|
|
31
41
|
return Boolean(await this.getIdToken());
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Get the Refresh Token from the storage.
|
|
45
|
+
*/
|
|
33
46
|
async getRefreshToken() {
|
|
34
47
|
return this.adapter.storage.getItem('refreshToken');
|
|
35
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the ID Token from the storage. If you want to get the ID Token claims,
|
|
51
|
+
* use {@link getIdTokenClaims} instead.
|
|
52
|
+
*/
|
|
36
53
|
async getIdToken() {
|
|
37
54
|
return this.adapter.storage.getItem('idToken');
|
|
38
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the Access Token from the storage. If the Access Token has expired, it
|
|
58
|
+
* will try to fetch a new one using the Refresh Token.
|
|
59
|
+
*
|
|
60
|
+
* If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
|
|
61
|
+
*
|
|
62
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
63
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
64
|
+
* resource, as specified in the Logto Console.
|
|
65
|
+
* @returns The Access Token string.
|
|
66
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
67
|
+
*/
|
|
39
68
|
async getAccessToken(resource) {
|
|
40
69
|
if (!(await this.getIdToken())) {
|
|
41
70
|
throw new errors.LogtoClientError('not_authenticated');
|
|
@@ -54,6 +83,9 @@ class LogtoClient {
|
|
|
54
83
|
*/
|
|
55
84
|
return this.getAccessTokenByRefreshToken(resource);
|
|
56
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the ID Token claims.
|
|
88
|
+
*/
|
|
57
89
|
async getIdTokenClaims() {
|
|
58
90
|
const idToken = await this.getIdToken();
|
|
59
91
|
if (!idToken) {
|
|
@@ -61,10 +93,27 @@ class LogtoClient {
|
|
|
61
93
|
}
|
|
62
94
|
return js.decodeIdToken(idToken);
|
|
63
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the Access Token claims for the specified resource.
|
|
98
|
+
*
|
|
99
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
100
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
101
|
+
* resource, as specified in the Logto Console.
|
|
102
|
+
*/
|
|
64
103
|
async getAccessTokenClaims(resource) {
|
|
65
104
|
const accessToken = await this.getAccessToken(resource);
|
|
66
105
|
return js.decodeAccessToken(accessToken);
|
|
67
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Get the user information from the Userinfo Endpoint.
|
|
109
|
+
*
|
|
110
|
+
* Note the Userinfo Endpoint will return more claims than the ID Token. See
|
|
111
|
+
* {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
|
|
112
|
+
* for more information.
|
|
113
|
+
*
|
|
114
|
+
* @returns The user information.
|
|
115
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
116
|
+
*/
|
|
68
117
|
async fetchUserInfo() {
|
|
69
118
|
const { userinfoEndpoint } = await this.getOidcConfig();
|
|
70
119
|
const accessToken = await this.getAccessToken();
|
|
@@ -73,6 +122,22 @@ class LogtoClient {
|
|
|
73
122
|
}
|
|
74
123
|
return js.fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
|
|
75
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Start the sign-in flow with the specified redirect URI. The URI must be
|
|
127
|
+
* registered in the Logto Console.
|
|
128
|
+
*
|
|
129
|
+
* The user will be redirected to that URI after the sign-in flow is completed,
|
|
130
|
+
* and the client will be able to get the authorization code from the URI.
|
|
131
|
+
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
132
|
+
* after the user is redirected in the callback URI.
|
|
133
|
+
*
|
|
134
|
+
* @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
|
|
135
|
+
* @param interactionMode The interaction mode to be used for the authorization request. Note it's not
|
|
136
|
+
* a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
|
|
137
|
+
*
|
|
138
|
+
* @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
|
|
139
|
+
* @see {@link InteractionMode}
|
|
140
|
+
*/
|
|
76
141
|
async signIn(redirectUri, interactionMode) {
|
|
77
142
|
const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
|
|
78
143
|
const { authorizationEndpoint } = await this.getOidcConfig();
|
|
@@ -90,11 +155,21 @@ class LogtoClient {
|
|
|
90
155
|
prompt,
|
|
91
156
|
interactionMode,
|
|
92
157
|
});
|
|
93
|
-
await
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
158
|
+
await Promise.all([
|
|
159
|
+
this.setSignInSession({ redirectUri, codeVerifier, state }),
|
|
160
|
+
this.setRefreshToken(null),
|
|
161
|
+
this.setIdToken(null),
|
|
162
|
+
]);
|
|
163
|
+
await this.adapter.navigate(signInUri);
|
|
97
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Check if the user is redirected from the sign-in page by checking if the
|
|
167
|
+
* current URL matches the redirect URI in the sign-in session.
|
|
168
|
+
*
|
|
169
|
+
* If there's no sign-in session, it will return `false`.
|
|
170
|
+
*
|
|
171
|
+
* @param url The current URL.
|
|
172
|
+
*/
|
|
98
173
|
async isSignInRedirected(url) {
|
|
99
174
|
const signInSession = await this.getSignInSession();
|
|
100
175
|
if (!signInSession) {
|
|
@@ -104,6 +179,15 @@ class LogtoClient {
|
|
|
104
179
|
const { origin, pathname } = new URL(url);
|
|
105
180
|
return `${origin}${pathname}` === redirectUri;
|
|
106
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
184
|
+
* callback URI and exchanging it for the tokens.
|
|
185
|
+
*
|
|
186
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
187
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
188
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
189
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
190
|
+
*/
|
|
107
191
|
async handleSignInCallback(callbackUri) {
|
|
108
192
|
const { requester } = this.adapter;
|
|
109
193
|
const signInSession = await this.getSignInSession();
|
|
@@ -139,6 +223,16 @@ class LogtoClient {
|
|
|
139
223
|
await this.saveAccessTokenMap();
|
|
140
224
|
await this.setSignInSession(null);
|
|
141
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
228
|
+
* registered in the Logto Console.
|
|
229
|
+
*
|
|
230
|
+
* It will also revoke all the tokens and clean up the storage.
|
|
231
|
+
*
|
|
232
|
+
* The user will be redirected that URI after the sign-out flow is completed.
|
|
233
|
+
* If the `postLogoutRedirectUri` is not specified, the user will be redirected
|
|
234
|
+
* to a default page.
|
|
235
|
+
*/
|
|
142
236
|
async signOut(postLogoutRedirectUri) {
|
|
143
237
|
const { appId: clientId } = this.logtoConfig;
|
|
144
238
|
const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
|
|
@@ -157,10 +251,12 @@ class LogtoClient {
|
|
|
157
251
|
clientId,
|
|
158
252
|
});
|
|
159
253
|
this.accessTokenMap.clear();
|
|
160
|
-
await
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
254
|
+
await Promise.all([
|
|
255
|
+
this.setRefreshToken(null),
|
|
256
|
+
this.setIdToken(null),
|
|
257
|
+
this.adapter.storage.removeItem('accessToken'),
|
|
258
|
+
]);
|
|
259
|
+
await this.adapter.navigate(url);
|
|
164
260
|
}
|
|
165
261
|
async getSignInSession() {
|
|
166
262
|
const jsonItem = await this.adapter.storage.getItem('signInSession');
|
|
@@ -207,7 +303,9 @@ class LogtoClient {
|
|
|
207
303
|
expiresAt: requestedAt + expiresIn,
|
|
208
304
|
});
|
|
209
305
|
await this.saveAccessTokenMap();
|
|
210
|
-
|
|
306
|
+
if (refreshToken) {
|
|
307
|
+
await this.setRefreshToken(refreshToken);
|
|
308
|
+
}
|
|
211
309
|
if (idToken) {
|
|
212
310
|
await this.verifyIdToken(idToken);
|
|
213
311
|
await this.setIdToken(idToken);
|
package/lib/index.d.ts
CHANGED
|
@@ -4,12 +4,19 @@ import { type JWTVerifyGetKey } from 'jose';
|
|
|
4
4
|
import { ClientAdapterInstance, type ClientAdapter } from './adapter/index.js';
|
|
5
5
|
import type { AccessToken, LogtoConfig, LogtoSignInSessionItem } from './types/index.js';
|
|
6
6
|
export type { IdTokenClaims, LogtoErrorCode, UserInfoResponse, InteractionMode } from '@logto/js';
|
|
7
|
-
export { LogtoError, OidcError, Prompt,
|
|
7
|
+
export { LogtoError, LogtoRequestError, OidcError, Prompt, ReservedScope, UserScope, } from '@logto/js';
|
|
8
8
|
export * from './errors.js';
|
|
9
9
|
export type { Storage, StorageKey, ClientAdapter } from './adapter/index.js';
|
|
10
10
|
export { PersistKey, CacheKey } from './adapter/index.js';
|
|
11
11
|
export { createRequester } from './utils/index.js';
|
|
12
12
|
export * from './types/index.js';
|
|
13
|
+
/**
|
|
14
|
+
* The Logto base client class that provides the essential methods for
|
|
15
|
+
* interacting with the Logto server.
|
|
16
|
+
*
|
|
17
|
+
* It also provides an adapter object that allows the customizations of the
|
|
18
|
+
* client behavior for different environments.
|
|
19
|
+
*/
|
|
13
20
|
export default class LogtoClient {
|
|
14
21
|
#private;
|
|
15
22
|
protected readonly logtoConfig: LogtoConfig;
|
|
@@ -26,16 +33,101 @@ export default class LogtoClient {
|
|
|
26
33
|
protected readonly adapter: ClientAdapterInstance;
|
|
27
34
|
protected readonly accessTokenMap: Map<string, AccessToken>;
|
|
28
35
|
constructor(logtoConfig: LogtoConfig, adapter: ClientAdapter);
|
|
36
|
+
/**
|
|
37
|
+
* Check if the user is authenticated by checking if the ID token exists.
|
|
38
|
+
*/
|
|
29
39
|
isAuthenticated(): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Get the Refresh Token from the storage.
|
|
42
|
+
*/
|
|
30
43
|
getRefreshToken(): Promise<Nullable<string>>;
|
|
44
|
+
/**
|
|
45
|
+
* Get the ID Token from the storage. If you want to get the ID Token claims,
|
|
46
|
+
* use {@link getIdTokenClaims} instead.
|
|
47
|
+
*/
|
|
31
48
|
getIdToken(): Promise<Nullable<string>>;
|
|
49
|
+
/**
|
|
50
|
+
* Get the Access Token from the storage. If the Access Token has expired, it
|
|
51
|
+
* will try to fetch a new one using the Refresh Token.
|
|
52
|
+
*
|
|
53
|
+
* If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
|
|
54
|
+
*
|
|
55
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
56
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
57
|
+
* resource, as specified in the Logto Console.
|
|
58
|
+
* @returns The Access Token string.
|
|
59
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
60
|
+
*/
|
|
32
61
|
getAccessToken(resource?: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Get the ID Token claims.
|
|
64
|
+
*/
|
|
33
65
|
getIdTokenClaims(): Promise<IdTokenClaims>;
|
|
66
|
+
/**
|
|
67
|
+
* Get the Access Token claims for the specified resource.
|
|
68
|
+
*
|
|
69
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
70
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
71
|
+
* resource, as specified in the Logto Console.
|
|
72
|
+
*/
|
|
34
73
|
getAccessTokenClaims(resource?: string): Promise<AccessTokenClaims>;
|
|
74
|
+
/**
|
|
75
|
+
* Get the user information from the Userinfo Endpoint.
|
|
76
|
+
*
|
|
77
|
+
* Note the Userinfo Endpoint will return more claims than the ID Token. See
|
|
78
|
+
* {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
|
|
79
|
+
* for more information.
|
|
80
|
+
*
|
|
81
|
+
* @returns The user information.
|
|
82
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
83
|
+
*/
|
|
35
84
|
fetchUserInfo(): Promise<UserInfoResponse>;
|
|
85
|
+
/**
|
|
86
|
+
* Start the sign-in flow with the specified redirect URI. The URI must be
|
|
87
|
+
* registered in the Logto Console.
|
|
88
|
+
*
|
|
89
|
+
* The user will be redirected to that URI after the sign-in flow is completed,
|
|
90
|
+
* and the client will be able to get the authorization code from the URI.
|
|
91
|
+
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
92
|
+
* after the user is redirected in the callback URI.
|
|
93
|
+
*
|
|
94
|
+
* @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
|
|
95
|
+
* @param interactionMode The interaction mode to be used for the authorization request. Note it's not
|
|
96
|
+
* a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
|
|
97
|
+
*
|
|
98
|
+
* @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
|
|
99
|
+
* @see {@link InteractionMode}
|
|
100
|
+
*/
|
|
36
101
|
signIn(redirectUri: string, interactionMode?: InteractionMode): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Check if the user is redirected from the sign-in page by checking if the
|
|
104
|
+
* current URL matches the redirect URI in the sign-in session.
|
|
105
|
+
*
|
|
106
|
+
* If there's no sign-in session, it will return `false`.
|
|
107
|
+
*
|
|
108
|
+
* @param url The current URL.
|
|
109
|
+
*/
|
|
37
110
|
isSignInRedirected(url: string): Promise<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
113
|
+
* callback URI and exchanging it for the tokens.
|
|
114
|
+
*
|
|
115
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
116
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
117
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
118
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
119
|
+
*/
|
|
38
120
|
handleSignInCallback(callbackUri: string): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
123
|
+
* registered in the Logto Console.
|
|
124
|
+
*
|
|
125
|
+
* It will also revoke all the tokens and clean up the storage.
|
|
126
|
+
*
|
|
127
|
+
* The user will be redirected that URI after the sign-out flow is completed.
|
|
128
|
+
* If the `postLogoutRedirectUri` is not specified, the user will be redirected
|
|
129
|
+
* to a default page.
|
|
130
|
+
*/
|
|
39
131
|
signOut(postLogoutRedirectUri?: string): Promise<void>;
|
|
40
132
|
protected getSignInSession(): Promise<Nullable<LogtoSignInSessionItem>>;
|
|
41
133
|
protected setSignInSession(value: Nullable<LogtoSignInSessionItem>): Promise<void>;
|
package/lib/index.js
CHANGED
|
@@ -11,6 +11,13 @@ import { once } from './utils/once.js';
|
|
|
11
11
|
import { PersistKey, CacheKey } from './adapter/types.js';
|
|
12
12
|
export { createRequester } from './utils/requester.js';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The Logto base client class that provides the essential methods for
|
|
16
|
+
* interacting with the Logto server.
|
|
17
|
+
*
|
|
18
|
+
* It also provides an adapter object that allows the customizations of the
|
|
19
|
+
* client behavior for different environments.
|
|
20
|
+
*/
|
|
14
21
|
class LogtoClient {
|
|
15
22
|
constructor(logtoConfig, adapter) {
|
|
16
23
|
this.getOidcConfig = memoize(this.#getOidcConfig);
|
|
@@ -24,15 +31,37 @@ class LogtoClient {
|
|
|
24
31
|
this.adapter = new ClientAdapterInstance(adapter);
|
|
25
32
|
void this.loadAccessTokenMap();
|
|
26
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the user is authenticated by checking if the ID token exists.
|
|
36
|
+
*/
|
|
27
37
|
async isAuthenticated() {
|
|
28
38
|
return Boolean(await this.getIdToken());
|
|
29
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Get the Refresh Token from the storage.
|
|
42
|
+
*/
|
|
30
43
|
async getRefreshToken() {
|
|
31
44
|
return this.adapter.storage.getItem('refreshToken');
|
|
32
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the ID Token from the storage. If you want to get the ID Token claims,
|
|
48
|
+
* use {@link getIdTokenClaims} instead.
|
|
49
|
+
*/
|
|
33
50
|
async getIdToken() {
|
|
34
51
|
return this.adapter.storage.getItem('idToken');
|
|
35
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the Access Token from the storage. If the Access Token has expired, it
|
|
55
|
+
* will try to fetch a new one using the Refresh Token.
|
|
56
|
+
*
|
|
57
|
+
* If you want to get the Access Token claims, use {@link getAccessTokenClaims} instead.
|
|
58
|
+
*
|
|
59
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
60
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
61
|
+
* resource, as specified in the Logto Console.
|
|
62
|
+
* @returns The Access Token string.
|
|
63
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
64
|
+
*/
|
|
36
65
|
async getAccessToken(resource) {
|
|
37
66
|
if (!(await this.getIdToken())) {
|
|
38
67
|
throw new LogtoClientError('not_authenticated');
|
|
@@ -51,6 +80,9 @@ class LogtoClient {
|
|
|
51
80
|
*/
|
|
52
81
|
return this.getAccessTokenByRefreshToken(resource);
|
|
53
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Get the ID Token claims.
|
|
85
|
+
*/
|
|
54
86
|
async getIdTokenClaims() {
|
|
55
87
|
const idToken = await this.getIdToken();
|
|
56
88
|
if (!idToken) {
|
|
@@ -58,10 +90,27 @@ class LogtoClient {
|
|
|
58
90
|
}
|
|
59
91
|
return decodeIdToken(idToken);
|
|
60
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the Access Token claims for the specified resource.
|
|
95
|
+
*
|
|
96
|
+
* @param resource The resource that the Access Token is granted for. If not
|
|
97
|
+
* specified, the Access Token will be used for OpenID Connect or the default
|
|
98
|
+
* resource, as specified in the Logto Console.
|
|
99
|
+
*/
|
|
61
100
|
async getAccessTokenClaims(resource) {
|
|
62
101
|
const accessToken = await this.getAccessToken(resource);
|
|
63
102
|
return decodeAccessToken(accessToken);
|
|
64
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Get the user information from the Userinfo Endpoint.
|
|
106
|
+
*
|
|
107
|
+
* Note the Userinfo Endpoint will return more claims than the ID Token. See
|
|
108
|
+
* {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
|
|
109
|
+
* for more information.
|
|
110
|
+
*
|
|
111
|
+
* @returns The user information.
|
|
112
|
+
* @throws LogtoClientError if the user is not authenticated.
|
|
113
|
+
*/
|
|
65
114
|
async fetchUserInfo() {
|
|
66
115
|
const { userinfoEndpoint } = await this.getOidcConfig();
|
|
67
116
|
const accessToken = await this.getAccessToken();
|
|
@@ -70,6 +119,22 @@ class LogtoClient {
|
|
|
70
119
|
}
|
|
71
120
|
return fetchUserInfo(userinfoEndpoint, accessToken, this.adapter.requester);
|
|
72
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Start the sign-in flow with the specified redirect URI. The URI must be
|
|
124
|
+
* registered in the Logto Console.
|
|
125
|
+
*
|
|
126
|
+
* The user will be redirected to that URI after the sign-in flow is completed,
|
|
127
|
+
* and the client will be able to get the authorization code from the URI.
|
|
128
|
+
* To fetch the tokens from the authorization code, use {@link handleSignInCallback}
|
|
129
|
+
* after the user is redirected in the callback URI.
|
|
130
|
+
*
|
|
131
|
+
* @param redirectUri The redirect URI that the user will be redirected to after the sign-in flow is completed.
|
|
132
|
+
* @param interactionMode The interaction mode to be used for the authorization request. Note it's not
|
|
133
|
+
* a part of the OIDC standard, but a Logto-specific extension. Defaults to `signIn`.
|
|
134
|
+
*
|
|
135
|
+
* @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#sign-in | Sign in} for more information.
|
|
136
|
+
* @see {@link InteractionMode}
|
|
137
|
+
*/
|
|
73
138
|
async signIn(redirectUri, interactionMode) {
|
|
74
139
|
const { appId: clientId, prompt, resources, scopes } = this.logtoConfig;
|
|
75
140
|
const { authorizationEndpoint } = await this.getOidcConfig();
|
|
@@ -87,11 +152,21 @@ class LogtoClient {
|
|
|
87
152
|
prompt,
|
|
88
153
|
interactionMode,
|
|
89
154
|
});
|
|
90
|
-
await
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
155
|
+
await Promise.all([
|
|
156
|
+
this.setSignInSession({ redirectUri, codeVerifier, state }),
|
|
157
|
+
this.setRefreshToken(null),
|
|
158
|
+
this.setIdToken(null),
|
|
159
|
+
]);
|
|
160
|
+
await this.adapter.navigate(signInUri);
|
|
94
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if the user is redirected from the sign-in page by checking if the
|
|
164
|
+
* current URL matches the redirect URI in the sign-in session.
|
|
165
|
+
*
|
|
166
|
+
* If there's no sign-in session, it will return `false`.
|
|
167
|
+
*
|
|
168
|
+
* @param url The current URL.
|
|
169
|
+
*/
|
|
95
170
|
async isSignInRedirected(url) {
|
|
96
171
|
const signInSession = await this.getSignInSession();
|
|
97
172
|
if (!signInSession) {
|
|
@@ -101,6 +176,15 @@ class LogtoClient {
|
|
|
101
176
|
const { origin, pathname } = new URL(url);
|
|
102
177
|
return `${origin}${pathname}` === redirectUri;
|
|
103
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Handle the sign-in callback by parsing the authorization code from the
|
|
181
|
+
* callback URI and exchanging it for the tokens.
|
|
182
|
+
*
|
|
183
|
+
* @param callbackUri The callback URI, including the search params, that the user is redirected to after the sign-in flow is completed.
|
|
184
|
+
* The origin and pathname of this URI must match the origin and pathname of the redirect URI specified in {@link signIn}.
|
|
185
|
+
* In many cases you'll probably end up passing `window.location.href` as the argument to this function.
|
|
186
|
+
* @throws LogtoClientError if the sign-in session is not found.
|
|
187
|
+
*/
|
|
104
188
|
async handleSignInCallback(callbackUri) {
|
|
105
189
|
const { requester } = this.adapter;
|
|
106
190
|
const signInSession = await this.getSignInSession();
|
|
@@ -136,6 +220,16 @@ class LogtoClient {
|
|
|
136
220
|
await this.saveAccessTokenMap();
|
|
137
221
|
await this.setSignInSession(null);
|
|
138
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Start the sign-out flow with the specified redirect URI. The URI must be
|
|
225
|
+
* registered in the Logto Console.
|
|
226
|
+
*
|
|
227
|
+
* It will also revoke all the tokens and clean up the storage.
|
|
228
|
+
*
|
|
229
|
+
* The user will be redirected that URI after the sign-out flow is completed.
|
|
230
|
+
* If the `postLogoutRedirectUri` is not specified, the user will be redirected
|
|
231
|
+
* to a default page.
|
|
232
|
+
*/
|
|
139
233
|
async signOut(postLogoutRedirectUri) {
|
|
140
234
|
const { appId: clientId } = this.logtoConfig;
|
|
141
235
|
const { endSessionEndpoint, revocationEndpoint } = await this.getOidcConfig();
|
|
@@ -154,10 +248,12 @@ class LogtoClient {
|
|
|
154
248
|
clientId,
|
|
155
249
|
});
|
|
156
250
|
this.accessTokenMap.clear();
|
|
157
|
-
await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
251
|
+
await Promise.all([
|
|
252
|
+
this.setRefreshToken(null),
|
|
253
|
+
this.setIdToken(null),
|
|
254
|
+
this.adapter.storage.removeItem('accessToken'),
|
|
255
|
+
]);
|
|
256
|
+
await this.adapter.navigate(url);
|
|
161
257
|
}
|
|
162
258
|
async getSignInSession() {
|
|
163
259
|
const jsonItem = await this.adapter.storage.getItem('signInSession');
|
|
@@ -204,7 +300,9 @@ class LogtoClient {
|
|
|
204
300
|
expiresAt: requestedAt + expiresIn,
|
|
205
301
|
});
|
|
206
302
|
await this.saveAccessTokenMap();
|
|
207
|
-
|
|
303
|
+
if (refreshToken) {
|
|
304
|
+
await this.setRefreshToken(refreshToken);
|
|
305
|
+
}
|
|
208
306
|
if (idToken) {
|
|
209
307
|
await this.verifyIdToken(idToken);
|
|
210
308
|
await this.setIdToken(idToken);
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,15 +1,51 @@
|
|
|
1
1
|
import type { Prompt } from '@logto/js';
|
|
2
|
+
/** The configuration object for the Logto client. */
|
|
2
3
|
export type LogtoConfig = {
|
|
4
|
+
/**
|
|
5
|
+
* The endpoint for the Logto server, you can get it from the integration guide
|
|
6
|
+
* or the team settings page of the Logto Console.
|
|
7
|
+
*
|
|
8
|
+
* @example https://foo.logto.app
|
|
9
|
+
*/
|
|
3
10
|
endpoint: string;
|
|
11
|
+
/**
|
|
12
|
+
* The client ID of your application, you can get it from the integration guide
|
|
13
|
+
* or the application details page of the Logto Console.
|
|
14
|
+
*/
|
|
4
15
|
appId: string;
|
|
16
|
+
/**
|
|
17
|
+
* The client secret of your application, you can get it from the application
|
|
18
|
+
* details page of the Logto Console.
|
|
19
|
+
*/
|
|
5
20
|
appSecret?: string;
|
|
21
|
+
/**
|
|
22
|
+
* The scopes (permissions) that your application needs to access.
|
|
23
|
+
* Scopes that will be added by default: `openid`, `offline_access` and `profile`.
|
|
24
|
+
*
|
|
25
|
+
* If resources are specified, scopes will be applied to every resource.
|
|
26
|
+
*
|
|
27
|
+
* @see {@link https://docs.logto.io/docs/recipes/integrate-logto/vanilla-js/#fetch-user-information | Fetch user information}
|
|
28
|
+
* for more information of available scopes for user information.
|
|
29
|
+
*/
|
|
6
30
|
scopes?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* The API resources that your application needs to access. You can specify
|
|
33
|
+
* multiple resources by providing an array of strings.
|
|
34
|
+
*
|
|
35
|
+
* @see {@link https://docs.logto.io/docs/recipes/rbac/ | RBAC} to learn more about how to use role-based access control (RBAC) to protect API resources.
|
|
36
|
+
*/
|
|
7
37
|
resources?: string[];
|
|
38
|
+
/**
|
|
39
|
+
* The prompt parameter to be used for the authorization request.
|
|
40
|
+
*/
|
|
8
41
|
prompt?: Prompt;
|
|
9
42
|
};
|
|
10
43
|
export type AccessToken = {
|
|
44
|
+
/** The access token string. */
|
|
11
45
|
token: string;
|
|
46
|
+
/** The scopes that the access token is granted for. */
|
|
12
47
|
scope: string;
|
|
48
|
+
/** The timestamp of the access token expiration. */
|
|
13
49
|
expiresAt: number;
|
|
14
50
|
};
|
|
15
51
|
export declare const isLogtoSignInSessionItem: (data: unknown) => data is LogtoSignInSessionItem;
|
package/lib/utils/requester.cjs
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
var js = require('@logto/js');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A factory function that creates a requester by accepting a `fetch`-like function.
|
|
7
|
+
*
|
|
8
|
+
* @param fetchFunction A `fetch`-like function.
|
|
9
|
+
* @returns A requester function.
|
|
10
|
+
* @see {@link Requester}
|
|
11
|
+
*/
|
|
5
12
|
const createRequester = (fetchFunction) => {
|
|
6
13
|
return async (...args) => {
|
|
7
14
|
const response = await fetchFunction(...args);
|
package/lib/utils/requester.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { Requester } from '@logto/js';
|
|
2
|
+
/**
|
|
3
|
+
* A factory function that creates a requester by accepting a `fetch`-like function.
|
|
4
|
+
*
|
|
5
|
+
* @param fetchFunction A `fetch`-like function.
|
|
6
|
+
* @returns A requester function.
|
|
7
|
+
* @see {@link Requester}
|
|
8
|
+
*/
|
|
2
9
|
export declare const createRequester: (fetchFunction: typeof fetch) => Requester;
|
package/lib/utils/requester.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { isLogtoRequestError, LogtoError, LogtoRequestError } from '@logto/js';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A factory function that creates a requester by accepting a `fetch`-like function.
|
|
5
|
+
*
|
|
6
|
+
* @param fetchFunction A `fetch`-like function.
|
|
7
|
+
* @returns A requester function.
|
|
8
|
+
* @see {@link Requester}
|
|
9
|
+
*/
|
|
3
10
|
const createRequester = (fetchFunction) => {
|
|
4
11
|
return async (...args) => {
|
|
5
12
|
const response = await fetchFunction(...args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logto/client",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./lib/index.cjs",
|
|
6
6
|
"module": "./lib/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"directory": "packages/client"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@logto/js": "^2.1.
|
|
24
|
+
"@logto/js": "^2.1.3",
|
|
25
25
|
"@silverhand/essentials": "^2.6.2",
|
|
26
26
|
"camelcase-keys": "^7.0.1",
|
|
27
27
|
"jose": "^4.13.2"
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"eslint": "^8.44.0",
|
|
37
37
|
"jest": "^29.5.0",
|
|
38
38
|
"jest-matcher-specific-error": "^1.0.0",
|
|
39
|
-
"lint-staged": "^
|
|
39
|
+
"lint-staged": "^14.0.0",
|
|
40
40
|
"nock": "^13.3.0",
|
|
41
41
|
"prettier": "^3.0.0",
|
|
42
42
|
"text-encoder": "^0.0.4",
|
|
43
|
-
"type-fest": "^
|
|
43
|
+
"type-fest": "^4.0.0",
|
|
44
44
|
"typescript": "^5.0.0"
|
|
45
45
|
},
|
|
46
46
|
"eslintConfig": {
|