React登录系统:如何监听localStorage中api_token变更并触发UNAUTH_USER
Hey there! Let's break down your problem and figure out the best approach here.
To answer your core question: Yes, you absolutely can use a storage event listener to handle cases where the api_token in localStorage is modified externally, and this is a valid part of a robust auth solution. But we'll need to pair it with other checks to cover all edge cases.
1. Implement the Storage Event Listener
The storage event fires when localStorage is modified by another same-origin tab or window. It won't trigger if the current page itself modifies localStorage, but it's perfect for catching changes from outside the current session (like a user clearing storage in dev tools or another tab logging out).
Here's how to add this to your Layout component, along with cleanup and auth checks:
import React from 'react' import Header from './Header' import Navigation from '../navigation/components/Navigation' import Ribbon from '../ribbon/Ribbon' import Footer from './Footer' import Shortcut from '../navigation/components/Shortcut' import LayoutSwitcher from '../layout/components/LayoutSwitcher' import { connect } from 'react-redux' import { unauthUser } from '../actions/authActions' // Import your logout action class Layout extends React.Component { constructor(props) { super(props); this.handleStorageChange = this.handleStorageChange.bind(this); this.checkAuthStatus = this.checkAuthStatus.bind(this); } componentWillMount() { // Check auth state when the component first loads this.checkAuthStatus(); } componentDidMount() { // Attach the storage listener window.addEventListener('storage', this.handleStorageChange); } componentWillUnmount() { // Clean up the listener to avoid memory leaks window.removeEventListener('storage', this.handleStorageChange); } checkAuthStatus() { const apiToken = localStorage.getItem('api_token'); // If token is gone but Redux still thinks we're authenticated if (!apiToken && this.props.authenticated) { this.props.dispatch(unauthUser()); // Avoid redirect loops if we're already on login page if (this.props.location.pathname !== '/login') { this.props.router.push('/login'); } } } handleStorageChange(e) { // Only react to changes to our api_token if (e.key === 'api_token') { // If token was removed or invalidated if (!e.newValue && this.props.authenticated) { this.props.dispatch(unauthUser()); if (this.props.location.pathname !== '/login') { this.props.router.push('/login'); } } } } componentDidUpdate(prevProps) { // Handle auth state changes from Redux (like API 401 responses) if (!this.props.authenticated && prevProps.authenticated) { if (this.props.location.pathname !== '/login') { this.props.router.push('/login'); } } } render() { if (!this.props.authenticated) { // We already handled redirect in componentDidUpdate, just return null here return null; } return ( <div> <Header /> <Navigation /> <div id="main" role="main"> <LayoutSwitcher /> <Ribbon /> {this.props.children} </div> <Footer /> <Shortcut /> </div> ) } } const mapStateToProps = (state) => ({ authenticated: state.auth.authenticated, location: state.router.location // If using react-router-redux, or get from props }); export default connect(mapStateToProps)(Layout);
2. Cover All Edge Cases
The storage listener is great for cross-tab changes, but we need to handle other scenarios where auth can fail:
a. Current Page Storage Modifications
If the user clears localStorage or edits the token in the current tab, the storage event won't fire. That's why we have the checkAuthStatus method running on component mount, and you should also add checks whenever your app modifies localStorage directly (e.g., after logging out, ensure you dispatch unauthUser immediately).
b. Expired/Invalid Tokens
Even if the token exists in localStorage, it might be expired or invalid. You should handle this in your API request layer:
// Example API wrapper export const makeApiRequest = (url, options = {}) => { const token = localStorage.getItem('api_token'); const authHeaders = token ? { Authorization: `Bearer ${token}` } : {}; return fetch(url, { ...options, headers: { ...options.headers, ...authHeaders } }) .then(response => { if (response.status === 401) { // Token is invalid/expired: trigger logout store.dispatch(unauthUser()); if (window.location.pathname !== '/login') { window.location.href = '/login'; } } return response; }) .catch(error => { // Handle other errors throw error; }); };
c. Avoid Redirect Loops
Your original code had redirects in multiple lifecycle methods, which can cause warnings or infinite loops. By moving the redirect logic to componentDidUpdate and checking the current path, we prevent that.
3. Final Thoughts
Adding the storage event listener is a smart move to catch external changes to your auth token. Pair it with initial mount checks, API error handling, and careful state sync between localStorage and Redux, and you'll have a solid auth flow that handles almost every edge case.
内容的提问来源于stack exchange,提问作者cubanito.1991

