Реактивная аутентификация (Auth0) - как правильно? - PullRequest
0 голосов
/ 08 января 2020

Я новичок ie, чтобы реагировать, но я учусь, и мне нужна ваша помощь здесь. Я использую Auth0 для аутентификации, и я реализовал их образец реакции по частям:

https://auth0.com/docs/quickstart/spa/react/01-login

Это части моего кода:

Приложение . js:

       <Auth0Provider
          domain={AUTH_CONFIG.domain}
          client_id={AUTH_CONFIG.clientId}
          redirect_uri={AUTH_CONFIG.callbackUrl}
          onRedirectCallback={onRedirectCallback}
        >
          <Router history={history}>
            <RequireAuthentication>
              <MyTheme>
                <MyLayout />
              </MyTheme>
            </RequireAuthentication>
          </Router>
        </Auth0Provider>

Auth0Provider:

import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "@auth0/auth0-spa-js";
import jwtDecode from "jwt-decode";
import axios from "axios";
import AUTH_CONFIG from "./auth0Config";
import { useDispatch } from "react-redux";
import * as authActions from "app/auth/store/actions";

const DEFAULT_REDIRECT_CALLBACK = () =>
  window.history.replaceState({}, document.title, window.location.pathname);

export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
  children,
  onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
  ...initOptions
}) => {
  const [isAuthenticated, setIsAuthenticated] = useState();
  const [user, setUser] = useState();
  const [auth0Client, setAuth0] = useState();
  const [loading, setLoading] = useState(true);
  const [popupOpen, setPopupOpen] = useState(false);

  const dispatch = useDispatch();

  useEffect(() => {
    const initAuth0 = async () => {
      console.log("initAuth0 start");
      const auth0FromHook = await createAuth0Client(initOptions);
      setAuth0(auth0FromHook);

      const isAuthenticated = await auth0FromHook.isAuthenticated();
      console.log("Authenticated from init: " + isAuthenticated);

      setIsAuthenticated(isAuthenticated);

      setLoading(false);
      console.log("initAuth0 end");
    };

    initAuth0();
    // eslint-disable-next-line
  }, []);

  const loginWithPopup = async (params = {}) => {
    setPopupOpen(true);
    try {
      await auth0Client.loginWithPopup(params);
    } catch (error) {
      console.error(error);
    } finally {
      setPopupOpen(false);
    }
    const user = await getUserData();
    setUser(user);
    dispatch(authActions.setUserDataAuth0(user));
    setIsAuthenticated(true);
  };

  const handleRedirectCallback = async () => {
    if (!auth0Client) {
      console.warn("Auth0 Service didn't initialize, check your configuration");
      return;
    }

    setLoading(true);
    await auth0Client.handleRedirectCallback();
    const user = await getUserData();
    setLoading(false);
    setIsAuthenticated(true);
    setUser(user);
    dispatch(authActions.setUserDataAuth0(user));
  };

  const getAccessToken = async () => {
    const accessToken = await auth0Client.getTokenSilently({
      audience: AUTH_CONFIG.identity_audience,
      scope: "read:allUsers read:UserPermission"
    });
    return accessToken;
  };

  const getIdToken = async () => {
    if (!auth0Client) {
      console.warn("Auth0 Service didn't initialize, check your configuration");
      return;
    }
    const claims = await auth0Client.getIdTokenClaims();
    return claims.__raw;
  };

  const getTokenData = async () => {
    const token = await getIdToken();
    const decoded = jwtDecode(token);
    if (!decoded) {
      return null;
    }
    return decoded;
  };

  const getUserData = async () => {
    console.log("getuserdata");
    const tokenData = await getTokenData();
    const accessToken = await getAccessToken();
    return new Promise((resolve, reject) => {
      const { sub: userId } = tokenData;

      const UserService =
        "https://localhost:44312/api/v1/usermanagement/user/" + userId;

      axios
        .get(UserService, {
          headers: {
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Credentials": "true",
            "Access-Control-Allow-Methods": "GET,HEAD,OPTIONS,POST,PUT",
            "Access-Control-Allow-Headers":
              "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers",
            "Content-Type": "application/json",
            Authorization: "Bearer " + accessToken
          }
        })
        .then(response => {
          resolve(response.data);
        })
        .catch(error => {
          // handle error
          console.warn("Cannot retrieve user data", error);
          reject(error);
        });
    });
  };

  return (
    <Auth0Context.Provider
      value={{
        isAuthenticated,
        user,
        loading,
        popupOpen,
        loginWithPopup,
        handleRedirectCallback,
        getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
        loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
        getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
        getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
        logout: (...p) => auth0Client.logout(...p)
      }}
    >
      {children}
    </Auth0Context.Provider>
  );
};

RequireAuthentication:

import React, { useEffect } from "react";
import { useAuth0 } from "app/auth/AuthProvider";
import { SplashScreen } from "@my";
import history from "@history";

export const RequireAuthentication = ({ children }) => {
  const { isAuthenticated, loading } = useAuth0();

  useEffect(() => {
    console.log("checkAuth");
    if (!loading) checkAuth();
    // eslint-disable-next-line
  }, []);

  const checkAuth = () => {
    console.log("checkAuth isAuthenticated: " + isAuthenticated);
    console.log("checkAuth loading: " + loading);

    if (!isAuthenticated && !loading) {
      history.push("/login");
    }
  };

  return isAuthenticated ? (
    <React.Fragment>{children}</React.Fragment>
  ) : (
    <SplashScreen />
  );
};

обратный вызов. js:

import React, { useEffect } from "react";
import { SplashScreen } from "@my";
import { useAuth0 } from "app/auth/AuthProvider";

function Callback(props) {
  const { isAuthenticated, handleRedirectCallback, loading } = useAuth0();

  useEffect(() => {
    const fn = async () => {
      if (!loading) {
        console.log("handleRedirectCallback: " + loading);
        await handleRedirectCallback();
      }
    };
    fn();
  }, [isAuthenticated, loading, handleRedirectCallback]);

  return <SplashScreen />;
}

export default Callback;

Проблема в том, что компонент RequireAuthentication отображается до полной инициализации Auth0Provider, и поэтому я никогда не получаю isAuthenticated на «true».

console screenshot

Компонент RequireAuthentication является дочерним по отношению к Auth0Provider. Можно ли дождаться полной инициализации Auth0Provider перед рендерингом компонента RequireAuthentication ???

Какой здесь правильный путь ?? Я совершенно не прав?

Спасибо

Крис

1 Ответ

1 голос
/ 09 января 2020

Зависит от загрузки и использования элементов isAuthenticated, так что компонент будет визуализироваться после их изменения.

  useEffect(() => {
    console.log("checkAuth");
    if (!loading) checkAuth();
    // eslint-disable-next-line
  }, [loading, isAuthenticated]);
...