Быстрое автоматическое закрытие всплывающих окон при использовании аутентификации в вложенных приложениях (NAA)

Вопрос или проблема

Я пытаюсь реализовать аутентификацию вложенного приложения, чтобы переключить свой надстройку Outlook от использования устаревших токенов Exchange. Мне удается получить действительный токен графического API, но только с некоторыми недостатками пользовательского опыта при использовании браузера.

При использовании Chrome, если пользователь выходит из системы, а затем снова входит, когда он пытается снова использовать надстройку, он не получает токен тихо и вместо этого получает быстрое всплывающее окно, которое автоматически закрывается, и он получает токен. На последующих электронных письмах токен получается тихо.

Это нормальное поведение? Кажется, что в этом случае они должны иметь возможность получить токен тихо, так как они уже дали согласие на необходимые разрешения. (Это также может привести к тому, что они вообще не смогут получить токен, если браузер блокирует это быстрое всплывающее окно. Это больше проблема с Firefox, где по какой-то причине мы видим 2 всплывающих окна вместо 1 и иногда получаем всплывающее окно, замороженное открытым и пустым, не уверены, связано ли это с чем-то или нет). Рассматриваем, стоит ли нам обратить внимание на метод, не основанный на NAA, чтобы избежать этих проблем.

Я не вижу этого быстрого всплывающего окна при использовании Outlook Desktop для Mac, там все работает прекрасно.

export const getMsalConfig = (instance: string): Configuration => {
  let config =  {
    auth: {
      clientId: getClientId(instance),
      authority: "https://login.microsoftonline.com/common",
      redirectUri: "https://localhost:3000/auth.html",
      postLogoutRedirectUri: "https://localhost:3000/auth.html",
    },
    cache: {
      cacheLocation: "localStorage",
    },
    system: {},
  };

export default function App({ isOfficeInitialized }: Props) {

  const [msalInstance, setMsalInstance] = React.useState<IPublicClientApplication>();

  useEffect(() => {
    createNestablePublicClientApplication(getMsalConfig(instance))
      .then(res => {
        setMsalInstance(res);
      }).catch(error => {
      console.error(error);
    });

  }, [instance])

  return (
    <>
    {msalInstance &&
    <MsalProvider instance={msalInstance}>
    <AppErrorBoundary>
      <GlobalStyle />
        <Root>
          ... <DoThing>
        </Root>
    </AppErrorBoundary>
    </MsalProvider>
}</>
  );
}

export default function DoThing({}: Props) {

  const accountIdentifiers = {
    username: "[email protected]" // TODO genericify
  }
  const request = {
    loginHint: "[email protected]",
    scopes: ["User.Read", "Mail.ReadWrite.Shared"]
  }

  const useMsalResult = useMsal();
  const msalInstance = useMsalResult['instance'];
  // @ts-ignore
  const inProgress = useMsalResult['inProgress'];
  // @ts-ignore
  const isAuthenticated = useIsAuthenticated();

  // @ts-ignore
  const {login, error } = useMsalAuthentication(InteractionType.Silent, request, accountIdentifiers);


  const doAction = async () => {
    // Код здесь получает ewsToken, который мы в настоящее время используем

    // Попробуйте получить токен графического API
    let graphApiToken = null;
    let graphApiError = null;
    try {
      const account = msalInstance.getActiveAccount();
      if (!account) {
        throw Error("Нет активной учетной записи");
      }

      const tokenRequest = {
        scopes: ["User.Read", "Mail.ReadWrite.Shared"],
        account: account
      };
      const tokenResponse = await msalInstance.acquireTokenSilent(tokenRequest);
      graphApiToken = tokenResponse.accessToken;
    } catch ($error) {
      graphApiError = error ?? new Error("Не удалось получить токен графического API тихо");
      console.error(graphApiError.message);
    }

    // Если мы не получили токен графического API тихо и не получили ewsToken, мы постараемся более громко
    // получить токен графического API
    try {
      if (!restToken && !graphApiToken) {
        if (inProgress == InteractionStatus.None) {
          try {
            await login(InteractionType.Popup, request);
          } catch (popupError) {
            if (popupError.errorCode === "popup_window_error") {
              throw new Error("Всплывающее окно заблокировано браузером. Пожалуйста, разрешите всплывающие окна и попробуйте снова.");
            }
          }
        } else {
          console.error("взаимодействие уже в процессе");
        }

        try {
          const account = msalInstance.getActiveAccount();
          if (!account) {
            throw Error("Нет активной учетной записи");
          }

          const tokenRequest = {
            scopes: ["User.Read", "Mail.ReadWrite.Shared"],
            account: account
          };
          const tokenResponse = await msalInstance.acquireTokenSilent(tokenRequest);
          graphApiToken = tokenResponse.accessToken;
        } catch ($error) {
          graphApiError = error;
          console.error(graphApiError.message);
          throw graphApiError;
        }
      }

    } catch (error) {
      graphApiError = error ?? new Error("Не удалось получить токен графического API");
    }

    if (!ewsToken && !graphApiToken) {
      onError({
        context: 'Невозможно обработать это сообщение.',
        error:
          ewsError ??
          graphApiError ??
          new Error(
            'Нет поддержки API для получения токена обратного вызова в этой среде.'
          ),
      });
      return;
    }
  }

  useEffect(() => {
    if (!!msalInstance) {
      doAction();
    }
  }, [msalInstance]); 

  return <Root>{// некоторые компоненты здесь}</Root>;
}

Ответ или решение

Проблема с авто-закрывающимися всплывающими окнами при использовании вложенной аутентификации приложений (NAA)

Ваша проблема с Nested App Authentication (NAA) и всплывающими окнами в браузере касается внедрения сторонних токенов для вашего надстройки Outlook. Разобраться в данной ситуации можно, проанализировав как поведение приложений в различных браузерах, так и методы реализации аутентификации в рамках вашей архитектуры.

Текущая ситуация

Вы успешно получаете действительный токен Graph API при использовании приложения в браузере Chrome. Проблема возникновения всплывающего окна, которое быстро закрывается, связана с тем, что в определённых ситуациях пользователь не может получить токен тихо (silently), что приводит к всплывающему окну аутентификации.

Важно отметить, что поведение, когда всплывающее окно автоматически закрывается, действительно может встречаться в браузерах, и оно может зависеть от управления сессиями и аутентификации в конкретном браузере. На это влияют такие факторы, как кэширование, наличие активного сеанса и предыдущие условия согласия на доступ.

Аутентификация и разрешения

Ваша проблема заключается в том, что при повторной аутентификации (логировании) пользователю необходимо повторно вводить свои данные. Это может быть связано с тем, что браузер очищает определенные сеансовые данные, что мешает молчаливой аутентификации. Ваша текущая конфигурация, вероятно, уже содержит все необходимые разрешения, но отсутствие активной учетной записи может приводить к необходимости повторного открытия окна.

Важное замечание: при использовании Firefox вы сталкиваетесь с двумя всплывающими окнами, а иногда и с зависанием одного из них. Это может быть связано с различиями в обработке JavaScript и анимации в этом браузере.

Стратегии для решения проблемы

  1. Проверка состояния аутентификации: Перед попыткой получения токена попытайтесь проверить состояние активной учетной записи, как у вас уже реализовано в коде:

    const account = msalInstance.getActiveAccount();
    if (!account) {
       throw Error("No active account found");
    }
  2. Изменение метода аутентификации: Рассмотрите возможность перехода на более надежные способы аутентификации, если восприятие пользователя по-прежнему остается негативным. Например, использование эгименов (refresh tokens) может улучшить пользовательский опыт.

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

    if (popupError.errorCode === "popup_window_error") {
       throw new Error("Popup blocked by browser. Please enable popups and try again.");
    }
  4. Тестирование различных браузеров: Проводите тестирование на различных браузерах, чтобы понять, как различные политики безопасности могут влиять на поведение приложения.

  5. Исключения для надстройки: В некоторых браузерах можно добавить исключения для обработки пользователей, что даст возможность им запускать вашу надстройку без блокировок всплывающих окон.

  6. Альтернативные способы аутентификации: Если проблемы продолжаются, возможно, следует рассмотреть переход на альтернатива NAA, как, например, OAuth 2.0 с явной аутентификацией, которая даст больший контроль над поведением и потоком аутентификации.

Заключение

Хотя использование NAA может привести к ряду трудностей в браузере, реализация вышеперечисленных стратегий, а также тщательная работа с ответами в кодовой базе могут помочь улучшить пользовательский опыт. Как всегда, важно проводить тестирование с конечными пользователями и принимать во внимание их отзывы для дальнейшей оптимизации работы вашего приложения.

Оцените материал
Добавить комментарий

Капча загружается...