You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 21项目Redux与HTTP拦截器集成异常:JWT同步问题及存储整合需求

Fixing Redux-HTTP Interceptor Sync & Storage Integration in Angular 21

Hey there, let's tackle your Angular 21 Redux and HTTP interceptor synchronization issues, along with the type errors and storage integration problems in your code. I'll break this down step by step to get everything working as expected.

1. Fix Type Errors in Your Auth Code

First, let's eliminate those implicit any type warnings and variable initialization issues. Type safety will make your code more reliable and easier to debug.

Update JWT Service Functions

Add explicit types and null checks to your JWT storage helpers:

import { User } from "../model/User";

// Get access token from localStorage with type safety
const getLocalAccessToken = (): string | null => {
  const userStr = localStorage.getItem("user");
  if (!userStr) return null;
  
  const user: User = JSON.parse(userStr);
  return user.accessToken ?? null;
};

// Get refresh token from localStorage
const getLocalRefreshToken = (): string | null => {
  const userStr = localStorage.getItem("user");
  if (!userStr) return null;
  
  const user: User = JSON.parse(userStr);
  return user.refreshToken ?? null;
};

// Update access token in localStorage
const updateLocalAccessToken = (token: string): void => {
  const userStr = localStorage.getItem("user");
  if (!userStr) return;
  
  const user: User = JSON.parse(userStr);
  user.accessToken = token;
  localStorage.setItem("user", JSON.stringify(user));
};

// Clear user data from localStorage
const removeUser = (): void => {
  localStorage.removeItem("user");
};

Fix the Login Function's Type Issues

Add typed interfaces for credentials and API responses, and use Redux Thunk to sync state after login:

import { Dispatch } from '@reduxjs/toolkit';
import { loginSuccess } from './authSlice';
import axios from 'axios';

// Define your API client (adjust baseURL to your backend)
const api = axios.create({ baseURL: '/api' });

// Typed interfaces for type safety
interface LoginCredentials {
  username: string;
  password: string;
}

interface AuthResponse extends User {
  accessToken: string;
  refreshToken: string;
}

// Login function with Redux sync
const login = (credentials: LoginCredentials) => {
  return (dispatch: Dispatch) => {
    return api.post<AuthResponse>("/auth/signin", credentials)
      .then(response => {
        const user = response.data;
        dispatch(loginSuccess(user)); // Sync user data to Redux
        return user;
      });
  };
};

2. Sync Redux Store with LocalStorage

The core issue here is that your Redux state and localStorage weren't staying in sync. Let's fix this with Redux Toolkit (the recommended way to use Redux in modern apps) by adding automatic localStorage sync in your reducer.

Create a Redux Auth Slice

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User } from '../model/User';

interface AuthState {
  isLoggedIn: boolean;
  user: User | null;
  accessToken: string | null;
  refreshToken: string | null;
}

// Initialize state from localStorage
const storedUserStr = localStorage.getItem("user");
const initialState: AuthState = storedUserStr ? {
  isLoggedIn: true,
  user: JSON.parse(storedUserStr) as User,
  accessToken: (JSON.parse(storedUserStr) as User).accessToken,
  refreshToken: (JSON.parse(storedUserStr) as User).refreshToken,
} : {
  isLoggedIn: false,
  user: null,
  accessToken: null,
  refreshToken: null,
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    loginSuccess: (state, action: PayloadAction<User>) => {
      state.isLoggedIn = true;
      state.user = action.payload;
      state.accessToken = action.payload.accessToken;
      state.refreshToken = action.payload.refreshToken;
      // Sync to localStorage whenever state updates
      localStorage.setItem("user", JSON.stringify(action.payload));
    },
    logoutSuccess: (state) => {
      state.isLoggedIn = false;
      state.user = null;
      state.accessToken = null;
      state.refreshToken = null;
      localStorage.removeItem("user");
    },
    refreshTokenSuccess: (state, action: PayloadAction<string>) => {
      state.accessToken = action.payload;
      // Update localStorage with new access token
      if (state.user) {
        state.user.accessToken = action.payload;
        localStorage.setItem("user", JSON.stringify(state.user));
      }
    }
  }
});

export const { loginSuccess, logoutSuccess, refreshTokenSuccess } = authSlice.actions;
export default authSlice.reducer;

Configure Your Redux Store

Set up the store with your auth reducer:

import { configureStore } from '@reduxjs/toolkit';
import authReducer from './authSlice';

export const store = configureStore({
  reducer: {
    auth: authReducer
  }
});

// Export types for component usage
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

3. Build a Sync'd HTTP Interceptor

Now let's create an Angular HTTP interceptor that pulls the latest token from Redux (not just localStorage) to ensure every request uses the most up-to-date auth data.

Create the Auth Interceptor

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { RootState } from './store';
import { selectAccessToken } from './authSelectors';
import { take, switchMap, catchError } from 'rxjs/operators';
import { refreshToken, logoutSuccess } from './authSlice';
import { of } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private store: Store<RootState>) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return this.store.select(selectAccessToken).pipe(
      take(1), // Grab the latest token from Redux
      map(token => {
        // Add Authorization header if token exists
        return token ? request.clone({
          setHeaders: { Authorization: `Bearer ${token}` }
        }) : request;
      }),
      switchMap(modifiedRequest => next.handle(modifiedRequest).pipe(
        catchError(error => {
          // Handle 401 errors by refreshing the token
          if (error.status === 401 && !request.url.includes('/auth/refreshtoken')) {
            return this.store.dispatch(refreshToken()).pipe(
              switchMap(newToken => {
                // Retry the request with the new token
                const retryRequest = request.clone({
                  setHeaders: { Authorization: `Bearer ${newToken}` }
                });
                return next.handle(retryRequest);
              }),
              catchError(() => {
                // Refresh failed: log user out
                this.store.dispatch(logoutSuccess());
                // Add your login page redirect logic here (e.g., this.router.navigate(['/login']))
                return of(error);
              })
            );
          }
          return of(error);
        })
      ))
    );
  }
}

Create a Redux Selector for Access Token

Add a selector to cleanly access the token from state:

import { createSelector } from '@reduxjs/toolkit';
import { RootState } from './store';

const selectAuthState = (state: RootState) => state.auth;

export const selectAccessToken = createSelector(
  selectAuthState,
  authState => authState.accessToken
);

Register the Interceptor in Angular

Add the interceptor to your app module to enable it globally:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';
import { store } from './store';
import { Store } from '@ngrx/store';

@NgModule({
  providers: [
    { provide: Store, useValue: store },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]
})
export class AppModule { }

4. Add Token Refresh Logic

Finally, add a thunk action to handle token refresh and sync with Redux:

import { Dispatch } from '@reduxjs/toolkit';
import { RootState } from './store';
import { refreshTokenSuccess } from './authSlice';
import axios from 'axios';

const api = axios.create({ baseURL: '/api' });

export const refreshToken = () => {
  return (dispatch: Dispatch, getState: () => RootState) => {
    const state = getState();
    const refreshToken = state.auth.refreshToken;
    
    if (!refreshToken) return Promise.reject('No refresh token available');

    return api.post<{ accessToken: string }>("/auth/refreshtoken", { refreshToken })
      .then(response => {
        dispatch(refreshTokenSuccess(response.data.accessToken));
        return response.data.accessToken;
      });
  };
};

With these changes, your Redux store will stay in perfect sync with localStorage, your HTTP interceptor will always use the latest token from Redux, and all type errors will be resolved. This setup ensures your auth data flows correctly between storage, Redux, and your API requests.

内容的提问来源于stack exchange,提问作者Geek IT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 09:42:32