I’m encountering an issue in my React Native app where the refresh token generated during authentication isn’t persisting in AsyncStorage. Despite successfully fetching new tokens from the server, AsyncStorage fails to store them, causing subsequent requests to revert to the old tokens. As a beginner, I’m unsure why AsyncStorage isn’t saving the updated tokens correctly. Any advice or solutions on how to ensure AsyncStorage properly stores and retrieves refreshed tokens would be greatly appreciated. Thank you!
This is my auth middleware
import { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';
import AppUser from '../models/AppUser';
import Staff from '../models/Staff';
export interface AuthRequest extends Request {
user?: {
id: string;
role: string;
};
}
const generateTokens = (user: any) => {
const payload = { user: { id: user._id, role: user.role } };
const token = jwt.sign(payload, process.env.JWT_SECRET as string, { expiresIn: '5m' });
const refreshToken = jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET as string, { expiresIn: '7d' });
console.log('Generated tokens:', { token, refreshToken });
return { token, refreshToken };
};
const auth = async (req: AuthRequest, res: Response, next: NextFunction) => {
const jwtSecret = process.env.JWT_SECRET;
const refreshTokenSecret = process.env.REFRESH_TOKEN_SECRET;
if (!jwtSecret || !refreshTokenSecret) {
console.error('JWT_SECRET or REFRESH_TOKEN_SECRET not defined');
throw new Error('JWT_SECRET or REFRESH_TOKEN_SECRET not defined');
}
let token = req.header('x-auth-token');
let refreshToken = req.header('x-refresh-token');
console.log('Received token:', token);
console.log('Received refresh token:', refreshToken);
if (!token && !refreshToken) {
console.error('No token, authorization denied');
return res.status(401).json({ error: 'No token, authorization denied' });
}
if (token) {
try {
const decoded = jwt.verify(token, jwtSecret) as JwtPayload;
req.user = decoded.user;
console.log('Token verified successfully, user:', req.user);
next();
} catch (err) {
if (err instanceof jwt.TokenExpiredError && refreshToken) {
console.log('Token expired, attempting to refresh');
try {
const decodedRefresh = jwt.verify(refreshToken, refreshTokenSecret) as JwtPayload;
const userId = decodedRefresh.user?.id;
console.log('Decoded refresh token, userId:', userId);
const user = await AppUser.findById(userId) || await Staff.findById(userId);
if (!user) {
console.error('Invalid refresh token: user not found');
return res.status(403).json({ error: 'Invalid refresh token: user not found' });
}
console.log('Found user with refresh token:', user);
if (user.refreshToken !== refreshToken) {
console.error('Refresh token does not match. Expected:', user.refreshToken, 'Received:', refreshToken);
return res.status(403).json({ error: 'Refresh token does not match' });
}
const { token: newToken, refreshToken: newRefreshToken } = generateTokens(user);
user.refreshToken = newRefreshToken;
await user.save();
const updatedUser = await Staff.findById(userId);
console.log('Updated User Refresh Token:', updatedUser?.refreshToken);
console.log('Generated new tokens:', { newToken, newRefreshToken });
res.setHeader('x-auth-token', newToken);
res.setHeader('x-refresh-token', newRefreshToken);
req.user = decodedRefresh.user;
next();
} catch (refreshErr) {
console.error('Invalid refresh token:', refreshErr);
return res.status(401).json({ error: 'Invalid refresh token' });
}
} else {
console.error('Token is not valid:', err);
return res.status(401).json({ error: 'Token is not valid' });
}
}
} else if (refreshToken) {
try {
const decodedRefresh = jwt.verify(refreshToken, refreshTokenSecret) as JwtPayload;
const userId = decodedRefresh.user?.id;
console.log('Decoded refresh token, userId:', userId);
const user = await AppUser.findById(userId) || await Staff.findById(userId);
if (!user) {
console.error('Invalid refresh token: user not found');
return res.status(403).json({ error: 'Invalid refresh token: user not found' });
}
console.log('Found user with refresh token:', user);
if (user.refreshToken !== refreshToken) {
console.error('Refresh token does not match. Expected:', user.refreshToken, 'Received:', refreshToken);
return res.status(403).json({ error: 'Refresh token does not match' });
}
const { token: newToken, refreshToken: newRefreshToken } = generateTokens(user);
user.refreshToken = newRefreshToken;
await user.save();
const updatedUser = await Staff.findById(userId);
console.log('Updated User Refresh Token:', updatedUser?.refreshToken);
console.log('Generated new tokens:', { newToken, newRefreshToken });
res.setHeader('x-auth-token', newToken);
res.setHeader('x-refresh-token', newRefreshToken);
req.user = decodedRefresh.user;
next();
} catch (refreshErr) {
console.error('Invalid refresh token:', refreshErr);
return res.status(401).json({ error: 'Invalid refresh token' });
}
}
};
export default auth;
AsyncStorageManager
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
export const BASE_URL = 'http://localhost:5001/api';
const api = axios.create({
baseURL: BASE_URL,
});
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
api.interceptors.request.use(
async config => {
const token = await AsyncStorage.getItem('token');
const refreshToken = await AsyncStorage.getItem('refreshToken');
console.log('Request Token:', token);
console.log('Request Refresh Token:', refreshToken);
if (token) {
config.headers['x-auth-token'] = token;
}
if (refreshToken) {
config.headers['x-refresh-token'] = refreshToken;
}
return config;
},
error => {
console.log('Request Interceptor Error:', error);
return Promise.reject(error);
}
);
api.interceptors.response.use(
response => {
console.log('Response:', response);
return response;
},
async error => {
const originalRequest = error.config;
console.log('Response Error:', error);
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({ resolve, reject });
}).then(token => {
if (token) {
originalRequest.headers['x-auth-token'] = token;
}
return axios(originalRequest);
}).catch(err => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
const refreshToken = await AsyncStorage.getItem('refreshToken');
console.log('Attempting to refresh token with:', refreshToken);
return new Promise(function (resolve, reject) {
axios.post(`${BASE_URL}/auth/refresh-token`, { refreshToken })
.then(async ({ data }) => {
console.log('New Tokens:', data);
await AsyncStorage.setItem('token', data.token);
await AsyncStorage.setItem('refreshToken', data.refreshToken);
// Verificare imediat după setare
const savedToken = await AsyncStorage.getItem('token');
const savedRefreshToken = await AsyncStorage.getItem('refreshToken');
console.log('Saved Token:', savedToken);
console.log('Saved Refresh Token:', savedRefreshToken);
api.defaults.headers.common['x-auth-token'] = data.token;
api.defaults.headers.common['x-refresh-token'] = data.refreshToken;
originalRequest.headers['x-auth-token'] = data.token;
processQueue(null, data.token);
resolve(axios(originalRequest));
})
.catch((err) => {
console.log('Refresh Token Error:', err);
processQueue(err, null);
AsyncStorage.removeItem('token');
AsyncStorage.removeItem('refreshToken');
reject(err);
})
.finally(() => {
isRefreshing = false;
});
});
}
return Promise.reject(error);
}
);
export default api;
and this is the log:
Formatted phone number for login: +39749317801
User found for phone number +39749317801: null
Staff found for phone number +39749317801: {
_id: new ObjectId('66701c0679dd0b697f6fab8e'),
name: 'Test',
email: '[email protected]',
phoneNumber: '+39749317801',
password: '-',
restaurant: new ObjectId('66462c14dccbc277a0dce424'),
role: 'delivery',
isVerified: true,
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzQ2NywiZXhwIjoxNzE5MzMyMjY3fQ.YWpZGK6QPzvRmECeUSdRUy0VxXGG-DQEBMsJ2DAtV-M',
__v: 0
}
Received token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE4NzI4MDcyfQ.qv1IkRAnSYG26RZv-_hwijfkNX0DAhHMamjBlfaKLY8
Received refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE5MzMyNjkyfQ.denkXXZhCDww3FUSlBFeFk93YlYdMnFHrfBmrrXQO_M
Token verified successfully, user: { id: '66701c0679dd0b697f6fab8e', role: 'delivery' }
Received token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE4NzI4MDcyfQ.qv1IkRAnSYG26RZv-_hwijfkNX0DAhHMamjBlfaKLY8
Received refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE5MzMyNjkyfQ.denkXXZhCDww3FUSlBFeFk93YlYdMnFHrfBmrrXQO_M
Token expired, attempting to refresh
Decoded refresh token, userId: 66701c0679dd0b697f6fab8e
Found user with refresh token: {
_id: new ObjectId('66701c0679dd0b697f6fab8e'),
name: 'Ionut',
email: '[email protected]',
phoneNumber: '+40749317801',
password: '$2b$10$/blV6uwfnMBmGrIERnw0NuDj21y9sRxIaWvUVP1ZnCFsmHowHh2Ii',
restaurant: new ObjectId('66462c14dccbc277a0dce424'),
role: 'delivery',
isVerified: true,
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE5MzMyNjkyfQ.denkXXZhCDww3FUSlBFeFk93YlYdMnFHrfBmrrXQO_M',
__v: 0
}
Generated tokens: {
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE4NzI4NzI5fQ.qLQRGN_3JLJmClWe28r2FnGWmcLi6NyNJYYST0slmMo',
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE5MzMzMjI5fQ.xLdJCCwvuqAnSDBFr1RZX9vNk_FKcViZ0CJdEjmdSDs'
}
Updated User Refresh Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE5MzMzMjI5fQ.xLdJCCwvuqAnSDBFr1RZX9vNk_FKcViZ0CJdEjmdSDs
Generated new tokens: {
newToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE4NzI4NzI5fQ.qLQRGN_3JLJmClWe28r2FnGWmcLi6NyNJYYST0slmMo',
newRefreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE5MzMzMjI5fQ.xLdJCCwvuqAnSDBFr1RZX9vNk_FKcViZ0CJdEjmdSDs'
}
Received token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE4NzI4MDcyfQ.qv1IkRAnSYG26RZv-_hwijfkNX0DAhHMamjBlfaKLY8
Received refresh token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE5MzMyNjkyfQ.denkXXZhCDww3FUSlBFeFk93YlYdMnFHrfBmrrXQO_M
Token expired, attempting to refresh
Decoded refresh token, userId: 66701c0679dd0b697f6fab8e
Found user with refresh token: {
_id: new ObjectId('66701c0679dd0b697f6fab8e'),
name: 'Test',
email: '[email protected]',
phoneNumber: '+39749317801',
password: '-',
restaurant: new ObjectId('66462c14dccbc277a0dce424'),
role: 'delivery',
isVerified: true,
refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE5MzMzMjI5fQ.xLdJCCwvuqAnSDBFr1RZX9vNk_FKcViZ0CJdEjmdSDs',
__v: 0
}
Refresh token does not match. Expected: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyODQyOSwiZXhwIjoxNzE5MzMzMjI5fQ.xLdJCCwvuqAnSDBFr1RZX9vNk_FKcViZ0CJdEjmdSDs Received: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiNjY3MDFjMDY3OWRkMGI2OTdmNmZhYjhlIiwicm9sZSI6ImRlbGl2ZXJ5In0sImlhdCI6MTcxODcyNzg5MiwiZXhwIjoxNzE5MzMyNjkyfQ.denkXXZhCDww3FUSlBFeFk93YlYdMnFHrfBmrrXQO_M
Some advices about fix the issue
Cost Iont is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.