Skip to content

Commit f27345d

Browse files
authored
FWF-4759 [fixed] fixed token expire issue when an api access with old… (#599)
* FWF-4759 [fixed] fixed token expire issue when an api access with old token * FWF-4759 [added] modified the error
1 parent 3a7d191 commit f27345d

File tree

2 files changed

+255
-172
lines changed

2 files changed

+255
-172
lines changed
Lines changed: 185 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,130 +1,159 @@
11
import Keycloak, {
2-
KeycloakInitOptions,
3-
KeycloakTokenParsed,
4-
KeycloakConfig,
5-
} from "keycloak-js";
6-
import StorageService from "../storage/storageService";
7-
8-
class KeycloakService {
9-
/**
10-
* Used to create Keycloak object
11-
*/
12-
private _keycloakConfig: KeycloakConfig | undefined;
13-
private kc: Keycloak | undefined;
14-
private static instance: KeycloakService;
15-
private token: string | undefined;
16-
private _tokenParsed: KeycloakTokenParsed | undefined;
17-
private timerId: any = 0;
18-
private userData: any
19-
20-
private constructor(url: string, realm: string, clientId: string, tenantId?: string) {
21-
this._keycloakConfig = {
22-
url: url,
23-
realm: realm,
24-
clientId: tenantId ? `${tenantId}-${clientId}` : clientId,
25-
};
26-
this.kc = new Keycloak(this._keycloakConfig);
27-
}
28-
29-
/**
30-
* used to call the `init` method from keycloak
31-
*/
32-
private keycloakInitConfig: KeycloakInitOptions = {
33-
onLoad: "check-sso",
34-
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
35-
pkceMethod: "S256",
36-
checkLoginIframe: false,
2+
KeycloakInitOptions,
3+
KeycloakTokenParsed,
4+
KeycloakConfig,
5+
} from "keycloak-js";
6+
import StorageService from "../storage/storageService";
7+
8+
class KeycloakService {
9+
/**
10+
* Used to create Keycloak object
11+
*/
12+
private _keycloakConfig: KeycloakConfig | undefined;
13+
private kc: Keycloak | undefined;
14+
private static instance: KeycloakService;
15+
private token: string | undefined;
16+
private _tokenParsed: KeycloakTokenParsed | undefined;
17+
private timerId: ReturnType<typeof setTimeout> | null = null;
18+
private userData: any;
19+
20+
private constructor(
21+
url: string,
22+
realm: string,
23+
clientId: string,
24+
tenantId?: string
25+
) {
26+
this._keycloakConfig = {
27+
url: url,
28+
realm: realm,
29+
clientId: tenantId ? `${tenantId}-${clientId}` : clientId,
3730
};
38-
39-
private login(): void {
40-
this.kc?.login();
41-
}
42-
43-
private logout(): void {
44-
this.kc?.logout();
45-
StorageService.clear()
31+
this.kc = new Keycloak(this._keycloakConfig);
32+
}
33+
34+
/**
35+
* used to call the `init` method from keycloak
36+
*/
37+
private keycloakInitConfig: KeycloakInitOptions = {
38+
onLoad: "check-sso",
39+
silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
40+
pkceMethod: "S256",
41+
checkLoginIframe: false,
42+
};
43+
44+
private login(): void {
45+
this.kc?.login();
46+
}
47+
48+
private logout(): void {
49+
this.kc?.logout();
50+
StorageService.clear();
51+
}
52+
53+
/**
54+
*
55+
* @returns Keycloak token validity in milliseconds
56+
*/
57+
private getTokenExpireTime(): number {
58+
const exp = this._tokenParsed?.exp;
59+
const iat = this._tokenParsed?.iat;
60+
if (exp && iat) {
61+
const toeknExpiretime =
62+
new Date(exp * 1000).valueOf() - new Date(iat * 1000).valueOf();
63+
return toeknExpiretime;
64+
} else {
65+
return 60000;
4666
}
47-
48-
/**
49-
*
50-
* @returns Keycloak token validity in milliseconds
51-
*/
52-
private getTokenExpireTime(): number {
53-
const exp = this._tokenParsed?.exp;
54-
const iat = this._tokenParsed?.iat;
55-
if (exp && iat) {
56-
const toeknExpiretime =
57-
new Date(exp * 1000).valueOf() - new Date(iat * 1000).valueOf();
58-
return toeknExpiretime;
67+
}
68+
69+
public static async updateToken(): Promise<string | null> {
70+
const instance = this.instance;
71+
if (!instance?.kc) return null;
72+
73+
try {
74+
const refreshed = await instance.kc.updateToken(-1);
75+
76+
if (refreshed) {
77+
instance.token = instance.kc.token!;
78+
StorageService.save(StorageService.User.AUTH_TOKEN, instance.token);
79+
return instance.token;
5980
} else {
60-
return 60000;
81+
return null;
6182
}
83+
} catch (err) {
84+
throw "error updating token";
6285
}
63-
64-
/**
65-
* Refresh the keycloak token before expiring
66-
*/
67-
private refreshToken(): void {
68-
this.timerId = setInterval(() => {
69-
this.kc
70-
?.updateToken(-1)
71-
.then((refreshed) => {
72-
if (refreshed) {
73-
console.log("Token refreshed!");
74-
clearInterval(this.timerId);
75-
this.token = this.kc.token
76-
StorageService.save(StorageService.User.AUTH_TOKEN, this.token!);
77-
this.refreshToken();
86+
}
87+
88+
/**
89+
* Refresh the keycloak token before expiring
90+
*/
91+
private refreshToken(): void {
92+
if (!this.kc) return;
93+
94+
const refreshLoop = async () => {
95+
try {
96+
const refreshed = await KeycloakService.updateToken();
97+
98+
if (!refreshed) {
99+
console.log("Token is still valid.");
100+
}
101+
102+
this.token = this.kc.token;
103+
104+
// Schedule the next refresh
105+
this.timerId = setTimeout(refreshLoop, this.getTokenExpireTime());
106+
107+
} catch (err) {
108+
console.error("Keycloak token update failed!", err);
109+
clearTimeout(this.timerId);
110+
this.logout(); // ensure logout is defined on the instance
111+
}
112+
};
113+
114+
refreshLoop(); // start the refresh loop
115+
}
116+
/**
117+
*
118+
* @param url - Valid keycloak url
119+
* @param realm - Valid keycloak realm
120+
* @param clientId - Valid keycloak clientId
121+
* @param tenantId - Optional - Valid tenant Id for multitenant environments
122+
* @returns KeycloakService instance
123+
*/
124+
public static getInstance(
125+
url: string,
126+
realm: string,
127+
clientId: string,
128+
tenantId?: string
129+
): KeycloakService {
130+
return this.instance
131+
? this.instance
132+
: (this.instance = new KeycloakService(url, realm, clientId, tenantId));
133+
}
134+
135+
/**
136+
* Initialize the keycloak
137+
* make sure `silent-check-sso.html` is present in public folder
138+
* @param callback - Optional - callback function to excecute after succeessful authentication
139+
*/
140+
public initKeycloak(callback: (authenticated) => void = () => {}): void {
141+
this.kc
142+
?.init(this.keycloakInitConfig)
143+
.then((authenticated) => {
144+
if (authenticated) {
145+
console.log("Authenticated");
146+
const tokenParsed = this.kc.tokenParsed;
147+
if (tokenParsed) {
148+
const UserRoles =
149+
tokenParsed.roles || tokenParsed.role || tokenParsed.client_roles;
150+
if (!UserRoles) {
151+
callback(false);
78152
} else {
79-
console.log("Token is still valid!");
80-
}
81-
})
82-
.catch((err) => {
83-
console.error("Keycloak token update failed!", err);
84-
clearInterval(this.timerId);
85-
this.logout();
86-
});
87-
}, this.getTokenExpireTime());
88-
}
89-
90-
/**
91-
*
92-
* @param url - Valid keycloak url
93-
* @param realm - Valid keycloak realm
94-
* @param clientId - Valid keycloak clientId
95-
* @param tenantId - Optional - Valid tenant Id for multitenant environments
96-
* @returns KeycloakService instance
97-
*/
98-
public static getInstance(
99-
url: string,
100-
realm: string,
101-
clientId: string,
102-
tenantId?: string
103-
): KeycloakService {
104-
return this.instance
105-
? this.instance
106-
: (this.instance = new KeycloakService(url, realm, clientId, tenantId));
107-
}
108-
109-
/**
110-
* Initialize the keycloak
111-
* make sure `silent-check-sso.html` is present in public folder
112-
* @param callback - Optional - callback function to excecute after succeessful authentication
113-
*/
114-
public initKeycloak(callback: (authenticated) => void = () => {}): void {
115-
this.kc
116-
?.init(this.keycloakInitConfig)
117-
.then((authenticated) => {
118-
if (authenticated) {
119-
console.log("Authenticated");
120-
const tokenParsed = this.kc.tokenParsed;
121-
if(tokenParsed){
122-
const UserRoles = tokenParsed.roles || tokenParsed.role || tokenParsed.client_roles;
123-
if(!UserRoles){
124-
callback(false);
125-
}
126-
else{
127-
StorageService.save(StorageService.User.USER_ROLE, JSON.stringify(UserRoles));
153+
StorageService.save(
154+
StorageService.User.USER_ROLE,
155+
JSON.stringify(UserRoles)
156+
);
128157
this.token = this.kc.token;
129158
this._tokenParsed = this.kc.tokenParsed;
130159
StorageService.save(StorageService.User.AUTH_TOKEN, this.token!);
@@ -137,45 +166,44 @@ import Keycloak, {
137166
callback(true);
138167
});
139168
this.refreshToken();
140-
}
141-
}else{
142-
this.logout();
143169
}
144-
145170
} else {
146-
console.warn("not authenticated!");
147-
this.login();
171+
this.logout();
148172
}
149-
})
150-
.catch((err) =>
151-
console.error("Failed to initialize KeycloakService", err)
152-
);
153-
}
154-
/**
155-
* logs the user out and clear all user data from session.
156-
*/
157-
public userLogout(): void {
158-
StorageService.clear();
159-
this.logout();
160-
}
161-
/**
162-
*
163-
* @returns the user token
164-
*/
165-
public getToken(): string {
166-
return this.token!;
167-
}
168-
/**
169-
*
170-
* @returns the user details
171-
*/
172-
public getUserData():any {
173-
return this.userData;
174-
}
173+
} else {
174+
console.warn("not authenticated!");
175+
this.login();
176+
}
177+
})
178+
.catch((err) =>
179+
console.error("Failed to initialize KeycloakService", err)
180+
);
181+
}
182+
/**
183+
* logs the user out and clear all user data from session.
184+
*/
185+
public userLogout(): void {
186+
StorageService.clear();
187+
this.logout();
188+
}
189+
/**
190+
*
191+
* @returns the user token
192+
*/
193+
public getToken(): string {
194+
return this.token!;
195+
}
196+
/**
197+
*
198+
* @returns the user details
199+
*/
200+
public getUserData(): any {
201+
return this.userData;
202+
}
175203

176-
public isAuthenticated(): boolean {
177-
return !!this.token;
178-
}
204+
public isAuthenticated(): boolean {
205+
return !!this.token;
179206
}
180-
181-
export default KeycloakService;
207+
}
208+
209+
export default KeycloakService;

0 commit comments

Comments
 (0)