Next.js 中 next_auth 和其他身份验证框架的替代方案

jay*_*tai 7 api reactjs spring-boot next.js next-auth

我正在尝试在 Next.js 上开发一个灵活的身份验证系统,可以使用 Spring (Java) API 后端。使用 Postman 可以完美地实现端点功能。该 API 还提供了自己的 JWT。我想使用 API 端点登录注册用户。这也意味着我需要一种方法来使用服务器中的 JWT 来对尝试登录的用户进行身份验证。

遵循 Next_auth 和 iron-sessions 的文档非常令人困惑。API 工作正常。尤其是 Next_auth 似乎为这种类型的身份验证提供了有限的支持。

我研究了很多帖子、教程,甚至发布了这个问题这个问题最接近我想要理解的问题,但它涉及登录后状态,并且该过程看起来有点令人困惑。这个问题似乎表明在 Next 上执行自定义身份验证非常复杂,并且最好使用框架。

我是否在这里遗漏了一些东西,或者让 Next js 与外部 API 和 JWT 一起使用是否非常复杂?我不需要 Next 提供的完整堆栈功能。我也不想被迫通过 Google、Twitter、FB 等进行身份验证。

我需要这样的东西,它是使用 React 创建的,并使用 REST API 端点来登录注册用户并管理相应的用户会话。

  var myHeaders = new Headers();
  myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
  
  var urlencoded = new URLSearchParams();
  urlencoded.append("username", enteredEmail);
  urlencoded.append("password", enteredPassword);

  var requestOptions = {
    method: 'POST',
    headers: myHeaders,
    body: urlencoded,
    redirect: 'follow'
  };

  fetch(API_LOGIN_URL, requestOptions)
    .then((res) => {
      setIsLoading(false);
      if (res.ok) {
        return res.json();
      } else {
        return res.json().then((data) => {
          let errorMessage = 'Authentication failed!';
          throw new Error(errorMessage);
        });
      }
    })
    .then((data)=> {
      authCtx.login(data.access_token);
      const processedData = JSON.stringify(data);
      console.log("Admin status "+ processedData);
     for(let i = 0; i < processedData.length; i++) {
            if(processedData.includes("ROLE_SUPER_ADMIN")) {
            console.log("Found Admin"); 
            authCtx.adminAccess(true);
          } 
          if(processedData.includes("ROLE_USER")) {
            console.log("Found User");
            break;
          }
          else {
            console.log("Not Found");
          }
    }})
    .catch((err) => {
      alert(err.message);
    });
}
};

  return (
  <section className={classes.auth}>
  <h1>{isLogin ? 'Login' : 'Sign Up'}</h1>
  <form onSubmit={submitHandler}>
    <div className={classes.control}>
      <label htmlFor='email'>Your Email</label>
      <input type='email' id='email' required ref={emailInputRef} />
    </div>
    <div className={classes.control}>
      <label htmlFor='password'>Your Password</label>
      <input type='password' id='password' required ref={passwordInputRef} />
    </div>
    <div className={classes.actions}>
      {!isLoading && <button>{isLogin ? 'Login' : 'Create Account'}</button>}
      {isLoading && <p>Sending request</p>}
      <button
        type='button'
        className={classes.toggle}
        onClick={switchAuthModeHandler}
      >
        {isLogin ? 'Create new account' : 'Login with existing account'}
      </button>
    </div>
  </form>
  </section>
  );
  };

 export default AuthForm;
Run Code Online (Sandbox Code Playgroud)

我想在 Next.js 中做类似的事情,而不需要按照 next_auth 等框架/库的规则工作。

我非常感谢任何解释如何使用 API 端点的 post 方法来查找用户名和密码的指导(建议、教程等)。

我还想知道如何使用 API 生成的 JWT 来完成该过程并对用户进行身份验证。我可以把这部分放在另一个问题中。对于这个问题,即使我知道如何使用我描述的 API 端点通过查找用户名和密码来登录,我也会很高兴。在 Next.js 中,我只看到使用 Next_auth 或 iron-sessions 等框架完成身份验证。我还没有看到您在 React 中找到的自定义身份验证方法类型(如上所述)。因此,我想知道:

我们必须使用 Next_auth 或 iron-sessions 进行身份验证吗?是否有任何自定义 Next js 身份验证方法的示例,这些方法不依赖于这些框架,并且可以与后端 API 和 JWT(例如 Spring)很好地配合?

预先感谢您的任何帮助。

jay*_*tai 9

在 Dream_Cap 的大力帮助下,他向我引导了一篇相关文章他自己的 node.js 代码,答案是完全可以编写自定义身份验证方法,而不依赖于任何框架(例如 next_auth)。

解决方案的关键在于 Next js 可以使用 React Context 作为高阶组件 (HOC),来保存身份验证状态并相应地在用户会话中保留更改。这与使用 [...nextuth].js 方法有些不同,后者旨在捕获所有请求。

这种替代方法基本上意味着您可以使用与普通 React 应用程序几乎相同的方法,但稍微修改为 Next.js 上下文:

let logoutTimer;
let initialToken;
let initialAdminToken;

const AuthContext = React.createContext({
  token: '',
  admintoken: '',
  isLoggedIn: false,
  isAdmin: false,
  login: (token) => { },
  adminAccess: (admintoken) => { },
  logout: () => { },
});

const calcTimeRemaining = (expirationTime) => {
  const currentTime = new Date().getTime();
  const adjExpireTime = new Date(expirationTime).getTime();
  const remaingDuration = adjExpireTime - currentTime;

  return remaingDuration;
}

export const AuthContextProvider = (props) => {
  const authCtx = useContext(AuthContext);
  const isAdmin = authCtx.isAdmin;

  const [token, setToken] = useState(initialToken);
  const [admintoken, setAdminToken] = useState(initialAdminToken);

  const userIsLoggedIn = !!token;
  const userHasAdmin = !!admintoken;


  useEffect(() => {
    initialToken = localStorage.getItem('token');
    initialAdminToken = localStorage.getItem('admintoken');
    if(initialAdminToken !== initialToken) {
      setToken(initialToken);
     
    } else {
      setToken(initialToken);
      setAdminToken(initialAdminToken);
    }

  }, [initialToken, initialAdminToken]);



  const logoutHandler = () => {
    setToken(null);
    setAdminToken(null);
    localStorage.removeItem('token');
    localStorage.removeItem('admintoken');
  };

  const loginHandler = (token) => {
    if(admintoken == null) {
      setToken(token);
    
    localStorage.setItem('token', token);
    } else {
      setToken(token);
      localStorage.setItem('token', token);
      setAdminToken(token);
      localStorage.setItem('admintoken', token);
    }
    // const remainingTime = calcTimeRemaining(expirationTime);
    setTimeout(logoutHandler, 300000);
    
  };

  const adminTokenHandler = (admintoken) => {
   setAdminToken(admintoken);
   localStorage.setItem('admintoken', admintoken);
  }

  const contextValue = {
    token: token,
    admintoken: admintoken,
    isAdmin: userHasAdmin,
    isLoggedIn: userIsLoggedIn,
    adminAccess: adminTokenHandler,
    login: loginHandler,
    logout: logoutHandler,
  };

  return (
    <AuthContext.Provider value={contextValue}>
      {props.children}
    </AuthContext.Provider>
  );
};


export default AuthContext;
Run Code Online (Sandbox Code Playgroud)

登录表单:

 const AuthForm = () => {
  const emailInputRef = useRef();
  const passwordInputRef = useRef();
  const [isLoading, setIsLoading] = useState(false);
  const [isAdmin, setIsAdmin] = useState(false);
  const router = useRouter();

  const authCtx = useContext(AuthContext);


  const submitHandler = (event) => {

    event.preventDefault();

    const enteredEmail = emailInputRef.current.value;
    const enteredPassword = passwordInputRef.current.value;


    var myHeaders = new Headers();
    myHeaders.append("Content-Type", "application/x-www-form-urlencoded");

    var urlencoded = new URLSearchParams();
    urlencoded.append("username", enteredEmail);
    urlencoded.append("password", enteredPassword);

    var requestOptions = {
      method: 'POST',
      headers: myHeaders,
      body: urlencoded,
      redirect: 'follow'
    };

    fetch(API_LOGIN_URL, requestOptions)
      .then(async (res) => {
        setIsLoading(false);
        if (res.ok) {
          return res.json();
        } else {
          const data = await res.json();
          let errorMessage = 'Authentication failed!';
          throw new Error(errorMessage);
        }
      })
      .then((data) => {
        authCtx.login(data.access_token);
        router.replace('/');
        const processedData = JSON.stringify(data);
        for (let i = 0; i < processedData.length; i++) {
          if (processedData.includes("ROLE_SUPER_ADMIN")) {
            console.log("Found Admin");
            authCtx.adminAccess(data.access_token);
              
          } else {
            console.log("Found User");
            authCtx.adminAccess(null);
          }
          
         
        }
      })
      .catch((err) => {
        alert(err.message);
      });

  };

  return (
    <section className={classes.auth}>
      <h1>Login</h1>
      <form onSubmit={submitHandler}>
        <div className={classes.control}>
          <label htmlFor='email'>Your Email</label>
          <input type='email' id='email' required ref={emailInputRef} />
        </div>
        <div className={classes.control}>
          <label htmlFor='password'>Your Password</label>
          <input type='password' id='password' required ref={passwordInputRef} />
        </div>
        <div className={classes.actions}>
          {!isLoading && <button>Login</button>}
          {isLoading && <p>Sending request</p>}
        </div>
      </form>
    </section>
  );
};

 
Run Code Online (Sandbox Code Playgroud)

保护路线:

const ProtectRoute = ({ children }) => {
  const authCtx = useContext(AuthContext);
const isLoggedIn = authCtx.isLoggedIn;

if (!isLoggedIn && typeof window !== 'undefined' && window.location.pathname == '/') {
      return <HomePage />;
    } else {
      if (!isLoggedIn && typeof window !== 'undefined' && window.location.pathname !== '/auth') {
        return <RestrictedSection />;
      } 
      else {
   return children;
    } 
  }

}

export default ProtectRoute;
Run Code Online (Sandbox Code Playgroud)

最后,路由保护包裹在主 _app.js 文件中:

function MyApp({ Component, pageProps }) {



// const ProtectedPages = dynamic(()=> import ('../store/ProtectRoute'));

  return (
   <AuthContextProvider>
    
        <Layout>
        <ProtectRoute> 
          <Component {...pageProps} />
          </ProtectRoute>
        </Layout>
      
    </AuthContextProvider>

  )
};

export default MyApp
Run Code Online (Sandbox Code Playgroud)