httpOnly 在DRF和reactJS之间保留身份验证令牌状态

问题描述 投票:0回答:1

我似乎无法在 Django 和 ReactJS 之间保持我的认证状态(当我刷新页面时),我失去了身份验证。 使用我的 access_token 在邮递员中向我的端点发送请求后,一切都运行良好。

这是我的 drf 的设置方式 设置.py

# CORS settings

CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = [
    "http://localhost:3000",
]

# CSRF settings
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "None"

views.py

class CookieTokenObtainPairView(TokenObtainPairView):
    def finalize_response(self, request, response, *args, **kwargs):
        if response.data.get("access"):
            response.set_cookie(
                "access_token",
                response.data["access"],
                httponly=True,
                secure=False,
                samesite="Lax",
            )
            del response.data["access"]
        return super().finalize_response(request, response, *args, **kwargs)


class CookieTokenRefreshView(TokenRefreshView):
    def finalize_response(self, request, response, *args, **kwargs):
        if response.data.get("access"):
            response.set_cookie(
                "access_token",
                response.data["access"],
                httponly=True,
                secure=False,
                samesite="Lax",
            )
            del response.data["access"]
        return super().finalize_response(request, response, *args, **kwargs)


@api_view(["GET"])
@permission_classes([IsAuthenticated])
def check_auth_status(request):
    # If the request reaches here, the user is authenticated
    return Response({"status": "authenticated"})

url.py

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/token/", CookieTokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("api/token/refresh/", CookieTokenRefreshView.as_view(), name="token_refresh"),
    path("api/check_auth_status/", check_auth_status, name="check_auth_status"),
    path("api/", include("vessels.urls")),
]

前端侧 authslice.js

export const verifyAuth = createAsyncThunk(
  "auth/verifyAuth",
  async (_, { rejectWithValue }) => {
    try {
      await api.get("/api/check_auth_status/");
      // If the request is successful, the user is authenticated
      return true;
    } catch (error) {
      // If there is an error (like a 401), the user is not authenticated
      return rejectWithValue(false);
    }
  }
);

export const authSlice = createSlice({
  name: "auth",
  initialState: {
    isAuthenticated: false,
    // other states...
  },
  reducers: {
    // ... your other reducers ...
  },
  extraReducers: (builder) => {
    builder
      .addCase(verifyAuth.fulfilled, (state) => {
        state.isAuthenticated = true;
      })
      .addCase(verifyAuth.rejected, (state) => {
        state.isAuthenticated = false;
      });
  },
});

export const { loginSuccess, logout } = authSlice.actions;
export default authSlice.reducer;

登录页面.js

import React, { useState } from "react";
import api from "../../api/axios";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { TextField, Button, Paper, Typography, Box } from "@mui/material";
import { verifyAuth } from "../../redux/slices/authSlice";
function LoginPage() {
  const navigate = useNavigate();
  const dispatch = useDispatch();
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();

    try {
      await api.post("api/token/", { username, password });

      // Verify authentication status
      dispatch(verifyAuth()).then(() => {
        // Redirect after successful login
        navigate("/");
      });
    } catch (error) {
      setError("Invalid credentials");
      console.error("Login error", error);
    }
  };

  return (
    <Box
      display="flex"
      justifyContent="center"
      alignItems="center"
      minHeight="100vh"
    >
      <Paper elevation={3} style={{ padding: "20px", width: "300px" }}>
        <Typography variant="h5" style={{ textAlign: "center" }}>
          Login
        </Typography>
        <form onSubmit={handleSubmit}>
          <TextField
            label="Username"
            variant="outlined"
            fullWidth
            margin="normal"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
          <TextField
            label="Password"
            type="password"
            variant="outlined"
            fullWidth
            margin="normal"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
          <Button
            type="submit"
            variant="contained"
            color="primary"
            fullWidth
            style={{ marginTop: "20px" }}
          >
            Login
          </Button>
          {error && (
            <Typography color="error" style={{ marginTop: "20px" }}>
              {error}
            </Typography>
          )}
        </form>
      </Paper>
    </Box>
  );
}

export default LoginPage;

应用程序.js

function App() {
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(verifyAuth());
  }, [dispatch]);

  return (
    <Router>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/"
          element={
            <ProtectedRoute>
              <HomePage />
            </ProtectedRoute>
          }
        />
      </Routes>
    </Router>
  );
}
export default App;

最后是ProtectedRoute.js

const ProtectedRoute = ({ children }) => {
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
};

export default ProtectedRoute;
reactjs django-rest-framework react-redux httponly cookie-httponly
1个回答
0
投票

我不认为您会丢失身份验证。我怀疑当页面重新加载并且您的

isAuthenticated
组件选择该初始状态值并将重定向呈现回登录页面时,
false
状态会重新初始化为
ProtectedRoute

一个简单的解决方案是将身份验证状态保存到 localStorage 并从 localStorage 初始化身份验证状态。

示例:

export const authSlice = createSlice({
  name: "auth",
  initialState: {
    isAuthenticated: !!JSON.parse(localStorage.getItem("authenticated")),
    // other states...
  },
  reducers: {
    // ... your other reducers ...
  },
  extraReducers: (builder) => {
    builder
      .addCase(verifyAuth.fulfilled, (state) => {
        state.isAuthenticated = true;
        localStorage.setItem("authenticated", true);
      })
      .addCase(verifyAuth.rejected, (state) => {
        state.isAuthenticated = false;
        localStorage.setItem("authenticated", false);
      });
  },
});

现在,当页面重新加载时,持久的

isAuthenticated
状态将被重新初始化,用户应该留在他们所在的受保护路线上。应用程序安装时调度的
verifyAuth
操作可能会在稍后使其身份验证失效。

© www.soinside.com 2019 - 2024. All rights reserved.