@avantmedia/id-react 0.0.1
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/package.json +29 -0
- package/src/hooks.ts +27 -0
- package/src/index.ts +19 -0
- package/src/pkce.ts +78 -0
- package/src/provider.tsx +282 -0
- package/src/types.ts +59 -0
- package/tsconfig.json +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@avantmedia/id-react",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"dev": "tsc --watch"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^18.3.0",
|
|
26
|
+
"react": "^18.3.0",
|
|
27
|
+
"typescript": "^5.7.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useAuthContext } from "./provider";
|
|
2
|
+
import type { AuthState } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to access auth state and actions
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* function App() {
|
|
10
|
+
* const { user, isLoading, login, logout } = useAuth();
|
|
11
|
+
*
|
|
12
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
13
|
+
* if (!user) return <button onClick={login}>Login</button>;
|
|
14
|
+
*
|
|
15
|
+
* return (
|
|
16
|
+
* <div>
|
|
17
|
+
* Hello {user.email}!
|
|
18
|
+
* <button onClick={logout}>Logout</button>
|
|
19
|
+
* </div>
|
|
20
|
+
* );
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function useAuth(): AuthState {
|
|
25
|
+
const { config, ...authState } = useAuthContext();
|
|
26
|
+
return authState;
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { AvantIdProvider } from "./provider";
|
|
3
|
+
|
|
4
|
+
// Hooks
|
|
5
|
+
export { useAuth } from "./hooks";
|
|
6
|
+
|
|
7
|
+
// Types
|
|
8
|
+
export type {
|
|
9
|
+
AvantIdConfig,
|
|
10
|
+
User,
|
|
11
|
+
TokenResponse,
|
|
12
|
+
AuthState,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
// PKCE utilities (for advanced use cases)
|
|
16
|
+
export {
|
|
17
|
+
generateCodeVerifier,
|
|
18
|
+
generateCodeChallenge,
|
|
19
|
+
} from "./pkce";
|
package/src/pkce.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE (Proof Key for Code Exchange) utilities
|
|
3
|
+
* Used to secure the OAuth2 authorization code flow for SPAs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a cryptographically random code verifier
|
|
8
|
+
* Must be between 43 and 128 characters
|
|
9
|
+
*/
|
|
10
|
+
export function generateCodeVerifier(): string {
|
|
11
|
+
const array = new Uint8Array(32);
|
|
12
|
+
crypto.getRandomValues(array);
|
|
13
|
+
return base64UrlEncode(array);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate code challenge from verifier using S256 method
|
|
18
|
+
* SHA256 hash of verifier, base64url encoded
|
|
19
|
+
*/
|
|
20
|
+
export async function generateCodeChallenge(verifier: string): Promise<string> {
|
|
21
|
+
const encoder = new TextEncoder();
|
|
22
|
+
const data = encoder.encode(verifier);
|
|
23
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
24
|
+
return base64UrlEncode(new Uint8Array(hash));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Base64url encode (URL-safe base64 without padding)
|
|
29
|
+
*/
|
|
30
|
+
function base64UrlEncode(buffer: Uint8Array): string {
|
|
31
|
+
const base64 = btoa(String.fromCharCode(...buffer));
|
|
32
|
+
return base64
|
|
33
|
+
.replace(/\+/g, "-")
|
|
34
|
+
.replace(/\//g, "_")
|
|
35
|
+
.replace(/=+$/, "");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Storage keys for PKCE state
|
|
40
|
+
*/
|
|
41
|
+
const VERIFIER_KEY = "avant_id_pkce_verifier";
|
|
42
|
+
const STATE_KEY = "avant_id_oauth_state";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Store code verifier in sessionStorage for later retrieval
|
|
46
|
+
*/
|
|
47
|
+
export function storeCodeVerifier(verifier: string): void {
|
|
48
|
+
sessionStorage.setItem(VERIFIER_KEY, verifier);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Retrieve and clear stored code verifier
|
|
53
|
+
*/
|
|
54
|
+
export function retrieveCodeVerifier(): string | null {
|
|
55
|
+
const verifier = sessionStorage.getItem(VERIFIER_KEY);
|
|
56
|
+
sessionStorage.removeItem(VERIFIER_KEY);
|
|
57
|
+
return verifier;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate and store OAuth state parameter for CSRF protection
|
|
62
|
+
*/
|
|
63
|
+
export function generateState(): string {
|
|
64
|
+
const array = new Uint8Array(16);
|
|
65
|
+
crypto.getRandomValues(array);
|
|
66
|
+
const state = base64UrlEncode(array);
|
|
67
|
+
sessionStorage.setItem(STATE_KEY, state);
|
|
68
|
+
return state;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Verify state parameter matches stored value
|
|
73
|
+
*/
|
|
74
|
+
export function verifyState(state: string): boolean {
|
|
75
|
+
const storedState = sessionStorage.getItem(STATE_KEY);
|
|
76
|
+
sessionStorage.removeItem(STATE_KEY);
|
|
77
|
+
return storedState === state;
|
|
78
|
+
}
|
package/src/provider.tsx
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
|
|
2
|
+
import type { AvantIdConfig, User, TokenResponse, AuthContextState } from "./types";
|
|
3
|
+
import {
|
|
4
|
+
generateCodeVerifier,
|
|
5
|
+
generateCodeChallenge,
|
|
6
|
+
storeCodeVerifier,
|
|
7
|
+
retrieveCodeVerifier,
|
|
8
|
+
generateState,
|
|
9
|
+
verifyState,
|
|
10
|
+
} from "./pkce";
|
|
11
|
+
|
|
12
|
+
const AuthContext = createContext<AuthContextState | null>(null);
|
|
13
|
+
|
|
14
|
+
// Token storage keys
|
|
15
|
+
const ACCESS_TOKEN_KEY = "avant_id_access_token";
|
|
16
|
+
const REFRESH_TOKEN_KEY = "avant_id_refresh_token";
|
|
17
|
+
const TOKEN_EXPIRY_KEY = "avant_id_token_expiry";
|
|
18
|
+
|
|
19
|
+
interface AvantIdProviderProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
config: AvantIdConfig;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function AvantIdProvider({ children, config }: AvantIdProviderProps) {
|
|
25
|
+
const [user, setUser] = useState<User | null>(null);
|
|
26
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
27
|
+
const [accessToken, setAccessToken] = useState<string | null>(null);
|
|
28
|
+
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
|
29
|
+
const [tokenExpiry, setTokenExpiry] = useState<number | null>(null);
|
|
30
|
+
|
|
31
|
+
// Normalize domain to ensure it has protocol
|
|
32
|
+
const baseUrl = config.domain.startsWith("http")
|
|
33
|
+
? config.domain
|
|
34
|
+
: `https://${config.domain}`;
|
|
35
|
+
|
|
36
|
+
// Default redirect URI
|
|
37
|
+
const redirectUri = config.redirectUri || `${window.location.origin}/callback`;
|
|
38
|
+
|
|
39
|
+
// Store tokens
|
|
40
|
+
const storeTokens = useCallback((tokens: TokenResponse) => {
|
|
41
|
+
const expiry = Date.now() + tokens.expires_in * 1000;
|
|
42
|
+
|
|
43
|
+
setAccessToken(tokens.access_token);
|
|
44
|
+
setRefreshToken(tokens.refresh_token);
|
|
45
|
+
setTokenExpiry(expiry);
|
|
46
|
+
|
|
47
|
+
// Always store access token in memory, optionally persist refresh token
|
|
48
|
+
if (config.persistSession) {
|
|
49
|
+
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token);
|
|
50
|
+
localStorage.setItem(TOKEN_EXPIRY_KEY, expiry.toString());
|
|
51
|
+
}
|
|
52
|
+
}, [config.persistSession]);
|
|
53
|
+
|
|
54
|
+
// Clear tokens
|
|
55
|
+
const clearTokens = useCallback(() => {
|
|
56
|
+
setAccessToken(null);
|
|
57
|
+
setRefreshToken(null);
|
|
58
|
+
setTokenExpiry(null);
|
|
59
|
+
setUser(null);
|
|
60
|
+
|
|
61
|
+
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
62
|
+
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
63
|
+
localStorage.removeItem(TOKEN_EXPIRY_KEY);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
// Fetch user profile with access token
|
|
67
|
+
const fetchUser = useCallback(async (token: string): Promise<User | null> => {
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(`${baseUrl}/auth/me`, {
|
|
70
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return await response.json();
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}, [baseUrl]);
|
|
82
|
+
|
|
83
|
+
// Refresh access token using refresh token
|
|
84
|
+
const refreshAccessToken = useCallback(async (token: string): Promise<TokenResponse | null> => {
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(`${baseUrl}/auth/refresh`, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: { "Content-Type": "application/json" },
|
|
89
|
+
body: JSON.stringify({ refreshToken: token }),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return await response.json();
|
|
97
|
+
} catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}, [baseUrl]);
|
|
101
|
+
|
|
102
|
+
// Get access token, refreshing if needed
|
|
103
|
+
const getAccessToken = useCallback(async (): Promise<string | null> => {
|
|
104
|
+
// Check if current token is still valid (with 30s buffer)
|
|
105
|
+
if (accessToken && tokenExpiry && Date.now() < tokenExpiry - 30000) {
|
|
106
|
+
return accessToken;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Try to refresh
|
|
110
|
+
if (refreshToken) {
|
|
111
|
+
const tokens = await refreshAccessToken(refreshToken);
|
|
112
|
+
if (tokens) {
|
|
113
|
+
storeTokens(tokens);
|
|
114
|
+
return tokens.access_token;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// No valid token
|
|
119
|
+
clearTokens();
|
|
120
|
+
return null;
|
|
121
|
+
}, [accessToken, tokenExpiry, refreshToken, refreshAccessToken, storeTokens, clearTokens]);
|
|
122
|
+
|
|
123
|
+
// Exchange authorization code for tokens
|
|
124
|
+
const exchangeCode = useCallback(async (code: string, codeVerifier: string): Promise<boolean> => {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(`${baseUrl}/oauth/token`, {
|
|
127
|
+
method: "POST",
|
|
128
|
+
headers: { "Content-Type": "application/json" },
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
grant_type: "authorization_code",
|
|
131
|
+
code,
|
|
132
|
+
code_verifier: codeVerifier,
|
|
133
|
+
client_id: config.clientId,
|
|
134
|
+
redirect_uri: redirectUri,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
console.error("Token exchange failed:", await response.text());
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const tokens: TokenResponse = await response.json();
|
|
144
|
+
storeTokens(tokens);
|
|
145
|
+
|
|
146
|
+
// Fetch user profile
|
|
147
|
+
const userProfile = await fetchUser(tokens.access_token);
|
|
148
|
+
if (userProfile) {
|
|
149
|
+
setUser(userProfile);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return true;
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error("Token exchange error:", err);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}, [baseUrl, config.clientId, redirectUri, storeTokens, fetchUser]);
|
|
158
|
+
|
|
159
|
+
// Login - redirect to Avant ID
|
|
160
|
+
const login = useCallback(async () => {
|
|
161
|
+
const verifier = generateCodeVerifier();
|
|
162
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
163
|
+
const state = generateState();
|
|
164
|
+
|
|
165
|
+
// Store verifier for later
|
|
166
|
+
storeCodeVerifier(verifier);
|
|
167
|
+
|
|
168
|
+
// Build authorization URL
|
|
169
|
+
const params = new URLSearchParams({
|
|
170
|
+
client_id: config.clientId,
|
|
171
|
+
redirect_uri: redirectUri,
|
|
172
|
+
response_type: "code",
|
|
173
|
+
code_challenge: challenge,
|
|
174
|
+
code_challenge_method: "S256",
|
|
175
|
+
state,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
window.location.href = `${baseUrl}/oauth/authorize?${params.toString()}`;
|
|
179
|
+
}, [config.clientId, redirectUri, baseUrl]);
|
|
180
|
+
|
|
181
|
+
// Logout
|
|
182
|
+
const logout = useCallback(async () => {
|
|
183
|
+
// Optionally call logout endpoint to revoke refresh token
|
|
184
|
+
if (refreshToken) {
|
|
185
|
+
try {
|
|
186
|
+
await fetch(`${baseUrl}/auth/logout`, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
headers: { "Content-Type": "application/json" },
|
|
189
|
+
body: JSON.stringify({ refreshToken }),
|
|
190
|
+
});
|
|
191
|
+
} catch {
|
|
192
|
+
// Ignore errors, still clear local state
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
clearTokens();
|
|
197
|
+
}, [refreshToken, baseUrl, clearTokens]);
|
|
198
|
+
|
|
199
|
+
// Handle OAuth callback on mount
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const handleCallback = async () => {
|
|
202
|
+
const params = new URLSearchParams(window.location.search);
|
|
203
|
+
const code = params.get("code");
|
|
204
|
+
const state = params.get("state");
|
|
205
|
+
const error = params.get("error");
|
|
206
|
+
|
|
207
|
+
if (error) {
|
|
208
|
+
console.error("OAuth error:", error, params.get("error_description"));
|
|
209
|
+
setIsLoading(false);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (code && state) {
|
|
214
|
+
// Verify state
|
|
215
|
+
if (!verifyState(state)) {
|
|
216
|
+
console.error("State mismatch - possible CSRF attack");
|
|
217
|
+
setIsLoading(false);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Get stored code verifier
|
|
222
|
+
const codeVerifier = retrieveCodeVerifier();
|
|
223
|
+
if (!codeVerifier) {
|
|
224
|
+
console.error("No code verifier found");
|
|
225
|
+
setIsLoading(false);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Exchange code for tokens
|
|
230
|
+
const success = await exchangeCode(code, codeVerifier);
|
|
231
|
+
|
|
232
|
+
// Clean up URL
|
|
233
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
234
|
+
|
|
235
|
+
setIsLoading(false);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// No callback params - check for existing session
|
|
240
|
+
if (config.persistSession) {
|
|
241
|
+
const storedRefresh = localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
242
|
+
if (storedRefresh) {
|
|
243
|
+
setRefreshToken(storedRefresh);
|
|
244
|
+
const tokens = await refreshAccessToken(storedRefresh);
|
|
245
|
+
if (tokens) {
|
|
246
|
+
storeTokens(tokens);
|
|
247
|
+
const userProfile = await fetchUser(tokens.access_token);
|
|
248
|
+
if (userProfile) {
|
|
249
|
+
setUser(userProfile);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
clearTokens();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
setIsLoading(false);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
handleCallback();
|
|
261
|
+
}, [config.persistSession, exchangeCode, refreshAccessToken, storeTokens, fetchUser, clearTokens]);
|
|
262
|
+
|
|
263
|
+
const value: AuthContextState = {
|
|
264
|
+
user,
|
|
265
|
+
isLoading,
|
|
266
|
+
isAuthenticated: !!user,
|
|
267
|
+
login,
|
|
268
|
+
logout,
|
|
269
|
+
getAccessToken,
|
|
270
|
+
config,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function useAuthContext(): AuthContextState {
|
|
277
|
+
const context = useContext(AuthContext);
|
|
278
|
+
if (!context) {
|
|
279
|
+
throw new Error("useAuth must be used within an AvantIdProvider");
|
|
280
|
+
}
|
|
281
|
+
return context;
|
|
282
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the AvantIdProvider
|
|
3
|
+
*/
|
|
4
|
+
export interface AvantIdConfig {
|
|
5
|
+
/** The domain of the Avant ID server (e.g., "auth.example.com" or "http://localhost:16000") */
|
|
6
|
+
domain: string;
|
|
7
|
+
/** The client ID of your application */
|
|
8
|
+
clientId: string;
|
|
9
|
+
/** The redirect URI for OAuth callbacks (defaults to current origin + /callback) */
|
|
10
|
+
redirectUri?: string;
|
|
11
|
+
/** Whether to persist refresh token in localStorage (default: false) */
|
|
12
|
+
persistSession?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* User object returned by the auth context
|
|
17
|
+
*/
|
|
18
|
+
export interface User {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
emailVerified?: boolean;
|
|
23
|
+
permissions?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Token response from the OAuth token endpoint
|
|
28
|
+
*/
|
|
29
|
+
export interface TokenResponse {
|
|
30
|
+
access_token: string;
|
|
31
|
+
refresh_token: string;
|
|
32
|
+
token_type: string;
|
|
33
|
+
expires_in: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Auth state exposed by useAuth hook
|
|
38
|
+
*/
|
|
39
|
+
export interface AuthState {
|
|
40
|
+
/** Current user or null if not authenticated */
|
|
41
|
+
user: User | null;
|
|
42
|
+
/** Whether the SDK is initializing/checking auth state */
|
|
43
|
+
isLoading: boolean;
|
|
44
|
+
/** Whether the user is authenticated */
|
|
45
|
+
isAuthenticated: boolean;
|
|
46
|
+
/** Redirect to Avant ID login page */
|
|
47
|
+
login: () => Promise<void>;
|
|
48
|
+
/** Clear local auth state and optionally revoke tokens */
|
|
49
|
+
logout: () => Promise<void>;
|
|
50
|
+
/** Get the current access token (refreshes if expired) */
|
|
51
|
+
getAccessToken: () => Promise<string | null>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Internal auth context state
|
|
56
|
+
*/
|
|
57
|
+
export interface AuthContextState extends AuthState {
|
|
58
|
+
config: AvantIdConfig;
|
|
59
|
+
}
|