178
JWT Refresh Tokens in React & Redux Toolkit
Refresh Tokens are credentials used to obtain access tokens. Refresh tokens are issued to the client by the authorization server and are used to obtain a new access token when the current access token becomes invalid or expires, or to obtain additional access tokens with identical or narrower scope. This implementation uses React and Redux Toolkit and is inspired by this repo.
git clone [email protected]:ihaback/refresh-token-redux-toolkit.git your-project-name
cd your-project-name
npm install
npm run start
const users = [
{
id: "1",
username: "john",
password: "john123",
isAdmin: true,
},
{
id: "2",
username: "joe",
password: "joe123",
isAdmin: false,
},
];
// server.js
const generateAccessToken = (user) => {
return jwt.sign({ id: user?.id, isAdmin: user?.isAdmin }, "mySecretKey", {
expiresIn: "3s",
});
};
const verify = (req, res, next) => {
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(" ")[1];
jwt.verify(token, "mySecretKey", (err, user) => {
if (err) {
return res.status(403).json("Token is not valid!");
}
req.user = user;
next();
});
} else {
res.status(401).json("You are not authenticated!");
}
};
// src/utils/index.ts
import axios from "axios";
export const axiosPublic = axios.create({ baseURL: "http://localhost:5000/api" });
export const axiosPrivate = axios.create({ baseURL: "http://localhost:5000/api" });
// src/utils/index.ts
axiosPrivate.interceptors.request.use(
async (config) => {
const user = store?.getState()?.userData?.user;
let currentDate = new Date();
if (user?.accessToken) {
const decodedToken: { exp: number } = jwt_decode(user?.accessToken);
if (decodedToken.exp * 1000 < currentDate.getTime()) {
await store.dispatch(refreshToken());
if (config?.headers) {
config.headers["authorization"] = `Bearer ${
store?.getState()?.userData?.user?.accessToken
}`;
}
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// src/features/userSlice.ts
export const userSlice = createSlice({
name: "user",
initialState,
reducers: {
updateUserName(state, action: PayloadAction<AppState["username"]>) {
state.username = action.payload;
},
updatePassword(state, action: PayloadAction<AppState["password"]>) {
state.password = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(
login.fulfilled,
(state, action: PayloadAction<AppState["user"]>) => {
localStorage.setItem("user", JSON.stringify(action.payload));
state.user = action.payload;
}
)
.addCase(logout.fulfilled, (state) => {
localStorage.removeItem("user");
state.user = null;
state.username = "";
state.password = "";
state.success = false;
state.error = false;
})
.addCase(deleteUser.pending, (state) => {
state.success = false;
state.error = false;
})
.addCase(deleteUser.fulfilled, (state) => {
state.success = true;
})
.addCase(deleteUser.rejected, (state) => {
state.error = true;
})
.addCase(refreshToken.fulfilled, (state, action) => {
localStorage.setItem("user", JSON.stringify(action.payload));
state.user = action.payload as AppState["user"];
});
},
});
// src/types/index.ts
export interface AppState {
user: {
accessToken: string;
isAdmin: boolean;
refreshToken: string;
username: string;
} | null;
username: string;
password: string;
success: boolean;
error: boolean;
}
178