bjoluc / next-redux-cookie-wrapper

Sync a subset of your Redux state with cookies in Next.js :cookie: :sparkles:
MIT License
114 stars 4 forks source link

how set refresh token in cookie #25

Closed zhangwei900808 closed 2 years ago

zhangwei900808 commented 2 years ago

What version of Next.js are you using?

"next": "latest",

What version of Node.js are you using?

v14.17.4

What browser are you using?

Chrome

What operating system are you using?

MacOS

How are you deploying your application?

yarn dev

Describe the Bug

when my token is expired so I refresh token , so console this not work!!!

warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
Not setting "auth.accessToken" cookie. Response has finished.
You should set cookie before res.send()
warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
warn  - You should not access 'res' after getServerSideProps resolves.
Read more: https://nextjs.org/docs/messages/gssp-no-mutating-res
Not setting "auth.refreshToken" cookie. Response has finished.
You should set cookie before res.send()
error= Error: Request failed with status code 401

setupIntersetor.js

import axiosInstance from "./axios";
import createAuthRefreshInterceptor from 'axios-auth-refresh';
import qs from 'qs'
import {setAuth} from '@/store/slices/authSlice'

const setup = (store) => {
  console.log('---------setup store--------', JSON.stringify(store.getState().auth))
  const {dispatch} = store;

  createAuthRefreshInterceptor(axiosInstance, async failedRequest => {
    const authState = store.getState().auth;
    console.log('-------------- failedRequest222 authState -------------', JSON.stringify(authState))
    if (authState.refreshToken) {
      return await axiosInstance.post('/auth/oauth/token',
        qs.stringify({
          grant_type: 'refresh_token',
          refresh_token: authState.refreshToken
        })).then(async tokenRefreshResponse => {
        console.log('------------获取新token了-----------', tokenRefreshResponse)
        failedRequest.response.config.headers.Authorization = 'Bearer ' + tokenRefreshResponse.data.access_token;
        await dispatch(setAuth({
          accessToken: tokenRefreshResponse.data.access_token,
          refreshToken: tokenRefreshResponse.data.refresh_token,
          isLogin: true,
          isExpired: false,
        }))
        return Promise.resolve();
      })
    } else {
      return Promise.resolve();
    }

  });
}

export default setup;

store/slices/authSlice

setAuth(state, action) {
      console.log('-----------setAuth-----------', action)
      if(action.payload){
        state.accessToken = action.payload.accessToken;
        state.refreshToken = action.payload.refreshToken;
        state.isLogin = action.payload.isLogin;
        state.isExpired = action.payload.isExpired;
      }
    }

store.js

import setupInterceptors from "./setupInterceptors";

const combinedReducers = combineReducers({
  [authSlice.name]: authSlice.reducer,
  [layoutSlice.name]: layoutSlice.reducer,
  [systemSlice.name]: systemSlice.reducer,
  [spaceSlice.name]: spaceSlice.reducer,
  [settingSlice.name]: settingSlice.reducer,
  [userSlice.name]: userSlice.reducer,
  [homeSlice.name]: homeSlice.reducer
});
export const store = wrapMakeStore(() => {
  const initStore = configureStore({
    reducer: combinedReducers,
    middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(
      nextReduxCookieMiddleware({
        subtrees: ["auth.accessToken", "auth.refreshToken", "auth.isLogin", "auth.me"],
      })
    )
  });
  setupInterceptors(initStore);
  return initStore;
});

const makeStore = () => store;

export const wrapper = createWrapper(store, {storeKey: 'key', debug: true});
bjoluc commented 2 years ago

Something seems to be changing the server-side store's state after getServerSideProps has returned already. That obviously can't work. You somehow need to make sure that getServerSideProps never returns before the store reaches its final state – are you awaiting everything?

zhangwei900808 commented 2 years ago

@bjoluc I know what you said and I check code is right, so I feel when await async invoke api the server had return , but I dont know how resolve it?

image

zhangwei900808 commented 2 years ago

image

bjoluc commented 2 years ago

Weird – no clue what's going on here, but I didn't fully check the interceptor logic. If you're interested, feel free to set up a code sandbox with the problem so I can help investigating in a free moment. Closing this as unrelated, but don't hesitate to post here anyway.

zhangwei900808 commented 2 years ago

@bjoluc image

zhangwei900808 commented 2 years ago

@bjoluc when I run yarn dev is ok, but when I run yarn build & start is not work!!!

zhangwei900808 commented 2 years ago

yarn dev image yarn build & start image

bjoluc commented 2 years ago

It's weird that the warnings appear above your "getServerSideProps end" log message. I don't know how this could be related to next-redux-cookie-wrapper, but in case it is: Can you set up a minimal failing example based on this repo's demo project? Interestingly, the demo already uses a thunk action without any problems :confused:

zhangwei900808 commented 2 years ago

😕 So I cant how to resolve it!! image

zhangwei900808 commented 2 years ago

@bjoluc I had resolve this problem!

store.js


const combinedReducers = combineReducers({
  [authSlice.name]: authSlice.reducer,
  [layoutSlice.name]: layoutSlice.reducer,
  [systemSlice.name]: systemSlice.reducer,
  [spaceSlice.name]: spaceSlice.reducer,
  [settingSlice.name]: settingSlice.reducer,
  [userSlice.name]: userSlice.reducer,
  [homeSlice.name]: homeSlice.reducer
});
export const initStore = configureStore({
  reducer: combinedReducers,
  middleware: (getDefaultMiddleware) => getDefaultMiddleware().prepend(
    nextReduxCookieMiddleware({
      // 是否压缩
      // compress: false,
      subtrees: ["auth.accessToken", "auth.refreshToken", "auth.isLogin", "auth.me"],
    })
  ).concat(logger)
})
export const store = wrapMakeStore(() => initStore);

export const wrapper = createWrapper(store, {storeKey: 'key', debug: true});

axios.js

import axios from 'axios';
import createAuthRefreshInterceptor from "axios-auth-refresh";
import {refresh} from '@/store/slices/authSlice'
import {initStore} from '@/store'

const axiosInstance = axios.create({
  baseURL: `${process.env.NEXT_PUBLIC_API_HOST}`,
  withCredentials: false,
});

// refresh token when 401
createAuthRefreshInterceptor(axiosInstance, async failedRequest => {
  const {dispatch} = initStore;
  const res = await dispatch(refresh());
  console.log('============createAuthRefreshInterceptor callback=======', res.payload.accessToken)
  failedRequest.response.config.headers.Authorization = 'Bearer ' + res.payload.accessToken;
  return Promise.resolve();
});

export default axiosInstance;

authSlice.js

export const refresh = createAsyncThunk('auth/refreshToken', async (params, thunkAPI) => {
  try {
    const {refreshToken} = thunkAPI.getState().auth;
    if (refreshToken) {
      console.log('==== refreshToken ====', refreshToken)
      const response = await axios.post('/auth/oauth/token', qs.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken
      }));
      const resdata = response.data;
      console.log('====refreshToken resdata ====', resdata)
      if (resdata.access_token) {
        // const refetch = await axios.get('/account/me', {
        //   headers: {Authorization: `Bearer ${resdata.access_token}`},
        // });
        // console.log('====refreshToken return1 ====', resdata)

        return {
          accessToken: resdata.access_token,
          refreshToken: resdata.refresh_token,
          isLogin: true,
          isExpired: false,
          // me: {
          //   name: refetch.data.name,
          //   avatar: refetch.data.avatar
          // }
        };
      } else {
        return thunkAPI.rejectWithValue({errorMsg: response.data.message});
      }
    } else {
      return null
    }

  } catch (error) {
    return thunkAPI.rejectWithValue({errorMsg: error.message});
  }
});

[refresh.fulfilled]: (state, action) => {
      if (action.payload) {
        state.accessToken = action.payload.accessToken;
        state.refreshToken = action.payload.refreshToken;
        state.isLogin = action.payload.isLogin;
        state.isExpired = action.payload.isExpired;
      }
    },