React 및 Redux 툴킷의 JWT 갱신 토큰

25267 단어 authreactreduxjwt
새로 고침 토큰은 액세스 토큰을 얻는 데 사용되는 자격 증명입니다. 새로 고침 토큰은 권한 부여 서버에서 클라이언트에 발급되며 현재 액세스 토큰이 유효하지 않거나 만료될 때 새 액세스 토큰을 얻거나 동일하거나 더 좁은 범위의 추가 액세스 토큰을 얻는 데 사용됩니다. 이 구현은 React 및 Redux Toolkit을 사용하며 .

리포지토리 복제




git clone [email protected]:ihaback/refresh-token-redux-toolkit.git your-project-name



cd your-project-name


프로젝트 설정




npm install


React와 Express 백엔드를 동시에 실행




npm run start


구현 테스트를 위한 자격 증명





const users = [
  {
    id: "1",
    username: "john",
    password: "john123",
    isAdmin: true,
  },
  {
    id: "2",
    username: "joe",
    password: "joe123",
    isAdmin: false,
  },
];



백엔드는 3초 후에 토큰을 새로 고칠 것으로 예상합니다.




// 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!");
  }
};


퍼블릭 및 프라이빗 엔드포인트와 통신하기 위한 두 가지 axios 인스턴스




// 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" });


새로 고침 토큰은 Axios 요청 인터셉터에 의해 처리됩니다.




// 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);
  }
);


백엔드와의 상태 처리 및 통신은 Redux 작업을 통해 처리됩니다.




// 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;
}


모든 테스트가 통과하면 자동 의존 봇 병합




name: Test on PR

on:
  pull_request:

permissions:
  pull-requests: write
  contents: write

jobs:
  build:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}

    steps:
      - uses: actions/checkout@v2

      - name: Set up node
        uses: actions/setup-node@v1
        with:
          node-version: 16.x

      - name: Install packages
        run: npm install

      - name: Run a security audit        
        run: npm audit --audit-level=critical

      - name: Lint application
        run: npm run lint

      - name: Build application
        run: npm run build

      - name: E2E tests
        uses: cypress-io/github-action@v2
        continue-on-error: false
        with:
          record: false
          start: npm run start
          wait-on: 'http://localhost:3000'
          wait-on-timeout: 60
          spec: cypress/integration/*.js
          browser: chrome

      - name: Enable auto-merge for Dependabot PRs
        run: gh pr merge --auto --merge "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}


코드



The full example is here

좋은 웹페이지 즐겨찾기