React
对应的官方页面地址
如何
首先,按照此处所述创建 React 应用程序。
生成 React 应用程序后,我们要安装我们的客户端库:
npm install @passwordlessdev/passwordless-client
我们的登录页面可能包含类似以下内容:
import {useContext, useRef, useState} from "react";
import authContext from "../context/AuthProvider";
import * as Passwordless from "@passwordlessdev/passwordless-client";
import YourBackendClient from "../services/YourBackendClient";
import {PASSWORDLESS_API_KEY, PASSWORDLESS_API_URL} from "../configuration/PasswordlessOptions";
export default function LoginPage() {
const errRef = useRef();
const [errMsg, setErrMsg] = useState("");
const [success, setSuccess] = useState(false);
const { setAuth } = useContext(authContext);
const handleSubmit = async (e) => {
e.preventDefault();
// 如果是自托管,PASSWORDLESS_API_URL 将与 https://v4.passwordless.dev 不同
const passwordless = new Passwordless.Client({
apiUrl: PASSWORDLESS_API_URL,
apiKey: PASSWORDLESS_API_KEY
});
const yourBackendClient = new YourBackendClient()
// 首先我们获取我们的令牌
const token = await passwordless.signinWithDiscoverable();
if (!token) {
return;
}
// 然后您在后端验证令牌的有效性。
const verifiedToken = await yourBackendClient.signIn(token.token);
// 例如,如果您的令牌被视为有效,您的后端可能会返回 JWT 令牌。
localStorage.setItem('jwt', verifiedToken.jwt);
// 我们很高兴能继续。
setAuth({ verifiedToken });
setSuccess(true);
}
return (
<>
{success ? (
<section>
<h1>You are logged in!</h1>
<br />
<p>{/* <a href="#">Go to Home</a> */}</p>
</section>
) : (
<section>
<p
ref={errRef}
className={errMsg ? "errmsg" : "offscreen"}
aria-live="assertive"
>
{errMsg}
</p>
<h1>Sign In</h1>
<button onClick={handleSubmit}>Sign In</button>
<p>
Need an Account?
<br />
<span className="line">
<a href="#">Sign Up</a>
</span>
</p>
</section>
)}
</>
);
}
我们的注册页面可能看起来像这样:
import {useEffect, useRef, useState} from "react";
import * as Passwordless from "@passwordlessdev/passwordless-client";
import {PASSWORDLESS_API_KEY, PASSWORDLESS_API_URL} from "../configuration/PasswordlessOptions";
import { ToastContainer, toast } from 'react-toastify';
import YourBackendClient from "../services/YourBackendClient";
export default function RegisterPage() {
const userRef = useRef();
const firstNameRef = useRef();
const lastNameRef = useRef();
const aliasRef = useRef();
const errRef = useRef();
const [user, setUser] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [alias, setAlias] = useState("");
const [errMsg, setErrMsg] = useState("");
useEffect(() => {
userRef.current.focus();
}, []);
useEffect(() => {
setErrMsg("");
}, [user]);
const handleSubmit = async (e) => {
let registerToken = null;
try {
const yourBackendClient = new YourBackendClient();
// 例如,在我们的应用程序中,除了用户名和令牌别名之外,我们还可以存储额外的字段,例如名字和姓氏。
registerToken = await yourBackendClient.register(user, firstName, lastName, alias);
}
catch (error)
{
toast(error.message, {
className: 'toast-error'
});
}
// 如果之前发生错误,'registerToken' 将为空,因此您不想向身份验证器注册令牌。
if (registerToken) {
const p = new Passwordless.Client({
apiKey: PASSWORDLESS_API_KEY,
apiUrl: PASSWORDLESS_API_URL
});
const finalResponse = await p.register(registerToken.token, alias);
if (finalResponse) {
toast(`Registered '${alias}'!`);
}
}
};
return (
<>
<section>
<p
ref={errRef}
className={errMsg ? "errmsg" : "offscreen"}
aria-live="assertive"
>
{errMsg}
</p>
<h1>Register</h1>
<label htmlFor="username">Username:</label>
<input
type="text"
id="username"
ref={userRef}
autoComplete="off"
onChange={(e) => setUser(e.target.value)}
value={user}
required
aria-describedby="uidnote"
/>
<label htmlFor="firstname">FirstName:</label>
<input
type="text"
id="firstName"
ref={firstNameRef}
autoComplete="off"
onChange={(e) => setFirstName(e.target.value)}
value={firstName}
required
aria-describedby="uidnote"
/>
<label htmlFor="lastname">LastName:</label>
<input
type="text"
id="lastname"
ref={lastNameRef}
autoComplete="off"
onChange={(e) => setLastName(e.target.value)}
value={lastName}
required
aria-describedby="uidnote"
/>
<label htmlFor="alias">Alias:</label>
<input
type="text"
id="alias"
ref={aliasRef}
autoComplete="off"
onChange={(e) => setAlias(e.target.value)}
value={alias}
required
aria-describedby="uidnote"
/>
<button onClick={handleSubmit}>Register</button>
<p>Already registered?</p>
<ToastContainer />
</section>
</>
);
}
假设您有多个组件需要访问与身份验证相关的数据,例如用户是否已登录。您可以使用此 AuthContext
向任何需要它的组件提供身份验证状态和更新功能,而不是通过组件层次结构以道具的形式传递这些数据。这减少了对道具钻探的需要,并使代码更清晰、更易于维护。
import { createContext, useState } from "react";
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({});
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
例如,您可以在子组件中使用 useContext
钩子来访问 AuthContext.Provider 提供的 auth 状态和 setAuth 函数。
import { useContext } from "react";
import AuthContext from "../context/AuthProvider";
const useAuth = () => {
return useContext(AuthContext);
}
export default useAuth;
我们的 index.jsx: AuthProvider
将封装您的整个应用程序,使所有使用它的组件都能获得身份验证状态。
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import {AuthProvider} from "./context/AuthProvider";
const root = ReactDOM.createRoot(
document.getElementById('root')
);
root.render(
<React.StrictMode>
<AuthProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</AuthProvider>
</React.StrictMode>
);
我们需要能够解码 JWT 令牌,并读取用户分配的角色。
import { useLocation, Navigate, Outlet } from "react-router-dom";
import useAuth from "../hooks/useAuth";
import jwtDecode from "jwt-decode";
function hasMatchingRole(allowedRoles, userRoles) {
if (!allowedRoles || allowedRoles.length === 0) {
return true;
}
for (let i = 0; i < allowedRoles.length; i++) {
if (userRoles.indexOf(allowedRoles[i]) !== -1) {
return true;
}
}
return false;
}
const RequireAuth = ({ allowedRoles }) => {
const { auth } = useAuth();
const location = useLocation();
let isAllowed = true;
if (allowedRoles) {
if (auth?.verifiedToken?.jwt) {
const decodedToken = jwtDecode(auth.verifiedToken.jwt);
isAllowed = hasMatchingRole(allowedRoles, decodedToken.role);
} else {
isAllowed = false;
}
}
// 您想重定向到特定页面或显示错误消息吗?
return (
isAllowed
? <Outlet />
: auth?.verifiedToken
? <Navigate to="/unauthorized" state={{ from: location }} replace />
: <Navigate to="/login" state={{ from: location }} replace />
);
}
export default RequireAuth;
在我们的 app.jsx
中或无论您路由到哪里,您都可以按如下方式定义您的路由,无论您是否需要特定角色来访问某个路由。
import React, {Component} from 'react';
import {Route, Routes} from "react-router-dom";
import UserPage from "./pages/UserPage";
import AdminPage from "./pages/AdminPage";
import PublicPage from "./pages/PublicPage";
import Layout from "./components/Layout";
import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import RequireAuth from "./components/RequireAuth";
import {ROLE_ADMIN, ROLE_USER} from "./constants/Roles";
import UnauthorizedPage from "./pages/UnauthorizedPage";
import 'react-toastify/dist/ReactToastify.css';
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Layout>
<Routes>
<Route exact path="/" element={ <PublicPage/> } />
<Route path="/register" element={ <RegisterPage/> } />
<Route path="/login" element={ <LoginPage/> } />
<Route path="unauthorized" element={<UnauthorizedPage />} />
<Route element={<RequireAuth allowedRoles={[ROLE_USER]} />}>
<Route path="/user" element={ <UserPage/> } />
</Route>
<Route element={<RequireAuth allowedRoles={[ROLE_ADMIN]} />}>
<Route path="/admin" element={ <AdminPage/> } />
</Route>
</Routes>
</Layout>
);
}
}
export default App;
参考
最后更新于