reactjs - 将 OIDC 添加到具有受限路由的 React 应用程序

标签 reactjs typescript oauth-2.0 openid-connect react-context

我想将 OIDC 添加到我的 React 应用程序中,我正在使用 oidc-client-ts因为它看起来很受欢迎并且仍在维护中。我的问题是我错过了一些 React 示例。

我想要的是除了一条要保护的路线之外的所有路线。如果用户未通过身份验证,则应将他们重定向到登录屏幕,该屏幕有一个按钮可以使用自定义提供程序激活身份验证流程。

我试过使用these two示例,但我不确定如何将它们粘合在一起以及如何将 Angular 代码转换为 React。

到目前为止,我已经将整个应用程序包装在一个 AuthContext 中,并将除一条路由之外的所有路由设为私有(private),如 first example 中所示。 :

index.tsx:

<StrictMode>
    <AuthProvider>
        <BrowserRouter>
            <Routes>
                <Route path={routes.LOGIN} element={<LoginContainer />} />
                <Route element={<Layout />}>
                    <Route index element={<Home />} />
                    <Route path="/openid/callback" element={<AuthCallback />} />
                        // Other pages
                </Route>
                <Route path="*" element={<ErrorPage />} />
            </Routes>
        </BrowserRouter>
    </AuthProvider>
</StrictMode>

Layout-具有私有(private)路由的组件,使除"/login" 之外的所有路径都是私有(private)的:

function RequireAuth({ children }: { children: JSX.Element }) {
    const auth = useAuth();

    if (!auth.user) {
        return <Navigate to="/login" replace />;
    }

    return children;
}

function Layout() {
    return (
        <RequireAuth>
            <>
                <Header />
                <Main />
                <Footer />
            </>
        </RequireAuth>
    );
}

授权提供者:

const AuthContext = createContext<AuthContextType>(null!);

const useAuth = () => useContext(AuthContext);

function AuthProvider({ children }: { children: React.ReactNode }) {
    const [user, setUser] = useState<any>(null);
    const authService = new AuthService();

    const login = () => authService.login().then(user1 => setUser(user1));

    const loginCallback = async () => {
        const authedUser = await authService.loginCallback();
        setUser(authedUser);
    };

    const logout = () => authService.login().then(() => setUser(null));
    const value = { user, login, logout };

    return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export { AuthProvider, useAuth };

authService 只是从 angular example 中复制的:

import { User, UserManager } from "oidc-client-ts";

export default class AuthService {
    userManager: UserManager;

    constructor() {
        const settings = {
            authority: "...",
            client_id: "...",
            redirect_uri: "http://localhost:3000/openid/callback",
            client_secret: "...",
            post_logout_redirect_uri: "http://localhost:3000/login"
        };
        this.userManager = new UserManager(settings);
    }

    public getUser(): Promise<User | null> {
        return this.userManager.getUser();
    }

    public login(): Promise<void> {
        return this.userManager.signinRedirect();
    }

    public loginCallback(): Promise<User> {
        return this.userManager.signinRedirectCallback();
    }

    public logout(): Promise<void> {
        return this.userManager.signoutRedirect();
    }
}

我的问题是我不知道如何在 AuthProvider 中设置用户,因此我可以检查我是否在 RequireAuth 组件中获得授权。它没有在 AuthProvider 的登录和注销功能中的 then 中设置,所以每当我尝试登录时,我都会被重定向到登录页面。

有人可以告诉我如何使用 OIDC 进行身份验证流程并将我的所有路径限制为只允许经过身份验证的用户使用吗?

此外 this answer表示应该有一个 AuthorizationCallback 组件来解析 URL。当我使用 oidc-client-ts这似乎为我解析了数据,我真的需要这个额外的步骤还是我可以将重定向 URL 设为“/”或“/home”?

编辑:

我发现 signinRedirect 转到一个新的 URL,这意味着脚本的其余部分永远不会运行。 signinRedirectCallback 是返回用户的调用。当我弄清楚如何正确保护路线后,我会将其作为答案发布。 RequireAuth 中的检查在用户设置之前完成。如何将检查推迟到用户已设置,这样即使我已登录也不会重定向到登录?如果我刷新页面,我将失去 AuthProvideruser 状态,并且即使存在事件 session ,我也会被发送到登录页面。当我以干净的方式加载应用程序时,我不确定我在哪里以及如何检查我是否正在运行 session 。

最佳答案

我从 Drew Reese 的回答中得到启发,设法解决了这个问题。 问题是 oidc-client-ts ' signinRedirect 将重定向到身份验证服务器,因此 React 代码将停止执​​行并且永远不会运行 then block 。诀窍是使用 signinRedirectCallback在登录调用之后,处理来自授权端点的响应。因此,当我点击重定向 URL (http://localhost:3000/openid/callback) 时,我会调用 signinRedirectCallback 来确定是否应该转到 HomeLogin - 组件。所以除了/login 和 post-login-redirect-url 之外的所有路由都将被授权保护:

<Routes>
  <Route path={routes.LOGIN} element={<LoginContainer />} />
  <Route path="/openid/callback" element={<AuthCallback />} />
  <Route element={<LayoutWithAuth />}>
    <Route index element={<Home />} />
    ...
  </Route>
  <Route path="*" element={<ErrorPage />} />
</Routes>;

然后在重定向回应用程序时,loginCallback/signingRedirectCallback(第一个只是将调用转发给 authService),在 中设置用户AuthContext(见下文),我导航到主页:

AuthCallback:

function AuthCallback() {
  const auth = useAuth();
  const navigate = useNavigate();

  useEffect(() => {
    auth.loginCallback().then(() => {
      navigate(routes.INDEX);
    });
  }, []);

  return <div>Processing signin...</div>;
}

通过使 loginCallback 异步,我确保当我重定向到登录页面时,用户将在 LayoutWithAuth 组件执行身份验证检查时设置:

function RequireAuth({ children }: { children: JSX.Element }) {
  const auth = useAuth();
  return auth.user === undefined ? <Navigate to="/login" replace /> : children;
}

function LayoutWithAuth() {
  return (
    <RequireAuth>
      <>
        <Header />
        <Body />
        <Footer />
      </>
    </RequireAuth>
  );
}

oidc-client-ts 将用户保存在sessionStorage ,所以如果页面被刷新,AuthContext 将首先检查 sessionStorage 以查看用户是否被授权:

AuthContext:

const AuthContext = createContext<AuthContextType>(null!);

const useAuth = () => useContext(AuthContext);

function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null | undefined>(
    JSON.parse(
      sessionStorage.getItem(process.env.REACT_APP_SESSION_ID!) || "null"
    ) || undefined
  );

  const authService = new AuthService();

  const loginCallback = async (): Promise<void> => {
    const authedUser = await authService.loginCallback();
    setUser(authedUser);
  };

  // Login and logout methods

  const value = { user, login, logout };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export { AuthProvider, useAuth };

更新: 添加 LoginContainer:

function LoginContainer() {
  const auth = useAuth();

  const login = () => {
    auth.login();
  };
  return (
    <Container>
      <Headline>Some headline</Headline>
      <Button variant="contained" onClick={() => login()}>
        {texts.BUTTON_LOGIN}
      </Button>
    </Container>
  );
}

export default LoginContainer;

关于reactjs - 将 OIDC 添加到具有受限路由的 React 应用程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72163825/

相关文章:

javascript - React JS 映射对象名称的问题

angular - 创建 'once emitting' 可观察对象

ruby-on-rails - "application_id"上的门卫 ActiveRecord::NotNullViolation(密码授予)

javascript - 可以使用对象方法或 JavaScript 中的其他方式进一步优化吗?

javascript - 将 ref 作为属性传递和将其作为 prop 传递有什么区别?

javascript - ReactJS 组件名称必须以大写字母开头吗?

android - 使用 OAuth2 安全验证移动访问的选项

typescript - 具有最后一个值的 Angular2 EventEmitter

interface - 在 Typescript 中如何修复无法设置未定义的属性 'first'

ruby-on-rails - 如何为门卫的 skip_authorization block 指定 super 应用列表?