Skip to content

Commit d4e79b7

Browse files
committed
Log every cold-start and recovery branch in the auth flow
Cold-start, the already-authenticated guard path, and the 401-refresh failure path were all silent. On iOS PWA every foreground is a cold start (the page gets terminated when backgrounded), so the silent branches meant no [auth] line ever reached Faro between page open and the next pagehide flush. Add: - Boot trace in main.ts the moment Faro is initialized, capturing standalone vs browser, returned-from-authority, and visibilityState - Cold-start snapshot in TokenRenewalService.init() showing whether the refresh/access token survived storage eviction - Positive log in authGuard when the session is already valid - Result details (isAuthenticated, hasAccessToken) on both the foreground refresh and the 401-retry refresh completion logs - Failure log when the 401-retry refresh itself fails
1 parent c593ac3 commit d4e79b7

4 files changed

Lines changed: 90 additions & 6 deletions

File tree

client/src/app/utils/auth-retry.interceptor.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,28 @@ export const authRetryInterceptor: HttpInterceptorFn = (req, next) => {
3434
);
3535

3636
return oidcSecurityService.forceRefreshSession().pipe(
37-
tap(() =>
37+
tap((result) =>
3838
console.info(
3939
'[auth] Token refreshed after 401 - retrying original request',
40-
JSON.stringify({ url: req.url })
40+
JSON.stringify({
41+
url: req.url,
42+
isAuthenticated: result?.isAuthenticated ?? false,
43+
})
4144
)
4245
),
46+
catchError((refreshError: unknown) => {
47+
console.warn(
48+
'[auth] Token refresh after 401 failed - propagating original error',
49+
JSON.stringify({
50+
url: req.url,
51+
error:
52+
refreshError instanceof Error
53+
? refreshError.message
54+
: String(refreshError),
55+
})
56+
);
57+
return throwError(() => error);
58+
}),
4359
switchMap(() => next(req))
4460
);
4561
})

client/src/app/utils/auth.guard.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export const authGuard: CanActivateFn = () => {
1717
take(1),
1818
switchMap((isAuthenticated) => {
1919
if (isAuthenticated) {
20+
console.info(
21+
'[auth] Auth guard passed - already authenticated, no renewal needed'
22+
);
2023
return of(true);
2124
}
2225

@@ -32,7 +35,8 @@ export const authGuard: CanActivateFn = () => {
3235
}
3336

3437
console.info(
35-
'[auth] Not authenticated but refresh token present - attempting silent renewal before full re-authentication'
38+
'[auth] Not authenticated but refresh token present - attempting silent renewal before full re-authentication',
39+
JSON.stringify({ refreshTokenLength: refreshToken.length })
3640
);
3741
return oidc.forceRefreshSession().pipe(
3842
switchMap((result) => {

client/src/app/utils/token-renewal.service.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export class TokenRenewalService {
2222
private readonly destroyRef = inject(DestroyRef);
2323

2424
init(): void {
25+
this.logColdStartSnapshot();
26+
2527
const visible$ = fromEvent(document, 'visibilitychange');
2628
const focus$ = fromEvent(window, 'focus');
2729
const online$ = fromEvent(window, 'online');
@@ -34,6 +36,49 @@ export class TokenRenewalService {
3436
.subscribe(() => this.renewIfForeground());
3537
}
3638

39+
/**
40+
* Single boot-time entry that always lands in Faro, even when nothing else
41+
* fires. On iOS PWA the page is frequently terminated when backgrounded, so
42+
* every "return to foreground" is actually a cold start with no
43+
* visibilitychange/focus event to hook into. This snapshot is the only signal
44+
* that explains which state the new page instance started in: did the OIDC
45+
* storage survive, did the user come back from the authority redirect, etc.
46+
*/
47+
private logColdStartSnapshot(): void {
48+
const url = new URL(window.location.href);
49+
const returnedFromAuthority =
50+
url.searchParams.has('code') || url.searchParams.has('error');
51+
52+
forkJoin({
53+
isAuthenticated: this.oidcSecurityService.isAuthenticated(),
54+
refreshToken: this.oidcSecurityService.getRefreshToken(),
55+
accessToken: this.oidcSecurityService.getAccessToken(),
56+
})
57+
.pipe(takeUntilDestroyed(this.destroyRef))
58+
.subscribe(({ isAuthenticated, refreshToken, accessToken }) => {
59+
const hasRefreshToken = !!refreshToken;
60+
const hasAccessToken = !!accessToken;
61+
62+
console.info(
63+
`[auth] Cold start - ${
64+
hasRefreshToken ? 'refresh token present in storage' : 'no refresh token in storage'
65+
}`,
66+
JSON.stringify({
67+
isAuthenticated,
68+
hasRefreshToken,
69+
hasAccessToken,
70+
refreshTokenLength: refreshToken?.length ?? 0,
71+
returnedFromAuthority,
72+
displayMode:
73+
window.matchMedia?.('(display-mode: standalone)').matches
74+
? 'standalone'
75+
: 'browser',
76+
storage: snapshotStorageKeys(),
77+
})
78+
);
79+
});
80+
}
81+
3782
private renewIfForeground(): void {
3883
if (document.visibilityState !== 'visible') {
3984
return;
@@ -77,8 +122,14 @@ export class TokenRenewalService {
77122
this.oidcSecurityService
78123
.forceRefreshSession()
79124
.pipe(
80-
tap(() =>
81-
console.info('[auth] Proactive foreground token refresh completed')
125+
tap((result) =>
126+
console.info(
127+
'[auth] Proactive foreground token refresh completed',
128+
JSON.stringify({
129+
isAuthenticated: result?.isAuthenticated ?? false,
130+
hasAccessToken: !!result?.accessToken,
131+
})
132+
)
82133
),
83134
catchError((error: unknown) => {
84135
console.error(

client/src/main.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,21 @@ import { initFaro } from './app/utils/faro';
66

77
loadEnvironmentConfig().then(environment => {
88
initFaro(environment.clientLogUrl, environment.clientAppName);
9+
const url = new URL(window.location.href);
10+
console.info(
11+
'[auth] Boot - Faro initialized, starting Angular bootstrap',
12+
JSON.stringify({
13+
returnedFromAuthority:
14+
url.searchParams.has('code') || url.searchParams.has('error'),
15+
displayMode:
16+
window.matchMedia?.('(display-mode: standalone)').matches
17+
? 'standalone'
18+
: 'browser',
19+
visibilityState: document.visibilityState,
20+
})
21+
);
922
bootstrapApplication(AppComponent, getAppConfig(environment))
10-
.catch((err) => console.error(err));
23+
.catch((err) => console.error('[auth] Angular bootstrap failed', err));
1124
});
1225

1326
async function loadEnvironmentConfig(): Promise<EnvironmentConfig> {

0 commit comments

Comments
 (0)