@tekcify/auth-core-client 1.0.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 ADDED
@@ -0,0 +1,374 @@
1
+ # @tekcify/auth-core-client
2
+
3
+ Core OAuth 2.0 client utilities for Tekcify Auth. This package provides low-level OAuth helpers that can be used across different frameworks and environments (browser, Node.js, React, Vue, etc.).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @tekcify/auth-core-client
9
+ # or
10
+ pnpm add @tekcify/auth-core-client
11
+ # or
12
+ yarn add @tekcify/auth-core-client
13
+ ```
14
+
15
+ ## Overview
16
+
17
+ This package provides the foundational OAuth 2.0 functionality for Tekcify Auth, including:
18
+
19
+ - **PKCE (Proof Key for Code Exchange)** generation for secure OAuth flows
20
+ - **OAuth Client** class for managing authentication flows
21
+ - **Standalone functions** for framework-agnostic usage
22
+ - **Type definitions** for TypeScript support
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Generate PKCE Parameters
27
+
28
+ PKCE is required for secure OAuth flows, especially for public clients (SPAs, mobile apps).
29
+
30
+ ```typescript
31
+ import { generateCodeVerifier, generateCodeChallenge } from '@tekcify/auth-core-client';
32
+
33
+ // Generate a code verifier (43-128 characters)
34
+ const codeVerifier = generateCodeVerifier();
35
+
36
+ // Generate the code challenge (SHA256 hash)
37
+ const codeChallenge = await generateCodeChallenge(codeVerifier, 'S256');
38
+
39
+ // Store the verifier securely (you'll need it later)
40
+ localStorage.setItem('code_verifier', codeVerifier);
41
+ ```
42
+
43
+ ### 2. Build Authorization URL
44
+
45
+ ```typescript
46
+ import { buildAuthorizeUrl } from '@tekcify/auth-core-client';
47
+
48
+ const authUrl = buildAuthorizeUrl('https://auth.example.com', {
49
+ clientId: 'your-client-id',
50
+ redirectUri: 'https://yourapp.com/callback',
51
+ scopes: ['read:profile', 'write:profile'],
52
+ state: crypto.randomUUID(), // CSRF protection
53
+ codeChallenge: codeChallenge,
54
+ codeChallengeMethod: 'S256',
55
+ });
56
+
57
+ // Redirect user to authUrl
58
+ window.location.href = authUrl;
59
+ ```
60
+
61
+ ### 3. Exchange Authorization Code for Tokens
62
+
63
+ After the user is redirected back with an authorization code:
64
+
65
+ ```typescript
66
+ import { exchangeCode } from '@tekcify/auth-core-client';
67
+
68
+ const urlParams = new URLSearchParams(window.location.search);
69
+ const code = urlParams.get('code');
70
+ const state = urlParams.get('state');
71
+
72
+ // Verify state matches (CSRF protection)
73
+ if (state !== expectedState) {
74
+ throw new Error('Invalid state parameter');
75
+ }
76
+
77
+ // Retrieve the stored code verifier
78
+ const codeVerifier = localStorage.getItem('code_verifier');
79
+
80
+ const tokens = await exchangeCode('https://auth.example.com', {
81
+ code: code!,
82
+ clientId: 'your-client-id',
83
+ clientSecret: 'your-client-secret', // Only for confidential clients
84
+ redirectUri: 'https://yourapp.com/callback',
85
+ codeVerifier: codeVerifier!,
86
+ });
87
+
88
+ // tokens.accessToken - Use for API requests
89
+ // tokens.refreshToken - Store securely for token refresh
90
+ // tokens.expiresIn - Token expiration time in seconds
91
+ ```
92
+
93
+ ### 4. Refresh Access Token
94
+
95
+ Access tokens expire. Use the refresh token to get a new one:
96
+
97
+ ```typescript
98
+ import { refreshAccessToken } from '@tekcify/auth-core-client';
99
+
100
+ const newTokens = await refreshAccessToken('https://auth.example.com', {
101
+ refreshToken: storedRefreshToken,
102
+ clientId: 'your-client-id',
103
+ clientSecret: 'your-client-secret',
104
+ });
105
+
106
+ // Update stored tokens
107
+ localStorage.setItem('access_token', newTokens.accessToken);
108
+ localStorage.setItem('refresh_token', newTokens.refreshToken);
109
+ ```
110
+
111
+ ### 5. Get User Information
112
+
113
+ ```typescript
114
+ import { getUserInfo } from '@tekcify/auth-core-client';
115
+
116
+ const userInfo = await getUserInfo('https://auth.example.com', accessToken);
117
+
118
+ console.log(userInfo.email); // appdever01@gmail.com
119
+ console.log(userInfo.name); // John Doe
120
+ console.log(userInfo.email_verified); // true
121
+ ```
122
+
123
+ ## Using the OAuth Client Class
124
+
125
+ For a more object-oriented approach, use the `OAuthClient` class:
126
+
127
+ ```typescript
128
+ import { OAuthClient } from '@tekcify/auth-core-client';
129
+
130
+ const client = new OAuthClient({
131
+ authServerUrl: 'https://auth.example.com',
132
+ clientId: 'your-client-id',
133
+ clientSecret: 'your-client-secret',
134
+ redirectUri: 'https://yourapp.com/callback',
135
+ scopes: ['read:profile', 'write:profile'],
136
+ });
137
+
138
+ // Build authorization URL
139
+ const authUrl = await client.buildAuthorizeUrl({
140
+ state: 'random-state',
141
+ codeChallenge: codeChallenge,
142
+ codeChallengeMethod: 'S256',
143
+ });
144
+
145
+ // Exchange code
146
+ const tokens = await client.exchangeCode(code, codeVerifier);
147
+
148
+ // Refresh token
149
+ const newTokens = await client.refreshAccessToken(refreshToken);
150
+
151
+ // Get user info
152
+ const userInfo = await client.getUserInfo(accessToken);
153
+
154
+ // Revoke token (logout)
155
+ await client.revokeToken(accessToken);
156
+
157
+ // Introspect token (check if valid)
158
+ const introspection = await client.introspectToken(accessToken);
159
+ if (introspection.active) {
160
+ console.log('Token is valid');
161
+ }
162
+ ```
163
+
164
+ ## Complete OAuth Flow Example
165
+
166
+ Here's a complete example of implementing the OAuth flow:
167
+
168
+ ```typescript
169
+ import {
170
+ OAuthClient,
171
+ generateCodeVerifier,
172
+ generateCodeChallenge,
173
+ } from '@tekcify/auth-core-client';
174
+
175
+ // Initialize client
176
+ const client = new OAuthClient({
177
+ authServerUrl: process.env.AUTH_SERVER_URL!,
178
+ clientId: process.env.CLIENT_ID!,
179
+ clientSecret: process.env.CLIENT_SECRET!,
180
+ redirectUri: process.env.REDIRECT_URI!,
181
+ scopes: ['read:profile'],
182
+ });
183
+
184
+ // Step 1: Start login flow
185
+ async function login() {
186
+ const verifier = generateCodeVerifier();
187
+ const challenge = await generateCodeChallenge(verifier, 'S256');
188
+ const state = crypto.randomUUID();
189
+
190
+ // Store verifier and state securely
191
+ sessionStorage.setItem('oauth_verifier', verifier);
192
+ sessionStorage.setItem('oauth_state', state);
193
+
194
+ // Build and redirect to authorization URL
195
+ const authUrl = await client.buildAuthorizeUrl({
196
+ state,
197
+ codeChallenge: challenge,
198
+ codeChallengeMethod: 'S256',
199
+ });
200
+
201
+ window.location.href = authUrl;
202
+ }
203
+
204
+ // Step 2: Handle callback
205
+ async function handleCallback() {
206
+ const urlParams = new URLSearchParams(window.location.search);
207
+ const code = urlParams.get('code');
208
+ const state = urlParams.get('state');
209
+ const storedState = sessionStorage.getItem('oauth_state');
210
+ const verifier = sessionStorage.getItem('oauth_verifier');
211
+
212
+ // Verify state
213
+ if (state !== storedState) {
214
+ throw new Error('Invalid state parameter');
215
+ }
216
+
217
+ if (!code || !verifier) {
218
+ throw new Error('Missing authorization code or verifier');
219
+ }
220
+
221
+ // Exchange code for tokens
222
+ const tokens = await client.exchangeCode(code, verifier);
223
+
224
+ // Store tokens securely
225
+ localStorage.setItem('access_token', tokens.accessToken);
226
+ localStorage.setItem('refresh_token', tokens.refreshToken);
227
+ localStorage.setItem('token_expires_at', String(Date.now() + tokens.expiresIn * 1000));
228
+
229
+ // Clean up
230
+ sessionStorage.removeItem('oauth_verifier');
231
+ sessionStorage.removeItem('oauth_state');
232
+
233
+ return tokens;
234
+ }
235
+
236
+ // Step 3: Make authenticated requests
237
+ async function fetchProtectedData() {
238
+ let accessToken = localStorage.getItem('access_token');
239
+ const expiresAt = parseInt(localStorage.getItem('token_expires_at') || '0');
240
+
241
+ // Refresh if expired
242
+ if (Date.now() >= expiresAt) {
243
+ const refreshToken = localStorage.getItem('refresh_token');
244
+ if (!refreshToken) {
245
+ throw new Error('No refresh token available');
246
+ }
247
+
248
+ const newTokens = await client.refreshAccessToken(refreshToken);
249
+ accessToken = newTokens.accessToken;
250
+ localStorage.setItem('access_token', newTokens.accessToken);
251
+ localStorage.setItem('refresh_token', newTokens.refreshToken);
252
+ localStorage.setItem('token_expires_at', String(Date.now() + newTokens.expiresIn * 1000));
253
+ }
254
+
255
+ // Use access token
256
+ const response = await fetch('https://api.example.com/protected', {
257
+ headers: {
258
+ Authorization: `Bearer ${accessToken}`,
259
+ },
260
+ });
261
+
262
+ return response.json();
263
+ }
264
+
265
+ // Step 4: Logout
266
+ async function logout() {
267
+ const accessToken = localStorage.getItem('access_token');
268
+ if (accessToken) {
269
+ await client.revokeToken(accessToken);
270
+ }
271
+
272
+ localStorage.removeItem('access_token');
273
+ localStorage.removeItem('refresh_token');
274
+ localStorage.removeItem('token_expires_at');
275
+ }
276
+ ```
277
+
278
+ ## API Reference
279
+
280
+ ### Functions
281
+
282
+ #### `generateCodeVerifier(): string`
283
+
284
+ Generates a cryptographically random code verifier (43-128 characters) for PKCE.
285
+
286
+ #### `generateCodeChallenge(verifier: string, method?: 'plain' | 'S256'): Promise<string>`
287
+
288
+ Generates a code challenge from a verifier. Use `S256` (SHA256) for security.
289
+
290
+ #### `buildAuthorizeUrl(authServerUrl: string, config: AuthorizeConfig): string`
291
+
292
+ Builds the OAuth authorization URL.
293
+
294
+ #### `exchangeCode(authServerUrl: string, config: ExchangeCodeConfig): Promise<TokenResponse>`
295
+
296
+ Exchanges an authorization code for access and refresh tokens.
297
+
298
+ #### `refreshAccessToken(authServerUrl: string, config: RefreshTokenConfig): Promise<TokenResponse>`
299
+
300
+ Refreshes an expired access token using a refresh token.
301
+
302
+ #### `revokeToken(authServerUrl: string, config: RevokeTokenConfig): Promise<void>`
303
+
304
+ Revokes an access or refresh token.
305
+
306
+ #### `introspectToken(authServerUrl: string, config: IntrospectConfig): Promise<IntrospectResult>`
307
+
308
+ Checks if a token is valid and returns its metadata.
309
+
310
+ #### `getUserInfo(authServerUrl: string, accessToken: string): Promise<UserInfo>`
311
+
312
+ Retrieves user information using an access token.
313
+
314
+ ### Types
315
+
316
+ ```typescript
317
+ interface TokenResponse {
318
+ accessToken: string;
319
+ refreshToken: string;
320
+ tokenType: string; // Usually "Bearer"
321
+ expiresIn: number; // Seconds until expiration
322
+ scope: string; // Space-separated scopes
323
+ }
324
+
325
+ interface UserInfo {
326
+ sub: string; // User ID
327
+ email: string;
328
+ email_verified: boolean;
329
+ given_name: string | null;
330
+ family_name: string | null;
331
+ name: string | null;
332
+ picture: string | null;
333
+ updated_at: number; // Unix timestamp
334
+ }
335
+
336
+ interface IntrospectResult {
337
+ active: boolean;
338
+ clientId?: string;
339
+ username?: string;
340
+ scope?: string;
341
+ sub?: string;
342
+ exp?: number;
343
+ iat?: number;
344
+ tokenType?: string;
345
+ }
346
+ ```
347
+
348
+ ## Security Best Practices
349
+
350
+ 1. **Always use PKCE**: Required for public clients, recommended for all
351
+ 2. **Store tokens securely**: Use httpOnly cookies or secure storage
352
+ 3. **Validate state parameter**: Prevents CSRF attacks
353
+ 4. **Never expose client secrets**: Only use in server-side code
354
+ 5. **Handle token expiration**: Implement automatic refresh logic
355
+ 6. **Revoke tokens on logout**: Prevents unauthorized access
356
+
357
+ ## Browser Compatibility
358
+
359
+ This package uses modern Web APIs:
360
+ - `crypto.getRandomValues()` for random number generation
361
+ - `crypto.subtle.digest()` for SHA256 hashing
362
+ - `fetch()` for HTTP requests
363
+
364
+ Supported in all modern browsers (Chrome, Firefox, Safari, Edge).
365
+
366
+ ## Node.js Usage
367
+
368
+ For Node.js environments, ensure you have:
369
+ - Node.js 18+ (for native fetch support)
370
+ - Or use a fetch polyfill like `node-fetch`
371
+
372
+ ## License
373
+
374
+ MIT
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@tekcify/auth-core-client",
3
+ "version": "1.0.0",
4
+ "description": "Core OAuth 2.0 client utilities for Tekcify Auth",
5
+ "author": "Tekcify",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/tekcify/auth.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/tekcify/auth/issues"
14
+ },
15
+ "homepage": "https://github.com/tekcify/auth#readme",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "require": "./dist/index.js"
21
+ }
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "clean": "rm -rf dist",
26
+ "lint": "eslint \"src/**/*.ts\" --max-warnings=0",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest"
29
+ },
30
+ "keywords": [
31
+ "oauth",
32
+ "oauth2",
33
+ "pkce",
34
+ "auth"
35
+ ],
36
+ "license": "MIT",
37
+ "devDependencies": {
38
+ "typescript": "^5.7.3",
39
+ "vitest": "^4.0.15"
40
+ }
41
+ }
42
+
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateCodeVerifier, generateCodeChallenge } from '../pkce';
3
+
4
+ describe('PKCE', () => {
5
+ it('should generate a code verifier', () => {
6
+ const verifier = generateCodeVerifier();
7
+ expect(verifier).toBeDefined();
8
+ expect(verifier.length).toBeGreaterThanOrEqual(43);
9
+ expect(verifier.length).toBeLessThanOrEqual(128);
10
+ });
11
+
12
+ it('should generate different verifiers each time', () => {
13
+ const verifier1 = generateCodeVerifier();
14
+ const verifier2 = generateCodeVerifier();
15
+ expect(verifier1).not.toBe(verifier2);
16
+ });
17
+
18
+ it('should generate a code challenge from verifier (S256)', async () => {
19
+ const verifier = generateCodeVerifier();
20
+ const challenge = await generateCodeChallenge(verifier, 'S256');
21
+ expect(challenge).toBeDefined();
22
+ expect(challenge).not.toBe(verifier);
23
+ expect(challenge.length).toBeGreaterThan(0);
24
+ });
25
+
26
+ it('should generate a plain code challenge', async () => {
27
+ const verifier = generateCodeVerifier();
28
+ const challenge = await generateCodeChallenge(verifier, 'plain');
29
+ expect(challenge).toBe(verifier);
30
+ });
31
+ });
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './pkce';
3
+ export * from './oauth';
package/src/oauth.ts ADDED
@@ -0,0 +1,250 @@
1
+ import type {
2
+ TokenResponse,
3
+ UserInfo,
4
+ IntrospectResult,
5
+ OAuthConfig,
6
+ } from './types';
7
+ import { generateCodeVerifier, generateCodeChallenge } from './pkce';
8
+
9
+ export function buildAuthorizeUrl(
10
+ authServerUrl: string,
11
+ config: {
12
+ clientId: string;
13
+ redirectUri: string;
14
+ scopes: string[];
15
+ state?: string;
16
+ codeChallenge?: string;
17
+ codeChallengeMethod?: 'plain' | 'S256';
18
+ },
19
+ ): string {
20
+ const url = new URL(`${authServerUrl}/oauth/authorize`);
21
+ url.searchParams.set('clientId', config.clientId);
22
+ url.searchParams.set('redirectUri', config.redirectUri);
23
+ url.searchParams.set('scopes', config.scopes.join(' '));
24
+ if (config.state) {
25
+ url.searchParams.set('state', config.state);
26
+ }
27
+ if (config.codeChallenge) {
28
+ url.searchParams.set('codeChallenge', config.codeChallenge);
29
+ url.searchParams.set(
30
+ 'codeChallengeMethod',
31
+ config.codeChallengeMethod ?? 'S256',
32
+ );
33
+ }
34
+ return url.toString();
35
+ }
36
+
37
+ export async function exchangeCode(
38
+ authServerUrl: string,
39
+ config: {
40
+ code: string;
41
+ clientId: string;
42
+ clientSecret: string;
43
+ redirectUri: string;
44
+ codeVerifier?: string;
45
+ },
46
+ ): Promise<TokenResponse> {
47
+ const response = await fetch(`${authServerUrl}/oauth/token`, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'Content-Type': 'application/json',
51
+ },
52
+ body: JSON.stringify({
53
+ grant_type: 'authorization_code',
54
+ code: config.code,
55
+ clientId: config.clientId,
56
+ clientSecret: config.clientSecret,
57
+ redirectUri: config.redirectUri,
58
+ codeVerifier: config.codeVerifier,
59
+ }),
60
+ });
61
+
62
+ if (!response.ok) {
63
+ const error = (await response.json().catch(() => ({}))) as {
64
+ message?: string;
65
+ };
66
+ throw new Error(error.message ?? 'Failed to exchange authorization code');
67
+ }
68
+
69
+ return (await response.json()) as TokenResponse;
70
+ }
71
+
72
+ export async function refreshAccessToken(
73
+ authServerUrl: string,
74
+ config: {
75
+ refreshToken: string;
76
+ clientId: string;
77
+ clientSecret: string;
78
+ },
79
+ ): Promise<TokenResponse> {
80
+ const response = await fetch(`${authServerUrl}/oauth/token`, {
81
+ method: 'POST',
82
+ headers: {
83
+ 'Content-Type': 'application/json',
84
+ },
85
+ body: JSON.stringify({
86
+ grant_type: 'refresh_token',
87
+ refreshToken: config.refreshToken,
88
+ clientId: config.clientId,
89
+ clientSecret: config.clientSecret,
90
+ }),
91
+ });
92
+
93
+ if (!response.ok) {
94
+ const error = (await response.json().catch(() => ({}))) as {
95
+ message?: string;
96
+ };
97
+ throw new Error(error.message ?? 'Failed to refresh access token');
98
+ }
99
+
100
+ return (await response.json()) as TokenResponse;
101
+ }
102
+
103
+ export async function revokeToken(
104
+ authServerUrl: string,
105
+ config: {
106
+ token: string;
107
+ clientId: string;
108
+ clientSecret: string;
109
+ },
110
+ ): Promise<void> {
111
+ const response = await fetch(`${authServerUrl}/oauth/revoke`, {
112
+ method: 'POST',
113
+ headers: {
114
+ 'Content-Type': 'application/json',
115
+ },
116
+ body: JSON.stringify({
117
+ token: config.token,
118
+ clientId: config.clientId,
119
+ clientSecret: config.clientSecret,
120
+ }),
121
+ });
122
+
123
+ if (!response.ok) {
124
+ const error = (await response.json().catch(() => ({}))) as {
125
+ message?: string;
126
+ };
127
+ throw new Error(error.message ?? 'Failed to revoke token');
128
+ }
129
+ }
130
+
131
+ export async function introspectToken(
132
+ authServerUrl: string,
133
+ config: {
134
+ token: string;
135
+ clientId?: string;
136
+ clientSecret?: string;
137
+ },
138
+ ): Promise<IntrospectResult> {
139
+ const body: Record<string, string> = {
140
+ token: config.token,
141
+ };
142
+
143
+ if (config.clientId) {
144
+ body.clientId = config.clientId;
145
+ }
146
+ if (config.clientSecret) {
147
+ body.clientSecret = config.clientSecret;
148
+ }
149
+
150
+ const response = await fetch(`${authServerUrl}/oauth/token/introspect`, {
151
+ method: 'POST',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ },
155
+ body: JSON.stringify(body),
156
+ });
157
+
158
+ if (!response.ok) {
159
+ const error = (await response.json().catch(() => ({}))) as {
160
+ message?: string;
161
+ };
162
+ throw new Error(error.message ?? 'Failed to introspect token');
163
+ }
164
+
165
+ return (await response.json()) as IntrospectResult;
166
+ }
167
+
168
+ export async function getUserInfo(
169
+ authServerUrl: string,
170
+ accessToken: string,
171
+ ): Promise<UserInfo> {
172
+ const response = await fetch(`${authServerUrl}/oauth/userinfo`, {
173
+ method: 'GET',
174
+ headers: {
175
+ Authorization: `Bearer ${accessToken}`,
176
+ },
177
+ });
178
+
179
+ if (!response.ok) {
180
+ const error = (await response.json().catch(() => ({}))) as {
181
+ message?: string;
182
+ };
183
+ throw new Error(error.message ?? 'Failed to get user info');
184
+ }
185
+
186
+ return (await response.json()) as UserInfo;
187
+ }
188
+
189
+ export class OAuthClient {
190
+ constructor(private config: OAuthConfig) {}
191
+
192
+ async buildAuthorizeUrl(options?: {
193
+ state?: string;
194
+ codeChallenge?: string;
195
+ codeChallengeMethod?: 'plain' | 'S256';
196
+ }): Promise<string> {
197
+ return buildAuthorizeUrl(this.config.authServerUrl, {
198
+ clientId: this.config.clientId,
199
+ redirectUri: this.config.redirectUri,
200
+ scopes: this.config.scopes,
201
+ ...options,
202
+ });
203
+ }
204
+
205
+ async exchangeCode(
206
+ code: string,
207
+ codeVerifier?: string,
208
+ ): Promise<TokenResponse> {
209
+ return exchangeCode(this.config.authServerUrl, {
210
+ code,
211
+ clientId: this.config.clientId,
212
+ clientSecret: this.config.clientSecret,
213
+ redirectUri: this.config.redirectUri,
214
+ codeVerifier,
215
+ });
216
+ }
217
+
218
+ async refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
219
+ return refreshAccessToken(this.config.authServerUrl, {
220
+ refreshToken,
221
+ clientId: this.config.clientId,
222
+ clientSecret: this.config.clientSecret,
223
+ });
224
+ }
225
+
226
+ async revokeToken(token: string): Promise<void> {
227
+ return revokeToken(this.config.authServerUrl, {
228
+ token,
229
+ clientId: this.config.clientId,
230
+ clientSecret: this.config.clientSecret,
231
+ });
232
+ }
233
+
234
+ async introspectToken(
235
+ token: string,
236
+ options?: { clientId?: string; clientSecret?: string },
237
+ ): Promise<IntrospectResult> {
238
+ return introspectToken(this.config.authServerUrl, {
239
+ token,
240
+ clientId: options?.clientId ?? this.config.clientId,
241
+ clientSecret: options?.clientSecret ?? this.config.clientSecret,
242
+ });
243
+ }
244
+
245
+ async getUserInfo(accessToken: string): Promise<UserInfo> {
246
+ return getUserInfo(this.config.authServerUrl, accessToken);
247
+ }
248
+ }
249
+
250
+ export { generateCodeVerifier, generateCodeChallenge };