@elogroup-sereduc/ser-front-core-client 1.1.1 → 2.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/OAUTH_MIGRATION.md +86 -0
- package/README.md +143 -388
- package/dist/{axios.d.ts → api-client.d.ts} +3 -3
- package/dist/api-client.d.ts.map +1 -0
- package/dist/{axios.js → api-client.js} +11 -11
- package/dist/api-client.js.map +1 -0
- package/dist/auth/bootstrap.d.ts +5 -0
- package/dist/auth/bootstrap.d.ts.map +1 -0
- package/dist/auth/bootstrap.js +28 -0
- package/dist/auth/bootstrap.js.map +1 -0
- package/dist/auth/http-client.d.ts +3 -0
- package/dist/auth/http-client.d.ts.map +1 -0
- package/dist/auth/http-client.js +31 -0
- package/dist/auth/http-client.js.map +1 -0
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +22 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/route-guards.d.ts +14 -0
- package/dist/auth/route-guards.d.ts.map +1 -0
- package/dist/auth/route-guards.js +23 -0
- package/dist/auth/route-guards.js.map +1 -0
- package/dist/auth/ser-oauth-js.d.ts +54 -0
- package/dist/auth/ser-oauth-js.d.ts.map +1 -0
- package/dist/auth/ser-oauth-js.js +405 -0
- package/dist/auth/ser-oauth-js.js.map +1 -0
- package/dist/auth/store.d.ts +14 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +20 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/index.d.ts +3 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/keycloak.d.ts +12 -2
- package/dist/keycloak.d.ts.map +1 -1
- package/dist/keycloak.js +168 -125
- package/dist/keycloak.js.map +1 -1
- package/dist/utils/auth.d.ts +8 -0
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +13 -2
- package/dist/utils/auth.js.map +1 -1
- package/package.json +8 -6
- package/public/silent-callback.html +19 -0
- package/public/silent-check-sso.html +30 -0
- package/dist/axios.d.ts.map +0 -1
- package/dist/axios.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,474 +1,229 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Ser Auth Library
|
|
2
2
|
|
|
3
|
-
Keycloak-
|
|
3
|
+
Sistema de autenticação OAuth/OIDC compatível com Keycloak, desenvolvido como substituto para keycloak-js com suporte a Silent SSO, PKCE S256, e gerenciamento automático de tokens.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Características
|
|
6
6
|
|
|
7
|
-
- ✅
|
|
8
|
-
- ✅
|
|
9
|
-
- ✅
|
|
10
|
-
- ✅
|
|
11
|
-
- ✅
|
|
12
|
-
- ✅
|
|
13
|
-
- ✅
|
|
7
|
+
- ✅ **Silent SSO**: Autenticação invisível via iframe
|
|
8
|
+
- ✅ **PKCE S256**: Segurança para SPAs (Single Page Applications)
|
|
9
|
+
- ✅ **Token Refresh**: Renovação automática de tokens
|
|
10
|
+
- ✅ **API Client**: Cliente HTTP com interceptadores automáticos
|
|
11
|
+
- ✅ **Route Guards**: Proteção de rotas para frameworks como TanStack Router
|
|
12
|
+
- ✅ **Zustand Store**: Gerenciamento de estado em memória
|
|
13
|
+
- ✅ **IDP Hint**: Suporte a redirecionamento automático para provedores específicos
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Instalação
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm install @elogroup-sereduc/ser-front-core-client
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Setup Básico
|
|
22
22
|
|
|
23
|
-
###
|
|
23
|
+
### 1. Configuração Inicial
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
keycloak: {
|
|
31
|
-
url: "https://your-keycloak-server",
|
|
32
|
-
realm: "your-realm",
|
|
33
|
-
clientId: "your-client-id",
|
|
34
|
-
},
|
|
35
|
-
api: {
|
|
36
|
-
baseURL: "https://your-api-server",
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Use like a normal axios instance
|
|
41
|
-
const users = await apiClient.get("/users");
|
|
42
|
-
const newUser = await apiClient.post("/users", userData);
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### Simple Client Factory
|
|
26
|
+
import {
|
|
27
|
+
createKeycloakAuthFromUrl,
|
|
28
|
+
initAuth,
|
|
29
|
+
} from "@elogroup-sereduc/ser-front-core-client";
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
// Criar instância do SerOAuth
|
|
32
|
+
const kc = createKeycloakAuthFromUrl(
|
|
33
|
+
"https://seu-keycloak.com/realms/seu-realm/protocol/openid-connect/auth",
|
|
34
|
+
"seu-client-id",
|
|
35
|
+
"seu-idp-hint", // opcional
|
|
36
|
+
);
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
realm: "your-realm",
|
|
53
|
-
clientId: "your-client-id",
|
|
54
|
-
});
|
|
38
|
+
// Inicializar autenticação
|
|
39
|
+
const authenticated = await initAuth(kc, "/seu-base-path");
|
|
55
40
|
```
|
|
56
41
|
|
|
57
|
-
|
|
42
|
+
### 2. Arquivo Silent-Check-SSO
|
|
58
43
|
|
|
59
|
-
|
|
44
|
+
Copie o arquivo `public/silent-check-sso.html` para a pasta `public` da sua aplicação.
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
### 3. Context de Autenticação (React)
|
|
62
47
|
|
|
63
48
|
```typescript
|
|
64
|
-
|
|
65
|
-
keycloak: {
|
|
66
|
-
url: "https://your-keycloak-server",
|
|
67
|
-
realm: "your-realm",
|
|
68
|
-
clientId: "your-client-id",
|
|
69
|
-
basePath: "/portal-aluno", // Application base path
|
|
70
|
-
},
|
|
71
|
-
api: {
|
|
72
|
-
baseURL: "https://your-api-server",
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const apiClient = createApiClient(config);
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The `basePath` is used to construct correct redirect URIs for:
|
|
80
|
-
|
|
81
|
-
- Silent SSO check redirects
|
|
82
|
-
- Login/logout redirects
|
|
83
|
-
- Error handling redirects
|
|
49
|
+
import { useAuthStore } from '@elogroup-sereduc/ser-front-core-client'
|
|
84
50
|
|
|
85
|
-
|
|
51
|
+
export function AuthContext({ children }) {
|
|
52
|
+
const authState = useAuthStore()
|
|
86
53
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
checkLoginIframe?: boolean;
|
|
98
|
-
// ... other KeycloakInitOptions
|
|
99
|
-
};
|
|
54
|
+
return (
|
|
55
|
+
<AuthProvider value={{
|
|
56
|
+
authenticated: authState.authenticated,
|
|
57
|
+
user: extractUserFromToken(authState.idToken),
|
|
58
|
+
login: () => kc.login(),
|
|
59
|
+
logout: () => kc.logout(),
|
|
60
|
+
}}>
|
|
61
|
+
{children}
|
|
62
|
+
</AuthProvider>
|
|
63
|
+
)
|
|
100
64
|
}
|
|
101
65
|
```
|
|
102
66
|
|
|
103
|
-
###
|
|
67
|
+
### 4. Cliente API
|
|
104
68
|
|
|
105
69
|
```typescript
|
|
106
|
-
|
|
107
|
-
baseURL: string; // API base URL
|
|
108
|
-
headers?: Record<string, string>; // Default headers
|
|
109
|
-
withCredentials?: boolean; // Include cookies (default: true)
|
|
110
|
-
onTokenRefreshFailed?: (error: Error) => void; // Custom error handler
|
|
111
|
-
loginRedirectUrl?: string; // Custom login redirect URL
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Login Interfaces
|
|
70
|
+
import { createApiClient } from "@elogroup-sereduc/ser-front-core-client";
|
|
116
71
|
|
|
117
|
-
|
|
118
|
-
interface DirectLoginCredentials {
|
|
119
|
-
username: string; // Username for direct login
|
|
120
|
-
password: string; // Password for direct login
|
|
121
|
-
}
|
|
72
|
+
const api = createApiClient(kc, "https://sua-api.com");
|
|
122
73
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
prompt?: "none" | "login" | "consent" | "select_account"; // OIDC prompt parameter
|
|
128
|
-
}
|
|
74
|
+
// O cliente automaticamente:
|
|
75
|
+
// - Adiciona Bearer token nos headers
|
|
76
|
+
// - Renova token em caso de 401
|
|
77
|
+
// - Retenta requisição com novo token
|
|
129
78
|
```
|
|
130
79
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
### Login Types
|
|
134
|
-
|
|
135
|
-
This package supports three types of login:
|
|
136
|
-
|
|
137
|
-
1. **External Login (Default)**: Redirects to Keycloak login page
|
|
138
|
-
2. **Direct Login (Internal Users)**: Uses username/password without external redirect
|
|
139
|
-
3. **Silent Login**: Checks authentication status without user interaction
|
|
140
|
-
|
|
141
|
-
### External Login (Standard Flow)
|
|
80
|
+
### 5. Route Guards
|
|
142
81
|
|
|
143
82
|
```typescript
|
|
144
|
-
import {
|
|
145
|
-
|
|
146
|
-
const authService = await createAuthService({
|
|
147
|
-
url: "https://your-keycloak-server",
|
|
148
|
-
realm: "your-realm",
|
|
149
|
-
clientId: "your-client-id",
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Redirects user to Keycloak login page
|
|
153
|
-
await authService.login();
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Direct Login (Internal Users)
|
|
157
|
-
|
|
158
|
-
For internal users that don't need external redirect, you can use direct login with username/password:
|
|
83
|
+
import { createAuthGuard } from "@elogroup-sereduc/ser-front-core-client";
|
|
84
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
159
85
|
|
|
160
|
-
|
|
161
|
-
import {
|
|
162
|
-
createAuthService,
|
|
163
|
-
type DirectLoginCredentials,
|
|
164
|
-
DirectLoginError,
|
|
165
|
-
} from "@elogroup-sereduc/ser-front-core-client";
|
|
86
|
+
const requireAuth = createAuthGuard(kc);
|
|
166
87
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
await authService.login({
|
|
172
|
-
credentials: {
|
|
173
|
-
username: "internal.user@company.com",
|
|
174
|
-
password: "userPassword123",
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
console.log("User logged in successfully!");
|
|
178
|
-
} catch (error) {
|
|
179
|
-
if (error instanceof DirectLoginError) {
|
|
180
|
-
console.error("Direct login failed:", error.message);
|
|
181
|
-
// Handle specific direct login errors:
|
|
182
|
-
// - Invalid credentials
|
|
183
|
-
// - Account locked
|
|
184
|
-
// - Client not configured for direct access
|
|
185
|
-
}
|
|
186
|
-
}
|
|
88
|
+
export const Route = createFileRoute("/protected")({
|
|
89
|
+
beforeLoad: requireAuth,
|
|
90
|
+
component: ProtectedComponent,
|
|
91
|
+
});
|
|
187
92
|
```
|
|
188
93
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
1. In Keycloak Admin Console, go to your client settings
|
|
192
|
-
2. Enable "Direct Access Grants Enabled" in the Capability config
|
|
193
|
-
3. Ensure the client has the appropriate roles and scope mappings
|
|
194
|
-
4. For production, consider IP restrictions and additional security measures
|
|
94
|
+
## API Reference
|
|
195
95
|
|
|
196
|
-
###
|
|
197
|
-
|
|
198
|
-
Silent login allows checking authentication status without user interaction, perfect for checking if a user is already logged in:
|
|
96
|
+
### SerOAuth
|
|
199
97
|
|
|
200
98
|
```typescript
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
AuthenticationError,
|
|
204
|
-
} from "@elogroup-sereduc/ser-front-core-client";
|
|
99
|
+
class SerOAuth {
|
|
100
|
+
constructor(config: SerOAuthConfig);
|
|
205
101
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (isAuthenticated) {
|
|
212
|
-
console.log("User is already logged in");
|
|
213
|
-
// Continue with app
|
|
214
|
-
} else {
|
|
215
|
-
console.log("User needs to login");
|
|
216
|
-
// Show login form or redirect to login
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error("Silent login check failed:", error);
|
|
220
|
-
// Fallback to normal login flow
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
**📋 Keycloak Configuration Requirements for Silent Login:**
|
|
102
|
+
// Métodos principais
|
|
103
|
+
async init(options?: InitOptions): Promise<boolean>;
|
|
104
|
+
async login(options?: LoginOptions): Promise<void>;
|
|
105
|
+
async logout(options?: LogoutOptions): Promise<void>;
|
|
106
|
+
async updateToken(minValidity?: number): Promise<boolean>;
|
|
225
107
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
108
|
+
// Propriedades
|
|
109
|
+
get authenticated(): boolean;
|
|
110
|
+
get token(): string | null;
|
|
111
|
+
get refreshToken(): string | null;
|
|
112
|
+
get idToken(): string | null;
|
|
231
113
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
isInternalUser: boolean,
|
|
239
|
-
credentials?: DirectLoginCredentials,
|
|
240
|
-
) {
|
|
241
|
-
try {
|
|
242
|
-
if (isInternalUser && credentials) {
|
|
243
|
-
// Direct login for internal users
|
|
244
|
-
await authService.login({ credentials });
|
|
245
|
-
} else {
|
|
246
|
-
// External redirect for external users
|
|
247
|
-
await authService.login();
|
|
248
|
-
}
|
|
249
|
-
} catch (error) {
|
|
250
|
-
console.error("Login failed:", error);
|
|
251
|
-
}
|
|
114
|
+
// Callbacks
|
|
115
|
+
onAuthSuccess?: () => void;
|
|
116
|
+
onAuthError?: (err: unknown) => void;
|
|
117
|
+
onAuthLogout?: () => void;
|
|
118
|
+
onTokenExpired?: () => void;
|
|
119
|
+
onTokenRefreshed?: () => void;
|
|
252
120
|
}
|
|
253
121
|
```
|
|
254
122
|
|
|
255
|
-
###
|
|
123
|
+
### Configuração
|
|
256
124
|
|
|
257
125
|
```typescript
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const authService = await createAuthService({
|
|
265
|
-
url: "https://your-keycloak-server",
|
|
266
|
-
realm: "your-realm",
|
|
267
|
-
clientId: "your-client-id",
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
// Check authentication state
|
|
271
|
-
const authState = getAuthState(authService);
|
|
272
|
-
console.log("Is authenticated:", authState.isAuthenticated);
|
|
273
|
-
|
|
274
|
-
// Login (external redirect)
|
|
275
|
-
if (!authState.isAuthenticated) {
|
|
276
|
-
await authService.login();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Direct login for internal users (no external redirect)
|
|
280
|
-
if (!authState.isAuthenticated) {
|
|
281
|
-
await authService.login({
|
|
282
|
-
credentials: {
|
|
283
|
-
username: "internal.user@company.com",
|
|
284
|
-
password: "userPassword123",
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
}
|
|
126
|
+
type SerOAuthConfig = {
|
|
127
|
+
url: string; // https://seu-keycloak.com
|
|
128
|
+
realm: string; // nome-do-realm
|
|
129
|
+
clientId: string; // seu-client-id
|
|
130
|
+
idp?: string; // hint para IDP específico
|
|
131
|
+
};
|
|
288
132
|
|
|
289
|
-
|
|
290
|
-
|
|
133
|
+
type InitOptions = {
|
|
134
|
+
onLoad?: "check-sso" | "login-required";
|
|
135
|
+
silentCheckSsoRedirectUri?: string;
|
|
136
|
+
pkceMethod?: "S256";
|
|
137
|
+
};
|
|
291
138
|
```
|
|
292
139
|
|
|
293
|
-
###
|
|
140
|
+
### Store (Zustand)
|
|
294
141
|
|
|
295
142
|
```typescript
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
143
|
+
const authState = useAuthStore();
|
|
144
|
+
// {
|
|
145
|
+
// accessToken: string | null
|
|
146
|
+
// refreshToken: string | null
|
|
147
|
+
// idToken: string | null
|
|
148
|
+
// accessExpiresAt: number | null
|
|
149
|
+
// authenticated: boolean
|
|
150
|
+
// setTokens: (tokens: TokenSet) => void
|
|
151
|
+
// clear: () => void
|
|
152
|
+
// }
|
|
305
153
|
```
|
|
306
154
|
|
|
307
|
-
##
|
|
155
|
+
## Migração do keycloak-js
|
|
308
156
|
|
|
309
|
-
###
|
|
157
|
+
### Antes (keycloak-js)
|
|
310
158
|
|
|
311
159
|
```typescript
|
|
312
|
-
import
|
|
313
|
-
isTokenExpired,
|
|
314
|
-
parseTokenUserInfo,
|
|
315
|
-
hasRole,
|
|
316
|
-
getUserRoles,
|
|
317
|
-
} from "@elogroup-sereduc/ser-front-core-client";
|
|
318
|
-
|
|
319
|
-
// Check if token is expired
|
|
320
|
-
const expired = isTokenExpired(token, 30); // 30-second buffer
|
|
160
|
+
import Keycloak from "keycloak-js";
|
|
321
161
|
|
|
322
|
-
|
|
323
|
-
|
|
162
|
+
const keycloak = new Keycloak({
|
|
163
|
+
url: "https://keycloak.com",
|
|
164
|
+
realm: "myrealm",
|
|
165
|
+
clientId: "myclient",
|
|
166
|
+
});
|
|
324
167
|
|
|
325
|
-
|
|
326
|
-
const isAdmin = hasRole(token, "admin");
|
|
327
|
-
const userRoles = getUserRoles(token);
|
|
168
|
+
await keycloak.init({ onLoad: "check-sso" });
|
|
328
169
|
```
|
|
329
170
|
|
|
330
|
-
###
|
|
171
|
+
### Depois (ser-auth)
|
|
331
172
|
|
|
332
173
|
```typescript
|
|
333
174
|
import {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
redirectToLogin,
|
|
175
|
+
createSerOAuth,
|
|
176
|
+
initAuth,
|
|
337
177
|
} from "@elogroup-sereduc/ser-front-core-client";
|
|
338
178
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
// Result: 'https://domain.com/backoffice/'
|
|
345
|
-
|
|
346
|
-
// Use with auth functions
|
|
347
|
-
const keycloakService = getKeycloakService(apiClient);
|
|
348
|
-
|
|
349
|
-
// Logout with base path
|
|
350
|
-
await logout(keycloakService, undefined, "/portal-aluno");
|
|
179
|
+
const kc = createSerOAuth({
|
|
180
|
+
url: "https://keycloak.com",
|
|
181
|
+
realm: "myrealm",
|
|
182
|
+
clientId: "myclient",
|
|
183
|
+
});
|
|
351
184
|
|
|
352
|
-
|
|
353
|
-
redirectToLogin(undefined, "/portal-aluno");
|
|
185
|
+
await initAuth(kc);
|
|
354
186
|
```
|
|
355
187
|
|
|
356
|
-
##
|
|
357
|
-
|
|
358
|
-
### Improved Error Messages
|
|
188
|
+
## Exemplos Avançados
|
|
359
189
|
|
|
360
|
-
|
|
190
|
+
### Custom Error Handling
|
|
361
191
|
|
|
362
192
|
```typescript
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
193
|
+
kc.onAuthError = (error) => {
|
|
194
|
+
console.error("Auth error:", error);
|
|
195
|
+
// Redirecionar para página de erro
|
|
196
|
+
};
|
|
367
197
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
realm: "nonexistent-realm",
|
|
372
|
-
clientId: "wrong-client-id",
|
|
373
|
-
});
|
|
374
|
-
} catch (error) {
|
|
375
|
-
if (error instanceof AuthenticationError) {
|
|
376
|
-
console.error("Detailed error:", error.message);
|
|
377
|
-
// Possible messages:
|
|
378
|
-
// - "Keycloak URL is required"
|
|
379
|
-
// - "Invalid Keycloak URL: ..."
|
|
380
|
-
// - "Cannot connect to Keycloak server at ..."
|
|
381
|
-
// - "Keycloak realm 'nonexistent-realm' not found"
|
|
382
|
-
// - "CORS error connecting to Keycloak..."
|
|
383
|
-
}
|
|
384
|
-
}
|
|
198
|
+
kc.onTokenExpired = () => {
|
|
199
|
+
console.log("Token expirado, renovando...");
|
|
200
|
+
};
|
|
385
201
|
```
|
|
386
202
|
|
|
387
|
-
###
|
|
388
|
-
|
|
389
|
-
By default, when token refresh fails, the user is redirected to the login page.
|
|
390
|
-
|
|
391
|
-
### Custom Error Handling
|
|
203
|
+
### Múltiplas Instâncias
|
|
392
204
|
|
|
393
205
|
```typescript
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
api: {
|
|
399
|
-
baseURL: "https://api.example.com",
|
|
400
|
-
onTokenRefreshFailed: (error) => {
|
|
401
|
-
console.error("Auth failed:", error);
|
|
402
|
-
// Custom handling - show modal, redirect to specific page, etc.
|
|
403
|
-
showAuthErrorModal();
|
|
404
|
-
},
|
|
405
|
-
},
|
|
206
|
+
const adminKc = createSerOAuth({
|
|
207
|
+
url: "https://admin-sso.com",
|
|
208
|
+
realm: "admin",
|
|
209
|
+
clientId: "admin-client",
|
|
406
210
|
});
|
|
407
|
-
```
|
|
408
211
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
<!doctype html>
|
|
415
|
-
<html>
|
|
416
|
-
<body>
|
|
417
|
-
<script>
|
|
418
|
-
parent.postMessage(location.href, location.origin);
|
|
419
|
-
</script>
|
|
420
|
-
</body>
|
|
421
|
-
</html>
|
|
212
|
+
const userKc = createSerOAuth({
|
|
213
|
+
url: "https://user-sso.com",
|
|
214
|
+
realm: "users",
|
|
215
|
+
clientId: "user-client",
|
|
216
|
+
});
|
|
422
217
|
```
|
|
423
218
|
|
|
424
|
-
|
|
219
|
+
### Interceptação Customizada
|
|
425
220
|
|
|
426
221
|
```typescript
|
|
427
|
-
const
|
|
428
|
-
keycloak: {
|
|
429
|
-
url: "https://your-keycloak-server",
|
|
430
|
-
realm: "your-realm",
|
|
431
|
-
clientId: "your-client-id",
|
|
432
|
-
initOptions: {
|
|
433
|
-
onLoad: "check-sso",
|
|
434
|
-
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
// ...
|
|
438
|
-
};
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
## TypeScript Support
|
|
222
|
+
const api = createApiClient(kc);
|
|
442
223
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
AuthState,
|
|
449
|
-
KeycloakConfig,
|
|
450
|
-
LoginOptions,
|
|
451
|
-
DirectLoginCredentials,
|
|
452
|
-
AuthenticationError,
|
|
453
|
-
TokenRefreshError,
|
|
454
|
-
DirectLoginError,
|
|
455
|
-
} from "@elogroup-sereduc/ser-front-core-client";
|
|
224
|
+
api.interceptors.request.use((config) => {
|
|
225
|
+
// Adicionar headers customizados
|
|
226
|
+
config.headers["X-Custom-Header"] = "value";
|
|
227
|
+
return config;
|
|
228
|
+
});
|
|
456
229
|
```
|
|
457
|
-
|
|
458
|
-
## Differences from portal-aluno-api-client
|
|
459
|
-
|
|
460
|
-
- ✅ **Memory-only storage**: Tokens stored in memory (more secure)
|
|
461
|
-
- ✅ **Proactive refresh**: Uses `updateToken(30)` before requests instead of 401/403 retry
|
|
462
|
-
- ✅ **Keycloak integration**: OIDC-based authentication instead of session-based
|
|
463
|
-
- ✅ **Per-client config**: Keycloak config provided per API client
|
|
464
|
-
- ✅ **No retry logic**: Does not retry requests on auth failure
|
|
465
|
-
- ✅ **Direct login support**: Internal users can login with username/password without external redirect
|
|
466
|
-
- ✅ **Silent login support**: Check authentication status without user interaction using OIDC prompt=none
|
|
467
|
-
- ✅ **Base path support**: Applications running in subfolders (e.g., /portal-aluno, /backoffice) are fully supported
|
|
468
|
-
- ✅ **Mixed authentication**: Supports both internal (direct) and external (redirect) users in the same app
|
|
469
|
-
- ✅ **Enhanced error handling**: Detailed error messages for debugging connection and configuration issues
|
|
470
|
-
- ✅ **Automatic validation**: Configuration validation with specific error messages
|
|
471
|
-
|
|
472
|
-
## License
|
|
473
|
-
|
|
474
|
-
MIT
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AxiosInstance } from "axios";
|
|
2
2
|
import { KeycloakService } from "./keycloak.js";
|
|
3
|
-
import type { CoreClientConfig
|
|
3
|
+
import type { CoreClientConfig } from "./types/index.js";
|
|
4
4
|
export declare function createApiClient(config: CoreClientConfig): AxiosInstance;
|
|
5
|
-
export declare function createSimpleApiClient(
|
|
5
|
+
export declare function createSimpleApiClient(config: CoreClientConfig): AxiosInstance;
|
|
6
6
|
export declare function getKeycloakService(apiClient: AxiosInstance): KeycloakService | undefined;
|
|
7
|
-
//# sourceMappingURL=
|
|
7
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAc,EACZ,KAAK,aAAa,EAGnB,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,kBAAkB,CAAC;AAM1E,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CA8EvE;AAyBD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,aAAa,CAE7E;AAKD,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,aAAa,GACvB,eAAe,GAAG,SAAS,CAE7B"}
|
|
@@ -2,8 +2,14 @@ import axios from "axios";
|
|
|
2
2
|
import { KeycloakService } from "./keycloak.js";
|
|
3
3
|
import { AuthenticationError, TokenRefreshError } from "./types/index.js";
|
|
4
4
|
export function createApiClient(config) {
|
|
5
|
-
const keycloakService =
|
|
6
|
-
|
|
5
|
+
const keycloakService = KeycloakService.getInstance(config.keycloak);
|
|
6
|
+
let initPromise;
|
|
7
|
+
if (!keycloakService.isInitialized) {
|
|
8
|
+
initPromise = keycloakService.initialize();
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
initPromise = Promise.resolve(true);
|
|
12
|
+
}
|
|
7
13
|
const apiClient = axios.create({
|
|
8
14
|
baseURL: config.api.baseURL,
|
|
9
15
|
withCredentials: config.api.withCredentials ?? true,
|
|
@@ -57,16 +63,10 @@ async function handleAuthenticationFailure(error, config) {
|
|
|
57
63
|
window.location.href = redirectUrl;
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
|
-
export function createSimpleApiClient(
|
|
61
|
-
return createApiClient(
|
|
62
|
-
keycloak: keycloakConfig,
|
|
63
|
-
api: {
|
|
64
|
-
baseURL,
|
|
65
|
-
...options,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
66
|
+
export function createSimpleApiClient(config) {
|
|
67
|
+
return createApiClient(config);
|
|
68
68
|
}
|
|
69
69
|
export function getKeycloakService(apiClient) {
|
|
70
70
|
return apiClient.keycloak;
|
|
71
71
|
}
|
|
72
|
-
//# sourceMappingURL=
|
|
72
|
+
//# sourceMappingURL=api-client.js.map
|