> ## Documentation Index
> Fetch the complete documentation index at: https://developer.box.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Box App Userの検索または作成

export const ProgressBar = ({pages = [], ...props}) => {
  const [currentStep, setCurrentStep] = useState(0);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const checkDarkMode = () => {
      const isDark = document.documentElement.classList.contains('dark');
      console.log('ProgressBar - isDarkMode:', isDark);
      setIsDarkMode(isDark);
    };
    checkDarkMode();
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => {
      observer.disconnect();
    };
  }, []);
  useEffect(() => {
    if (pages.length > 0) {
      const currentPath = window.location.pathname;
      const stepIndex = pages.findIndex(page => {
        const pagePath = page.startsWith('/') ? page : `/${page}`;
        return currentPath.endsWith(pagePath) || currentPath.includes(pagePath);
      });
      if (stepIndex !== -1) {
        setCurrentStep(stepIndex + 1);
      }
    }
  }, [pages]);
  if (!pages || pages.length === 0) {
    return null;
  }
  const step = currentStep;
  const total = pages.length;
  console.log('ProgressBar - Rendering with isDarkMode:', isDarkMode);
  const progressBarContainerStyle = {
    width: '100%',
    marginBottom: '32px',
    display: 'flex',
    alignItems: 'center',
    gap: '16px'
  };
  const stepsContainerStyle = {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    flexShrink: 0
  };
  const progressBarTrackStyle = {
    flex: 1,
    height: '22px',
    backgroundColor: 'rgba(169, 210, 244, 0.06)',
    border: isDarkMode ? '1px solid rgba(230, 241, 247, 0.67)' : '1px solid #e3ecf3',
    borderRadius: '4px',
    overflow: 'hidden',
    position: 'relative'
  };
  const progressBarFillStyle = {
    height: '100%',
    backgroundColor: 'rgba(113, 192, 248, 0.23)',
    width: `${step / total * 100}%`,
    transition: 'width 0.3s ease'
  };
  const getStepStyle = (stepNumber, isActive) => {
    if (isDarkMode) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: isActive ? 'rgba(113, 192, 248, 0.23)' : 'transparent',
        color: isActive ? '#60a5fa' : '#a0aec0',
        border: isActive ? '1px solid #e3ecf3' : '1px solid #e0e6eb',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    if (isActive) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: 'rgba(169, 210, 244, 0.32)',
        color: '#374151',
        border: '1px solid #e1eef8',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    return {
      width: '22px',
      height: '22px',
      borderRadius: '4px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      fontSize: '12px',
      fontWeight: '600',
      position: 'relative',
      zIndex: 1,
      transition: 'all 0.3s ease',
      backgroundColor: '#fbfbfb',
      color: '#9ca3af',
      border: '1px solid #e3ecf3',
      cursor: 'pointer',
      textDecoration: 'none'
    };
  };
  return <div style={progressBarContainerStyle} {...props}>
      <div style={stepsContainerStyle}>
        {Array.from({
    length: total
  }, (_, index) => {
    const stepNumber = index + 1;
    const pageIndex = index;
    const pagePath = pages[pageIndex];
    const fullPath = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
    const isActive = stepNumber === step;
    return <a key={stepNumber} href={fullPath} style={getStepStyle(stepNumber, isActive)}>
              {stepNumber}
            </a>;
  })}
      </div>
      <div style={progressBarTrackStyle}>
        <div style={progressBarFillStyle}></div>
      </div>
    </div>;
};

export const Link = ({href, children, className, ...props}) => {
  const localizedHref = localizeLink(href);
  return <a href={localizedHref} className={className} {...props}>
      {children}
    </a>;
};

export const ChoiceDebug = ({option}) => {
  const [currentValue, setCurrentValue] = useState(null);
  const [allState, setAllState] = useState({});
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateState = () => {
      if (window.choiceStateManager) {
        setCurrentValue(window.choiceStateManager.getValue(option));
        setAllState(window.choiceStateManager.getState());
      }
    };
    updateState();
    const unsubscribe = window.listenToChoice?.(option, updateState) || (() => {});
    const handleGlobalUpdate = () => updateState();
    window.addEventListener("choiceStateUpdate", handleGlobalUpdate);
    return () => {
      unsubscribe();
      window.removeEventListener("choiceStateUpdate", handleGlobalUpdate);
    };
  }, [option]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  return <div style={{
    padding: "10px",
    backgroundColor: isDarkMode ? "#2d3748" : "#f5f5f5",
    border: isDarkMode ? "1px solid #4a5568" : "1px solid #ddd",
    borderRadius: "4px",
    fontSize: "12px",
    fontFamily: "monospace",
    marginTop: "20px",
    color: isDarkMode ? "#e2e8f0" : "inherit"
  }}>
      <strong>{translate("Choice Debug:")}</strong>
      <br />
      {translate("Option:")} {option}
      <br />
      {translate("Current Value:")} {currentValue || "undefined"}
      <br />
      {translate("All State:")} {JSON.stringify(allState, null, 2)}
    </div>;
};

export const Observe = ({option, value, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const matches = window.matchesChoiceValues?.(option, value) || false;
      setShouldShow(matches);
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value]);
  if (!shouldShow) {
    return null;
  }
  return <div {...props}>{children}</div>;
};

export const Trigger = ({option, value, children, ...props}) => {
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  return <div onClick={handleClick} style={{
    cursor: "pointer"
  }} {...props}>
      {children}
    </div>;
};

export const Grid = ({columns = 2, compact = false, children, ...props}) => {
  const gridStyles = {
    display: "grid",
    gridTemplateColumns: `repeat(${columns}, 1fr)`,
    gap: compact ? "8px" : "16px",
    marginBottom: compact ? "10px" : "20px"
  };
  return <div style={gridStyles} {...props}>
      {children}
    </div>;
};

export const Choice = ({option, value, color = "", unset = false, lazy = false, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  const [hasEverShown, setHasEverShown] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const hasOptionValue = window.hasChoiceValue?.(option) || false;
      const matchesValue = window.matchesChoiceValues?.(option, value) || false;
      let show = false;
      if (unset && !hasOptionValue) {
        show = true;
      } else if (!unset && matchesValue) {
        show = true;
      }
      setShouldShow(show);
      if (show && !hasEverShown) {
        setHasEverShown(true);
      }
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value, unset, hasEverShown]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      padding: "20px",
      marginBottom: "20px",
      borderRadius: "8px",
      backgroundColor: isDarkMode ? "#1a202c" : "#ffffff"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      },
      none: {
        backgroundColor: "transparent",
        padding: "0",
        margin: "0",
        border: "none"
      }
    };
    const colorStyles = color !== "none" ? colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({}) : colorMap.none;
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  if (lazy && !hasEverShown && !shouldShow) {
    return null;
  }
  return <div style={{
    ...getColorStyles(),
    display: shouldShow ? "block" : "none"
  }} className="choice-content" {...props}>
      {children}
    </div>;
};

export const Choose = ({option, value, color = "", children, ...props}) => {
  const [isSelected, setIsSelected] = useState(false);
  const [hasOptionTriggered, setHasOptionTriggered] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const currentValue = window.getChoiceValue?.(option);
    const optionTriggered = window.hasChoiceValue?.(option) || false;
    setIsSelected(currentValue === value);
    setHasOptionTriggered(optionTriggered);
    const unsubscribe = window.listenToChoice?.(option, newValue => {
      setIsSelected(newValue === value);
      setHasOptionTriggered(true);
    }) || (() => {});
    return unsubscribe;
  }, [option, value]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  const handleKeyDown = e => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      handleClick();
    }
  };
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      cursor: "pointer",
      padding: "20px",
      position: "relative",
      backgroundColor: isDarkMode ? "#2d3748" : "#f8f9fa",
      outline: "none",
      height: "100%",
      borderRadius: "8px",
      transition: "all 0.2s ease",
      display: "flex",
      flexDirection: "column"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      }
    };
    const colorStyles = colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({});
    if (isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        borderStyle: "solid",
        borderWidth: "3px",
        borderColor: colorStyles.borderColor || (isDarkMode ? "#42a5f5" : "#0061d5"),
        backgroundColor: colorStyles.backgroundColor || (isDarkMode ? "#1a2a3a" : "#e3f2fd"),
        boxShadow: isDarkMode ? "0 2px 8px rgba(66, 165, 245, 0.3)" : "0 2px 8px rgba(0, 97, 213, 0.3)",
        transform: "scale(1.02)"
      };
    }
    if (hasOptionTriggered && !isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        opacity: 0.5
      };
    }
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  const iconStyles = {
    float: "left",
    position: "relative",
    top: "2px",
    marginRight: "12px",
    width: "20px",
    height: "20px",
    color: isSelected ? isDarkMode ? "#42a5f5" : "#0061d5" : isDarkMode ? "#a0aec0" : "#666"
  };
  return <div onClick={handleClick} style={getColorStyles()} tabIndex={0} onKeyDown={handleKeyDown} {...props}>
      <div style={iconStyles}>
        {isSelected ? <svg viewBox="0 0 24 24" fill="currentColor" style={{
    width: "100%",
    height: "100%"
  }}>
            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
          </svg> : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{
    width: "100%",
    height: "100%"
  }}>
            <circle cx="12" cy="12" r="10" />
          </svg>}
      </div>
      <div style={{
    flex: 1
  }} className="choose-content">
        {children}
      </div>
    </div>;
};

<ProgressBar
  pages={[
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/scaffold-application-code",
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/configure-okta",
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/configure-box",
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/logging-into-app",
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/find-or-create-box-users",
                    "guides/sso-identities-and-app-users/connect-okta-to-app-users/run-the-app"
                  ]}
/>

この時点で、アプリケーションコードが作成されています。このコードでは、ユーザーアクセスからのトラフィックを処理し、これらをログインのためにOktaに転送して、Oktaのユーザー情報を提供した後、最終的にはBoxのハンドラ (現時点で未作成) に渡します。

このセクションでは、以下のようにBoxの最終的なコンポーネントを取り上げます。

* Oktaユーザーに、関連付けられたBox App Userアカウントがあるかどうかを検証します。
* (関連付けられているアカウントがない場合は) 関連付けられているOktaレコードに新しいApp Userを作成します。
* Boxユーザーのトークンを取得してユーザー固有のAPIコールを実行します。

## Platform App Userの作成

ユーザーを検証する前に、関連付けられたBoxユーザーアカウントがOktaユーザーにない場合のために、そのアカウントを作成する方法が必要です。

<Choice option="programming.platform" value="node" color="none">
  ローカルアプリケーションディレクトリで、手順1で作成した`server.js`ファイルを読み込みます。

  次の`box`オブジェクトをファイルに追加し、保存します。

  ```js theme={null}
  const box = (() => {
      const configJSON = JSON.parse(fs.readFileSync(path.resolve(__dirname, './config.json')));
      const sdk = boxSDK.getPreconfiguredInstance(configJSON);
      const client = sdk.getAppAuthClient('enterprise');

      let oktaRecord = {};
      let userId = '';
      let userClient;

      function validateUser(userInfo, res) {
          // TODO: VALIDATE USER
      }

      function createUser(res) {
          // TODO: CREATE USER
      }

      return {
          validateUser,
          createUser
      };
  })();
  ```

  このオブジェクトはいくつかの項目を定義します:

  * 構成: Box Node SDKの新しいインスタンスがインスタンス化され、多数の変数とともにオブジェクト関数で使用可能になります。
  * `validateUser`関数: 関連付けられたOktaユーザーにBoxユーザーが存在するかどうかを検証するためのコードを保持します。
  * `createUser`関数: 関連付けられたOktaユーザーIDにバインドされる新しいBoxユーザーを作成します。

  この構造を定義したら、`// TODO: CREATE USER`セクションを以下のコードに置き換えます。

  ```js theme={null}
  const spaceAmount = 1073741824;   // ~ 1gb

  client.enterprise.addAppUser(
      this.oktaRecord.name,
      {
        space_amount: spaceAmount,
        external_app_user_id: this.oktaRecord.sub
      }
  ).then(appUser => {
      res.send(`New user created: ${appUser.name}`);
  });
  ```

  このコードにより、新しいBox App Userが作成され、ユーザーオブジェクトの`external_app_user_id`パラメータが一意のOktaユーザーIDに設定されます。これで、2つのユーザーレコード間のバインドが定義されます。
</Choice>

<Choice option="programming.platform" value="java" color="none">
  ローカルアプリケーションディレクトリで、手順1で作成した`/src/main/java/com/box/sample/Application.java`ファイルを読み込みます。別のアプリケーション名を使用している場合は、同等のディレクトリを読み込みます。

  `public class Application`の定義内に、以下のメソッドを追加します。

  ```java theme={null}
  static String validateUser(OidcUser user) throws IOException {
      // TODO: VALIDATE USER
  }

  static String createUser(OidcUser user) {
      // TODO: CREATE USER
  }
  ```

  これらのメソッドはBoxユーザーの検証と作成を処理します。各メソッドの詳細は以下のとおりです。

  * `validateUser`: 関連付けられたOktaユーザーにBoxユーザーが存在するかどうかを検証するためのコードを保持します。
  * `createUser`: 関連付けられたOktaユーザーIDにバインドされる新しいBoxユーザーを作成します。

  これらのメソッドを定義したら、`# TODO: CREATE USER`を以下のコードに置き換えます。

  ```java theme={null}
  String oktaName = (String) user.getAttributes().get("name");
  Object oktaSub = user.getAttributes().get("sub");

  CreateUserParams params = new CreateUserParams();
  params.setExternalAppUserId((String) oktaSub);
  BoxUser.Info createdUserInfo = BoxUser.createAppUser(api, oktaName, params);

  return "New User Created: " + createdUserInfo.getName();
  ```

  このコードにより、新しいBox App Userが作成され、ユーザーオブジェクトの`external_app_user_id`パラメータが一意のOktaユーザーIDに設定されます。これで、2つのユーザーレコード間のバインドが定義されます。
</Choice>

<Choice option="programming.platform" value="python" color="none">
  ローカルアプリケーションディレクトリで、手順1で作成した`server.py`ファイルを読み込みます。

  既存のコードのルート定義の下に、以下の`Box`クラスオブジェクトを追加します。

  ```python theme={null}
  # Box user class
  class Box(object):
      def __init__(self):
          # Instantiate Box Client instance
          auth = JWTAuth.from_settings_file('../config.json')
          self.box_client = Client(auth)

      # Validate if Box user exists
      def validateUser(self, g):
          # TODO: VALIDATE USER

      # Create new Box user
      def createUser(self, ouser):
          # TODO: CREATE USER
  ```

  このクラスで定義する内容は以下のとおりです。

  * `init`: 初期化時に、Box Python SDKの新しいインスタンスがインスタンス化され、オブジェクトのメソッドで使用可能になります。
  * `validateUser`メソッド: ユーザーオブジェクトを入力として受け取り、関連付けられたOktaユーザーにBoxユーザーが存在するかどうかを検証するためのコードを保持します。
  * `createUser`メソッド: ユーザーオブジェクトを入力として受け取り、関連付けられたOktaユーザーIDにバインドされる新しいBoxユーザーを作成します。

  このクラスを定義したら、`# TODO: CREATE USER`セクションを以下のコードに置き換えます。

  ```python theme={null}
  user_name = f'{ouser.profile.firstName} {ouser.profile.lastName}'
  uid = ouser.id
  space = 1073741824

  user = self.box_client.create_user(user_name, None, space_amount=space, external_app_user_id=uid)
  return f'New user created: {user_name}'
  ```

  このコードにより、新しいBox App Userが作成され、ユーザーオブジェクトの`external_app_user_id`パラメータが一意のOktaユーザーIDに設定されます。これで、2つのユーザーレコード間のバインドが定義されます。
</Choice>

<Choice option="programming.platform" value="cs" color="none">
  `Controllers` > `AccountController.cs`ファイル内で、関連付けられた`AccountController`クラスの中に以下のメソッドを追加します。

  ```csharp theme={null}
  static async Task validateUser(string name, string sub)
  {
      // Configure Box SDK instance
      var reader = new StreamReader("config.json");
      var json = reader.ReadToEnd();
      var config = BoxConfig.CreateFromJsonString(json);
      var sdk = new BoxJWTAuth(config);
      var token = sdk.AdminToken();
      BoxClient client = sdk.AdminClient(token);

      // Search for matching Box app user for Okta ID
      BoxCollection<BoxUser> users = await client.UsersManager.GetEnterpriseUsersAsync(externalAppUserId:sub);
      System.Diagnostics.Debug.WriteLine(users.TotalCount);

      if (users.TotalCount > 0)
      {
           // TODO: VALIDATE USER
      }
      else
      {
          // TODO: CREATE USER
      }
  }
  ```

  このコードブロック内では、手順2でダウンロードした`config.json`ファイルを使用して新しいBox .NET SDKクライアントが作成されます。このコードサンプルの場合、その`config.json`ファイルはローカルアプリケーションディレクトリのルートに格納されます。

  その後、Oktaの一意のID `sub`が`externalAppUserId`検索パラメータとして渡され、クライアントを使用して、Boxに登録されている会社内のすべてのユーザーが検索されます。その後、返されるユーザーの数を確認すると、有効なユーザーが検出されたかどうかがわかります。

  この構造を定義したら、// TODO: CREATE USERセクションを以下のコードに置き換えます。

  ```csharp theme={null}
  var userRequest = new BoxUserRequest()
  {
      Name = name,
      ExternalAppUserId = sub,
      IsPlatformAccessOnly = true
  };
  var user = await client.UsersManager.CreateEnterpriseUserAsync(userRequest);
  System.Diagnostics.Debug.WriteLine("New user created: " + user.Name);
  ```

  このコードにより、新しいBox App Userが作成され、ユーザーオブジェクトの`external_app_user_id`パラメータが一意のOktaユーザーIDに設定されます。これで、2つのユーザーレコード間のバインドが定義されます。

  その後、新しいユーザーが作成されたことを示す診断メッセージが書き戻されます。
</Choice>

<Choice option="programming.platform" unset color="none">
  <Danger>
    **前の手順が完了していません**最初に、手順1でお好みの言語/フレームワークを選択してください。
  </Danger>
</Choice>

## Oktaユーザーの検証

ここまで、ユーザーを作成する機能を定義してきました。次に定義するコードでは、Boxを使用する会社の全ユーザーを検索して関連付けられた`external_app_user_id`を探すことで、Oktaユーザーレコードに関連付けられたBoxユーザーレコードが存在するかどうかを検証します。

<Choice option="programming.platform" value="node" color="none">
  `// TODO: VALIDATE USER`コメントを以下の内容に置き換えます。

  ```js theme={null}
  this.oktaRecord = userInfo

  client.enterprise.getUsers({ "external_app_user_id": this.oktaRecord.sub })
      .then((result) => {
          if (result.total_count > 0) {
              // TODO: MAKE AUTHENTICATED USER CALL
          } else {
              // User not found - create user
              this.createUser();
          }
      });
  ```

  Box Node SDKを使用した場合、`enterprise.getUsers`を呼び出して会社の全ユーザーを検索し、一意のOktaユーザーIDを`external_app_user_id`値として渡すことで、そのユーザーに特化した検索を行います。

  見つかった場合 (つまり、レコードの数が1以上だった場合) は、そのユーザーレコードを使用して、Box APIに対して認証済みの呼び出しを実行できます。これは次のセクションで定義します。

  見つからなかった場合は、1つ前のセクションで定義した`createUser`関数を呼び出して、その`external_app_user_id`と関連付けられた新しいBoxユーザーを作成します。
</Choice>

<Choice option="programming.platform" value="java" color="none">
  `// TODO: VALIDATE USER`コメントを以下の内容に置き換えます。

  ```java theme={null}
  // Set up Box enterprise client
  Reader reader = new FileReader("config.json");
  BoxConfig config = BoxConfig.readFrom(reader);
  api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(config);

  // Get Okta user sub for unique ID attachment to Box user
  Object oktaSub = user.getAttributes().get("sub");

  // Check enterprise users for matching external_app_user_id against Okta sub
  URL url = new URL("https://api.box.com/2.0/users?external_app_user_id=" + oktaSub);
  BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
  BoxJSONResponse jsonResponse = (BoxJSONResponse) request.send();
  JsonObject jsonObj = jsonResponse.getJsonObject();
  JsonValue totalCount = jsonObj.get("total_count");

  // Set return string
  String outputString = "";

  if (totalCount.asInt() > 0) {
      // TODO: MAKE AUTHENTICATED USER CALL
  } else {
      outputString = createUser(user);
  }

  return outputString;
  ```

  Box Java SDKの汎用リクエストメソッドを使用した場合、`https://api.box.com/2.0/users`エンドポイントを直接呼び出して会社のユーザーを検索し、一意のOktaユーザーIDを`external_app_user_id`値として渡すことで、そのユーザーに特化した検索を行います。

  見つかった場合 (つまり、レコードの数が1以上だった場合) は、そのユーザーレコードを使用して、Box APIに対して認証済みの呼び出しを実行できます。これは次のセクションで定義します。

  見つからなかった場合は、1つ前のセクションで定義した`createUser`関数を呼び出して、その`external_app_user_id`と関連付けられた新しいBoxユーザーを作成します。
</Choice>

<Choice option="programming.platform" value="python" color="none">
  `# TODO: VALIDATE USER`コメントを以下の内容に置き換えます。

  ```python theme={null}
  # Fetch Okta user ID
  uid = g.user.id

  # Validate is user exists
  url = f'https://api.box.com/2.0/users?external_app_user_id={uid}'
  response = self.box_client.make_request('GET', url)
  user_info = response.json()

  # If user not found, create user, otherwise fetch user token
  if (user_info['total_count'] == 0):
      self.createUser(g.user)
  else:
      # TODO: MAKE AUTHENTICATED USER CALL
  ```

  Box Python SDKの汎用リクエストメソッドを使用した場合、`https://api.box.com/2.0/users`エンドポイントを直接呼び出して会社のユーザーを検索し、一意のOktaユーザーIDを`external_app_user_id`値として渡すことで、そのユーザーに特化した検索を行います。

  見つかった場合 (つまり、レコードの数が1以上だった場合) は、そのユーザーレコードを使用して、Box APIに対して認証済みの呼び出しを実行できます。これは次のセクションで定義します。

  見つからなかった場合は、1つ前のセクションで定義した`createUser`関数を呼び出して、その`external_app_user_id`と関連付けられた新しいBoxユーザーを作成します。
</Choice>

<Choice option="programming.platform" value="cs" color="none">
  `// TODO: VALIDATE USER`コメントを以下の内容に置き換えます。

  ```csharp theme={null}
  var userId = users.Entries[0].Id;
  var userToken = sdk.UserToken(userId);
  BoxClient userClient = sdk.UserClient(userToken, userId);

  // TODO: MAKE AUTHENTICATED USER CALL
  ```

  有効なユーザーが見つかった場合、Box IDが抽出されます。このIDは、アプリケーションではなく明確にそのユーザーのスコープに設定されたBox SDKクライアントの生成に使用されます。
</Choice>

<Choice option="programming.platform" unset color="none">
  <Danger>
    **前の手順が完了していません**最初に、手順1でお好みの言語/フレームワークを選択してください。
  </Danger>
</Choice>

## 認証済みのBoxユーザーの呼び出し

Oktaユーザーの関連付けられたBoxユーザーが検出されたら、明確に<Link href="/guides/authentication/jwt/user-access-tokens">そのユーザーのスコープに設定された</Link>アクセストークンを生成してBox APIコールを実行します。その後、現在のユーザーを取得するための呼び出しを実行して、すべてが機能していることと有効なユーザーアクセストークンがあることを確認します。

<Choice option="programming.platform" value="node" color="none">
  1つ前のセクションの`// TODO: MAKE AUTHENTICATED USER CALL`を次の内容に置き換えます。

  ```js theme={null}
  this.userId = result.entries[0].id;
  this.userClient = sdk.getAppAuthClient('user', this.userId);

  this.userClient.users.get(this.userClient.CURRENT_USER_ID)
      .then(currentUser => {
          res.send(`Hello ${currentUser.name}`);
      });
  ```

  見つかったユーザーのBoxユーザーIDをキャプチャし、そのユーザーのスコープに設定されたユーザークライアントオブジェクトを生成します。最後に、このユーザークライアントオブジェクトを使用して現在のユーザーを取得する呼び出しを実行すると、Oktaに関連付けられたBox App Userのユーザープロフィール情報が返されます。
</Choice>

<Choice option="programming.platform" value="java" color="none">
  1つ前のセクションの`// TODO: MAKE AUTHENTICATED USER CALL`を次の内容に置き換えます。

  ```java theme={null}
  // User found, authenticate as user
  // Fetch user ID
  JsonArray entries = (JsonArray) jsonObj.get("entries");
  JsonObject userRecord = (JsonObject) entries.get(0);
  JsonValue userId = userRecord.get("id");

  // Get user scoped access token and fetch current user with it
  BoxDeveloperEditionAPIConnection userApi = BoxDeveloperEditionAPIConnection.getAppUserConnection(userId.asString(), config);
  BoxUser currentUser = BoxUser.getCurrentUser(userApi);
  BoxUser.Info currentUserInfo = currentUser.getInfo();

  outputString = "Hello " + currentUserInfo.getName();
  ```

  見つかったユーザーのBoxユーザーIDをキャプチャし、そのユーザーのスコープに設定されたユーザークライアントオブジェクトを生成します。最後に、このユーザークライアントオブジェクトを使用して現在のユーザーを取得する呼び出しを実行すると、Oktaに関連付けられたBox App Userのユーザープロフィール情報が返されます。
</Choice>

<Choice option="programming.platform" value="python" color="none">
  1つ前のセクションの`# TODO: MAKE AUTHENTICATED USER CALL`を次の内容に置き換えます。

  ```python theme={null}
  # Create user client based on discovered user
  user = user_info['entries'][0]
  user_to_impersonate = self.box_client.user(user_id=user['id'])
  user_client = self.box_client.as_user(user_to_impersonate)

  # Get current user
  current_user = user_client.user().get()
  return f'Hello {current_user.name}'
  ```

  見つかったユーザーのBoxユーザーIDをキャプチャし、そのユーザーのスコープに設定されたユーザークライアントオブジェクトを生成します。最後に、このユーザークライアントオブジェクトを使用して現在のユーザーを取得する呼び出しを実行すると、Oktaに関連付けられたBox App Userのユーザープロフィール情報が返されます。
</Choice>

<Choice option="programming.platform" value="cs" color="none">
  1つ前のセクションの`// TODO: MAKE AUTHENTICATED USER CALL`を次の内容に置き換えます。

  ```csharp theme={null}
  BoxUser currentUser = await userClient.UsersManager.GetCurrentUserInformationAsync();
  System.Diagnostics.Debug.WriteLine("Current user name: " + currentUser.Name);
  ```

  このユーザーのスコープに設定されたクライアントを使用すると、Boxから現在のユーザーレコードが抽出され、現在のユーザー名を含む診断メッセージが書き戻されます。
</Choice>

<Choice option="programming.platform" unset color="none">
  <Danger>
    **前の手順が完了していません**最初に、手順1でお好みの言語/フレームワークを選択してください。
  </Danger>
</Choice>

## まとめ

* OktaユーザーがBoxユーザーとして存在するかどうかを検証しました。
* 存在しない場合は新しいApp Userを作成しました。
* 既存のBoxユーザーに対してBox APIコールを実行しました。

<Observe option="box.app_type" value="use_own,create_new_">
  <Next>Boxユーザーの検証と作成を設定しました</Next>
</Observe>
