• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

damienbod / angular-auth-oidc-client / 16102496676

06 Jul 2025 07:35PM UTC coverage: 92.115% (-0.6%) from 92.71%
16102496676

Pull #2107

github

web-flow
Merge b6293b5a6 into 5304cc1cd
Pull Request #2107: Fix silent refresh iframe with multiple idps

722 of 863 branches covered (83.66%)

Branch coverage included in aggregate %.

28 of 39 new or added lines in 4 files covered. (71.79%)

1 existing line in 1 file now uncovered.

2631 of 2777 relevant lines covered (94.74%)

8.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.25
/projects/angular-auth-oidc-client/src/lib/callback/refresh-session.service.ts
1
import { inject, Injectable } from '@angular/core';
2
import {
3
  forkJoin,
4
  Observable,
5
  of,
6
  throwError,
7
  TimeoutError,
8
  timer,
9
} from 'rxjs';
10
import {
11
  filter,
12
  map,
13
  mergeMap,
14
  retryWhen,
15
  switchMap,
16
  take,
17
  tap,
18
  timeout,
19
} from 'rxjs/operators';
20
import { AuthStateService } from '../auth-state/auth-state.service';
21
import { AuthWellKnownService } from '../config/auth-well-known/auth-well-known.service';
22
import { OpenIdConfiguration } from '../config/openid-configuration';
23
import { CallbackContext } from '../flows/callback-context';
24
import { FlowsDataService } from '../flows/flows-data.service';
25
import { RefreshSessionIframeService } from '../iframe/refresh-session-iframe.service';
26
import { SilentRenewService } from '../iframe/silent-renew.service';
27
import { LoggerService } from '../logging/logger.service';
28
import { LoginResponse } from '../login/login-response';
29
import { StoragePersistenceService } from '../storage/storage-persistence.service';
30
import { UserService } from '../user-data/user.service';
31
import { FlowHelper } from '../utils/flowHelper/flow-helper.service';
32
import { RefreshSessionRefreshTokenService } from './refresh-session-refresh-token.service';
33

34
export const MAX_RETRY_ATTEMPTS = 3;
1✔
35

36
@Injectable({ providedIn: 'root' })
37
export class RefreshSessionService {
1✔
38
  private readonly flowHelper = inject(FlowHelper);
19✔
39
  private readonly flowsDataService = inject(FlowsDataService);
19✔
40
  private readonly loggerService = inject(LoggerService);
19✔
41
  private readonly silentRenewService = inject(SilentRenewService);
19✔
42
  private readonly authStateService = inject(AuthStateService);
19✔
43
  private readonly authWellKnownService = inject(AuthWellKnownService);
19✔
44
  private readonly refreshSessionIframeService = inject(
19✔
45
    RefreshSessionIframeService
46
  );
47
  private readonly storagePersistenceService = inject(
19✔
48
    StoragePersistenceService
49
  );
50
  private readonly refreshSessionRefreshTokenService = inject(
19✔
51
    RefreshSessionRefreshTokenService
52
  );
53
  private readonly userService = inject(UserService);
19✔
54

55
  userForceRefreshSession(
56
    config: OpenIdConfiguration | null,
57
    allConfigs: OpenIdConfiguration[],
58
    extraCustomParams?: { [key: string]: string | number | boolean }
59
  ): Observable<LoginResponse> {
60
    if (!config) {
5!
61
      return throwError(
×
62
        () =>
63
          new Error(
×
64
            'Please provide a configuration before setting up the module'
65
          )
66
      );
67
    }
68

69
    this.persistCustomParams(extraCustomParams, config);
5✔
70

71
    return this.forceRefreshSession(config, allConfigs, extraCustomParams).pipe(
5✔
72
      tap(() => this.flowsDataService.resetSilentRenewRunning(config))
4✔
73
    );
74
  }
75

76
  forceRefreshSession(
77
    config: OpenIdConfiguration,
78
    allConfigs: OpenIdConfiguration[],
79
    extraCustomParams?: { [key: string]: string | number | boolean }
80
  ): Observable<LoginResponse> {
81
    const { customParamsRefreshTokenRequest, configId } = config;
11✔
82
    const mergedParams = {
11✔
83
      ...customParamsRefreshTokenRequest,
84
      ...extraCustomParams,
85
    };
86

87
    if (this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)) {
11✔
88
      return this.startRefreshSession(config, allConfigs, mergedParams).pipe(
5✔
89
        map(() => {
90
          const isAuthenticated =
91
            this.authStateService.areAuthStorageTokensValid(config);
5✔
92

93
          if (isAuthenticated) {
5✔
94
            return {
4✔
95
              idToken: this.authStateService.getIdToken(config),
96
              accessToken: this.authStateService.getAccessToken(config),
97
              userData: this.userService.getUserDataFromStore(config),
98
              isAuthenticated,
99
              configId,
100
            } as LoginResponse;
101
          }
102

103
          return {
1✔
104
            isAuthenticated: false,
105
            errorMessage: '',
106
            userData: null,
107
            idToken: '',
108
            accessToken: '',
109
            configId,
110
          };
111
        })
112
      );
113
    }
114

115
    const { silentRenewTimeoutInSeconds } = config;
6✔
116
    const timeOutTime = (silentRenewTimeoutInSeconds ?? 0) * 1000;
6!
117

118
    return forkJoin([
6✔
119
      this.startRefreshSession(config, allConfigs, extraCustomParams),
120
      this.silentRenewService.refreshSessionWithIFrameCompleted$.pipe(
121
        filter((result) => result?.configId === config.configId),
4✔
122
        take(1)
123
      ),
124
    ]).pipe(
125
      timeout(timeOutTime),
126
      retryWhen((errors) => {
127
        return errors.pipe(
2✔
128
          mergeMap((error, index) => {
129
            const scalingDuration = 1000;
5✔
130
            const currentAttempt = index + 1;
5✔
131

132
            if (
5✔
133
              !(error instanceof TimeoutError) ||
9✔
134
              currentAttempt > MAX_RETRY_ATTEMPTS
135
            ) {
136
              return throwError(() => new Error(error));
2✔
137
            }
138

139
            this.loggerService.logDebug(
3✔
140
              config,
141
              `forceRefreshSession timeout. Attempt #${currentAttempt}`
142
            );
143

144
            this.flowsDataService.resetSilentRenewRunning(config);
3✔
145

146
            return timer(currentAttempt * scalingDuration);
3✔
147
          })
148
        );
149
      }),
150
      map(([_, callbackContext]) => {
151
        const isAuthenticated =
152
          this.authStateService.areAuthStorageTokensValid(config);
2✔
153

154
        if (isAuthenticated) {
2!
155

NEW
156
          const authResult = callbackContext && 'authResult' in callbackContext
×
157
            ? callbackContext.authResult
158
            : null;
159

UNCOV
160
          return {
×
161
            idToken: authResult?.id_token ?? '',
×
162
            accessToken: authResult?.access_token ?? '',
×
163
            userData: this.userService.getUserDataFromStore(config),
164
            isAuthenticated,
165
            configId,
166
          };
167
        }
168

169
        return {
2✔
170
          isAuthenticated: false,
171
          errorMessage: '',
172
          userData: null,
173
          idToken: '',
174
          accessToken: '',
175
          configId,
176
        };
177
      })
178
    );
179
  }
180

181
  private persistCustomParams(
182
    extraCustomParams: { [key: string]: string | number | boolean } | undefined,
183
    config: OpenIdConfiguration
184
  ): void {
185
    const { useRefreshToken } = config;
5✔
186

187
    if (extraCustomParams) {
5✔
188
      if (useRefreshToken) {
2✔
189
        this.storagePersistenceService.write(
1✔
190
          'storageCustomParamsRefresh',
191
          extraCustomParams,
192
          config
193
        );
194
      } else {
195
        this.storagePersistenceService.write(
1✔
196
          'storageCustomParamsAuthRequest',
197
          extraCustomParams,
198
          config
199
        );
200
      }
201
    }
202
  }
203

204
  private startRefreshSession(
205
    config: OpenIdConfiguration,
206
    allConfigs: OpenIdConfiguration[],
207
    extraCustomParams?: { [key: string]: string | number | boolean }
208
  ): Observable<boolean | CallbackContext | null> {
209
    const isSilentRenewRunning =
210
      this.flowsDataService.isSilentRenewRunning(config);
5✔
211

212
    this.loggerService.logDebug(
5✔
213
      config,
214
      `Checking: silentRenewRunning: ${isSilentRenewRunning}`
215
    );
216
    const shouldBeExecuted = !isSilentRenewRunning;
5✔
217

218
    if (!shouldBeExecuted) {
5✔
219
      return of(null);
2✔
220
    }
221

222
    return this.authWellKnownService
3✔
223
      .queryAndStoreAuthWellKnownEndPoints(config)
224
      .pipe(
225
        switchMap(() => {
226
          this.flowsDataService.setSilentRenewRunning(config);
3✔
227

228
          if (this.flowHelper.isCurrentFlowCodeFlowWithRefreshTokens(config)) {
3✔
229
            // Refresh Session using Refresh tokens
230
            return this.refreshSessionRefreshTokenService.refreshSessionWithRefreshTokens(
2✔
231
              config,
232
              allConfigs,
233
              extraCustomParams
234
            );
235
          }
236

237
          return this.refreshSessionIframeService.refreshSessionWithIframe(
1✔
238
            config,
239
            allConfigs,
240
            extraCustomParams
241
          );
242
        })
243
      );
244
  }
245
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc