Skip to content

Commit

Permalink
Fix: Refresh Token in Custom Interceptor (#191)
Browse files Browse the repository at this point in the history
  • Loading branch information
GODrums authored Nov 27, 2024
1 parent 6712697 commit ff7bf9a
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 16 deletions.
23 changes: 14 additions & 9 deletions webapp/src/app/core/security/keycloak.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export interface UserProfile {
export class KeycloakService {
_keycloak: Keycloak | undefined;
profile: UserProfile | undefined;
tokenRefreshInterval = 60; // in seconds

get keycloak() {
if (!this._keycloak) {
Expand All @@ -42,29 +41,35 @@ export class KeycloakService {
if (!authenticated) {
return authenticated;
}
// Load user profile
this.profile = (await this.keycloak.loadUserInfo()) as unknown as UserProfile;
this.profile.token = this.keycloak.token || '';
this.profile.roles = this.keycloak.realmAccess?.roles || [];

// Check refresh token expiry
setInterval(() => {
this.updateToken();
}, this.tokenRefreshInterval * 1000);

return true;
}

private async updateToken() {
/**
* Update access token if it is about to expire or has expired
* This is independent from the silent check sso or refresh token validity.
* @returns
*/
async updateToken() {
if (!this.keycloak.isTokenExpired(60)) {
return false;
}
try {
// Try to refresh token if it's about to expire
const refreshed = await this.keycloak.updateToken(this.tokenRefreshInterval + 10);
// Try to refresh token
const refreshed = await this.keycloak.updateToken(60);
if (refreshed) {
this.profile!.token = this.keycloak.token || '';
}
return refreshed;
} catch (error) {
console.error('Failed to refresh token:', error);
// Redirect to login if refresh fails
await this.keycloak.login();
return false;
}
}

Expand Down
21 changes: 14 additions & 7 deletions webapp/src/app/core/security/security-interceptor.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { SecurityStore } from './security-store.service';
import { from, switchMap } from 'rxjs';

export const securityInterceptor: HttpInterceptorFn = (req, next) => {
const keycloakService = inject(SecurityStore);

const bearer = keycloakService.user()?.bearer;
// update access token to prevent 401s
return from(keycloakService.updateToken()).pipe(
switchMap(() => {
// add bearer token to request
const bearer = keycloakService.user()?.bearer;

if (!bearer) {
return next(req);
}
if (!bearer) {
return next(req);
}

return next(
req.clone({
headers: req.headers.set('Authorization', `Bearer ${bearer}`)
return next(
req.clone({
headers: req.headers.set('Authorization', `Bearer ${bearer}`)
})
);
})
);
};
10 changes: 10 additions & 0 deletions webapp/src/app/core/security/security-store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,14 @@ export class SecurityStore {
async signOut() {
await this.keycloakService.logout();
}

async updateToken() {
await this.keycloakService.updateToken();
// update bearer in user with new token
const user = this.user();
if (user && this.keycloakService.profile) {
user.bearer = this.keycloakService.profile.token;
this.user.set(user);
}
}
}

0 comments on commit ff7bf9a

Please sign in to comment.