Anonymous
Der Interceptor kann die Variable „localStorage“ nicht aufnehmen und an den Dienst übergeben
Post
by Anonymous » 15 Jan 2026, 23:22
Problem
Ich habe ein Token-Objekt in localStorage gespeichert:
Code: Select all
{
accessToken: string
idToken: string
refreshToken: string
}
Wenn ich die Seite aktualisiere, habe ich den folgenden Interceptor erstellt:
Code: Select all
import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from "@angular/common/http"
import { inject } from "@angular/core"
import { AuthStore } from "../store/authentication/authentication.store"
import { AuthService } from "../services/auth"
import { catchError, switchMap, throwError } from "rxjs"
import { shouldSkip } from "./helpers/should-skip.helper"
export const authInterceptor: HttpInterceptorFn = (
req: HttpRequest,
next: HttpHandlerFn
) => {
const authStore = inject(AuthStore)
const authService = inject(AuthService)
// Skip login/refresh endpoints
if (shouldSkip(req.url)) {
return next(req)
}
console.log('INTERCEPTING:', req.url)
const accessToken = authStore.idToken()
const authReq = accessToken
? req.clone({
setHeaders: {
Authorization: `Bearer ${accessToken}`
}
})
: req
return next(authReq).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status !== 401) {
return throwError(() => error)
}
// Attempt token refresh
return authService.refreshToken(authStore.refreshToken()).pipe(
switchMap((response: any) => {
// Update store
authStore.setTokens({
accessToken: response.accessToken,
refreshToken: response.refreshToken,
idToken: response.idToken
})
// Retry original request
const retryReq = authReq.clone({
setHeaders: {
Authorization: `Bearer ${response.idToken}`
}
})
return next(retryReq)
}),
catchError(refreshError => {
// Refresh failed → logout
authStore.setTokens({
accessToken: '',
refreshToken: '',
idToken: ''
})
return throwError(() => refreshError)
})
)
})
)
}
Hier ist der ShouldSkip-Helfer als Referenz:
Code: Select all
const AUTH_SKIP_URLS = [
'/auth/login'
]
export function shouldSkip(url: string): boolean {
return AUTH_SKIP_URLS.some(path => url.includes(path))
}
Anstatt das RefreshToken zu treffen, wird es zu /login umgeleitet und trifft nicht auf den Sitzungs-/Refresh-Endpunkt. Im Idealfall sollte das idToken direkt aus localStorage abgerufen und an den Dienst
übergeben und meine Anwendungssitzung aktualisiert werden.
Zur weiteren Referenz finden Sie hier meinen AuthStore, die Effekte, Ereignisse und Reduzierer:
Code: Select all
import { patchState, signalStore, withHooks, withMethods, withState } from "@ngrx/signals"
import { withAuthReducer } from "./authentication.reducer"
import { withAuthEffects } from "./authentication.effect"
import { injectDispatch } from "@ngrx/signals/events"
import { authInitEvents } from "./authentication.events"
export interface Auth {
user: string
accessToken: string
refreshToken: string
isAuthenticated: boolean
idToken: string
authLoading: boolean
hasError: boolean
error: string
}
const initialState: Auth = {
user: '',
accessToken: '',
refreshToken: '',
idToken: '',
isAuthenticated: false,
authLoading: false,
hasError: false,
error: ''
}
export const AuthStore = signalStore(
{ providedIn: 'root'},
withState(initialState),
withMethods((store) => ({
setTokens(tokens: { accessToken: string; idToken: string, refreshToken: string }) {
patchState(store, {
accessToken: tokens.accessToken,
idToken: tokens.idToken,
refreshToken: tokens.refreshToken
})
},
})),
withAuthReducer(),
withAuthEffects(),
withHooks({
onInit() {
const dispatch = injectDispatch(authInitEvents)
dispatch.start(true)
}
})
)
Code: Select all
import { signalStoreFeature } from "@ngrx/signals";
import { on, withReducer } from "@ngrx/signals/events";
import { authApiEvents, authPageEvents } from "./authentication.events";
export function withAuthReducer() {
return signalStoreFeature(
withReducer(
on(authPageEvents.login, () => {
return { authLoading: true}
}),
),
withReducer(
on(authApiEvents.logout, () => ({
user: '',
accessToken: '',
idToken: '',
refreshToken: '',
isAuthenticated: false,
authLoading: false,
hasError: false,
error: ''
}))
),
withReducer(
on(authApiEvents.loginSuccess, (event: { payload: {accessToken: string, idToken: string, refreshToken: string}}) => {
localStorage.setItem('auth_tokens', JSON.stringify({
accessToken: event.payload.accessToken,
refreshToken: event.payload.refreshToken,
idToken: event.payload.idToken
}))
return {
accessToken: event.payload.accessToken,
refreshToken: event.payload.refreshToken,
isAuthenticated: true,
authLoading: false
}
})
)
)
}
Code: Select all
import { type } from "@ngrx/signals";
import { eventGroup } from "@ngrx/signals/events";
export const authInitEvents = eventGroup({
source: 'Auth Init',
events: {
start: type()
}
})
export const authPageEvents = eventGroup({
source: 'Login Page',
events: {
login: type()
}
})
export const authApiEvents = eventGroup({
source: 'Login API',
events: {
logout: type(),
loginSuccess: type()
}
})
Code: Select all
import { inject } from "@angular/core";
import { signalStoreFeature } from "@ngrx/signals";
import { Events, withEffects } from "@ngrx/signals/events";
import { AuthService } from "../../services/auth";
import { authApiEvents, authInitEvents, authPageEvents } from "./authentication.events";
import { catchError, concatMap, exhaustMap, map, of, tap } from "rxjs";
import { Router } from "@angular/router";
export function withAuthEffects() {
return signalStoreFeature(
withEffects(
(
store: Record,
events = inject(Events),
router = inject(Router),
authService = inject(AuthService)
) => ({
login$: events.on(authPageEvents.login).pipe(
exhaustMap((event) =>
authService.login(event.payload.username, event.payload.password).pipe(
tap(() => {
router.navigate(['/'])}
),
concatMap((response: any) => of(authApiEvents.loginSuccess({
accessToken: response.accessToken,
idToken: response.idToken,
refreshToken: response.refreshToken
})))
)
)
),
hydrate$: events.on(authInitEvents.start).pipe(
exhaustMap(() => {
let tokens = authService.load()
let refreshToken = tokens.refreshToken ?? '' // it should be set here
if(!refreshToken) {
return of(authApiEvents.logout(true))a
}
return authService.refreshToken(refreshToken).pipe(
tap((response: any) => {
authService.refresh(response.refreshToken)
}),
map(({token}) => {
tokens = authService.load()!
// console.log(tokens)
return authApiEvents.loginSuccess({
accessToken: tokens.accessToken,
idToken: tokens.idToken,
refreshToken: tokens.refreshToken
})
}),
catchError(() => of(authApiEvents.logout(true)))
)
})
),
logout$: events.on(authApiEvents.logout).pipe(
tap(() => {
authService.clear()
router.navigate(['/login'])
})
)
})
)
)
}
1768515770
Anonymous
[b]Problem[/b] Ich habe ein Token-Objekt in localStorage gespeichert: [code]{ accessToken: string idToken: string refreshToken: string } [/code] Wenn ich die Seite aktualisiere, habe ich den folgenden Interceptor erstellt: [code]import { HttpErrorResponse, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from "@angular/common/http" import { inject } from "@angular/core" import { AuthStore } from "../store/authentication/authentication.store" import { AuthService } from "../services/auth" import { catchError, switchMap, throwError } from "rxjs" import { shouldSkip } from "./helpers/should-skip.helper" export const authInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn ) => { const authStore = inject(AuthStore) const authService = inject(AuthService) // Skip login/refresh endpoints if (shouldSkip(req.url)) { return next(req) } console.log('INTERCEPTING:', req.url) const accessToken = authStore.idToken() const authReq = accessToken ? req.clone({ setHeaders: { Authorization: `Bearer ${accessToken}` } }) : req return next(authReq).pipe( catchError((error: HttpErrorResponse) => { if (error.status !== 401) { return throwError(() => error) } // Attempt token refresh return authService.refreshToken(authStore.refreshToken()).pipe( switchMap((response: any) => { // Update store authStore.setTokens({ accessToken: response.accessToken, refreshToken: response.refreshToken, idToken: response.idToken }) // Retry original request const retryReq = authReq.clone({ setHeaders: { Authorization: `Bearer ${response.idToken}` } }) return next(retryReq) }), catchError(refreshError => { // Refresh failed → logout authStore.setTokens({ accessToken: '', refreshToken: '', idToken: '' }) return throwError(() => refreshError) }) ) }) ) } [/code] Hier ist der ShouldSkip-Helfer als Referenz: [code]const AUTH_SKIP_URLS = [ '/auth/login' ] export function shouldSkip(url: string): boolean { return AUTH_SKIP_URLS.some(path => url.includes(path)) } [/code] Anstatt das RefreshToken zu treffen, wird es zu /login umgeleitet und trifft nicht auf den Sitzungs-/Refresh-Endpunkt. Im Idealfall sollte das idToken direkt aus localStorage abgerufen und an den Dienst [url=viewtopic.php?t=23808]übergeben[/url] und meine Anwendungssitzung aktualisiert werden. Zur weiteren Referenz finden Sie hier meinen AuthStore, die Effekte, Ereignisse und Reduzierer: [code]import { patchState, signalStore, withHooks, withMethods, withState } from "@ngrx/signals" import { withAuthReducer } from "./authentication.reducer" import { withAuthEffects } from "./authentication.effect" import { injectDispatch } from "@ngrx/signals/events" import { authInitEvents } from "./authentication.events" export interface Auth { user: string accessToken: string refreshToken: string isAuthenticated: boolean idToken: string authLoading: boolean hasError: boolean error: string } const initialState: Auth = { user: '', accessToken: '', refreshToken: '', idToken: '', isAuthenticated: false, authLoading: false, hasError: false, error: '' } export const AuthStore = signalStore( { providedIn: 'root'}, withState(initialState), withMethods((store) => ({ setTokens(tokens: { accessToken: string; idToken: string, refreshToken: string }) { patchState(store, { accessToken: tokens.accessToken, idToken: tokens.idToken, refreshToken: tokens.refreshToken }) }, })), withAuthReducer(), withAuthEffects(), withHooks({ onInit() { const dispatch = injectDispatch(authInitEvents) dispatch.start(true) } }) ) [/code] [code]import { signalStoreFeature } from "@ngrx/signals"; import { on, withReducer } from "@ngrx/signals/events"; import { authApiEvents, authPageEvents } from "./authentication.events"; export function withAuthReducer() { return signalStoreFeature( withReducer( on(authPageEvents.login, () => { return { authLoading: true} }), ), withReducer( on(authApiEvents.logout, () => ({ user: '', accessToken: '', idToken: '', refreshToken: '', isAuthenticated: false, authLoading: false, hasError: false, error: '' })) ), withReducer( on(authApiEvents.loginSuccess, (event: { payload: {accessToken: string, idToken: string, refreshToken: string}}) => { localStorage.setItem('auth_tokens', JSON.stringify({ accessToken: event.payload.accessToken, refreshToken: event.payload.refreshToken, idToken: event.payload.idToken })) return { accessToken: event.payload.accessToken, refreshToken: event.payload.refreshToken, isAuthenticated: true, authLoading: false } }) ) ) } [/code] [code]import { type } from "@ngrx/signals"; import { eventGroup } from "@ngrx/signals/events"; export const authInitEvents = eventGroup({ source: 'Auth Init', events: { start: type() } }) export const authPageEvents = eventGroup({ source: 'Login Page', events: { login: type() } }) export const authApiEvents = eventGroup({ source: 'Login API', events: { logout: type(), loginSuccess: type() } }) [/code] [code]import { inject } from "@angular/core"; import { signalStoreFeature } from "@ngrx/signals"; import { Events, withEffects } from "@ngrx/signals/events"; import { AuthService } from "../../services/auth"; import { authApiEvents, authInitEvents, authPageEvents } from "./authentication.events"; import { catchError, concatMap, exhaustMap, map, of, tap } from "rxjs"; import { Router } from "@angular/router"; export function withAuthEffects() { return signalStoreFeature( withEffects( ( store: Record, events = inject(Events), router = inject(Router), authService = inject(AuthService) ) => ({ login$: events.on(authPageEvents.login).pipe( exhaustMap((event) => authService.login(event.payload.username, event.payload.password).pipe( tap(() => { router.navigate(['/'])} ), concatMap((response: any) => of(authApiEvents.loginSuccess({ accessToken: response.accessToken, idToken: response.idToken, refreshToken: response.refreshToken }))) ) ) ), hydrate$: events.on(authInitEvents.start).pipe( exhaustMap(() => { let tokens = authService.load() let refreshToken = tokens.refreshToken ?? '' // it should be set here if(!refreshToken) { return of(authApiEvents.logout(true))a } return authService.refreshToken(refreshToken).pipe( tap((response: any) => { authService.refresh(response.refreshToken) }), map(({token}) => { tokens = authService.load()! // console.log(tokens) return authApiEvents.loginSuccess({ accessToken: tokens.accessToken, idToken: tokens.idToken, refreshToken: tokens.refreshToken }) }), catchError(() => of(authApiEvents.logout(true))) ) }) ), logout$: events.on(authApiEvents.logout).pipe( tap(() => { authService.clear() router.navigate(['/login']) }) ) }) ) ) } [/code]