> ## 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.

# Oktaによるアプリへのログイン

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 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、Box、基本のアプリケーションを設定したら、次は、アプリケーションコードフローにおける最初のステップ、Oktaログインに目を向けます。

Oktaログインでは、ログインのためにユーザーをOktaにリダイレクトする際に使用される言語のOpenID Connect (OIDC) フレームワークを使用して、Oktaのユーザー情報をアプリケーションに返します。こうしたOktaのユーザー情報は、次の手順でBoxユーザーの検証と作成に使用されます。

このセクションでは、以下の操作について説明します。

* アプリケーション構成のスケルトンを設定します。
* ユーザートラフィックを処理するために選択したフレームワークのルートを定義します。
* Oktaのユーザー情報を、Boxユーザーを検証する次の手順に渡します。

## スケルトンの設定

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

  まず、以下のパッケージ定義と構成情報をファイルにコピーします。

  ```js theme={null}
  const session = require('express-session');
  const { ExpressOIDC } = require('@okta/oidc-middleware');
  const bodyParser = require('body-parser');
  const boxSDK = require('box-node-sdk');
  const config = require('./config.js');
  const express = require('express')();
  const http = require('http');
  const path = require('path');
  const fs = require('fs');

  express.use(session({
      secret: 'this should be secure',
      resave: true,
      saveUninitialized: false
  }));

  const oidc = new ExpressOIDC({
      issuer: `https://${config.oktaOrgUrl}/oauth2/default`,
      client_id: config.oktaClientId,
      client_secret: config.oktaClientSecret,
      appBaseUrl: config.oktaBaseUrl,
      loginRedirectUri: `${config.oktaBaseUrl}${config.oktaRedirect}`,
      scope: 'openid profile'
  });

  express.use(oidc.router);
  express.use(bodyParser.json());
  express.use(bodyParser.urlencoded({
      extended: true
  }));
  ```

  これにより、Express構成とOkta OIDCコネクタの情報が設定されます。ExpressはOIDCコネクタを使用するよう設定され、このクイックスタートガイドの手順2で保存したOktaの情報はOkta統合のコネクタの構成に使用されます。

  次に、ルーティングの詳細を追加します。

  ```js theme={null}
  // Redirect to Okta login
  express.get('/', (req, res) => {
      // TODO: HANDLE ROUTE
  });
  ```

  これにより、アプリケーションのエントリルートが定義されます。ユーザーがアプリケーションのルート (`/`) にアクセスを試みると、このルート内のコードが実行されます。

  最後に、Expressサーバーの初期化を追加してトラフィックをリッスンします。

  ```js theme={null}
  // Create server
  const port = process.env.PORT || 3000;
  http.createServer(express).listen(port, () => {
      console.log(`Server started: Listening on port ${port}`);
  });
  ```
</Choice>

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

  以下の基本的なアプリケーション構造をファイルにコピーします。

  ```java theme={null}
  package com.box.okta.sample;

  import java.io.FileReader;
  import java.io.IOException;
  import java.io.Reader;
  import java.net.URL;

  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.security.core.annotation.AuthenticationPrincipal;
  import org.springframework.security.oauth2.core.oidc.user.OidcUser;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RestController;

  import com.box.sdk.BoxAPIRequest;
  import com.box.sdk.BoxConfig;
  import com.box.sdk.BoxDeveloperEditionAPIConnection;
  import com.box.sdk.BoxJSONResponse;
  import com.box.sdk.BoxUser;
  import com.box.sdk.CreateUserParams;
  import com.eclipsesource.json.JsonArray;
  import com.eclipsesource.json.JsonObject;
  import com.eclipsesource.json.JsonValue;

  @RestController
  @EnableAutoConfiguration
  public class Application {
      static BoxDeveloperEditionAPIConnection api;

      // TODO: SET ROUTE

      // TODO: INITIALIZE SERVER
  }
  ```

  これにより、必要なimport、`Application`クラス、標準的な共有Box APIの接続属性が設定されます。これらは次の手順で定義します。

  `// TODO: SET ROUTE`を以下の内容に置き換えます。

  ```java theme={null}
  @RequestMapping("/")
  String home(@AuthenticationPrincipal OidcUser user) throws IOException {
    // TODO: HANDLE ROUTE
  }
  ```

  このルートマッピングではアプリケーションのエントリルートを定義します。ユーザーがログアウトした状態でアプリケーションのルート (`/`) にアクセスを試みると、OIDCコネクタによってユーザーは自動的にOktaログインにプッシュされるため、リダイレクトを設定する必要がありません。ユーザーがログイン状態の場合は、このルート内のコードが実行されます。

  `// TODO: INITIALIZE SERVER`を以下の内容に置き換えて、Spring Bootサーバーを初期化してトラフィックをリッスンします。

  ```java theme={null}
  public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
  }
  ```
</Choice>

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

  以下の基本的なアプリケーション構造をファイルにコピーします。

  ```python theme={null}
  from flask import Flask, redirect, g, url_for
  from flask_oidc import OpenIDConnect
  from okta import UsersClient
  from boxsdk import Client
  from boxsdk import JWTAuth
  import requests
  import config
  import json

  app = Flask(__name__)
  app.config.update({
      'SECRET_KEY': config.okta_client_secret,
      'OIDC_CLIENT_SECRETS': './client_secrets.json',
      'OIDC_DEBUG': True,
      'OIDC_ID_TOKEN_COOKIE_SECURE': False,
      'OIDC_SCOPES': ["openid", "profile"],
      'OIDC_CALLBACK_ROUTE': config.okta_callback_route
  })

  oidc = OpenIDConnect(app)
  okta_client = UsersClient(config.okta_org_url, config.okta_auth_token)
  ```

  これにより、Flask構成、Oktaクライアント、Okta OIDCコネクタの情報が設定されます。FlaskはOIDCコネクタを使用するよう設定され、このクイックスタートガイドの手順2で保存したOktaの情報はOkta統合のコネクタの構成に使用されます。

  次に、ルート処理が行われる前に実行する`before_request`定義を追加します。ここでは、この定義を使用してOktaのユーザー情報 (存在する場合) をキャプチャします。

  ```python theme={null}
  # Fetch Okta user record if logged in
  @app.before_request
  def before_request():
    # TODO: HANDLE BEFORE REQUEST
  ```

  最後に、アプリケーションのエントリルートと`box_auth`ルートを定義します。

  ```python theme={null}
  # Main application route
  @app.route('/')
  def start():
    # TODO: HANDLE MAIN ROUTE

  # Box user verification
  @app.route("/box_auth")
  @oidc.require_login
  def box_auth():
    # TODO: HANDLE BOX AUTH ROUTE

  return 'Complete'
  ```

  ユーザーがアプリケーションのルート (`/`) にアクセスを試みると、このルート内のコードが実行されます。Oktaユーザーを検証する際は、`box_auth`ルート内のコードが実行されます。
</Choice>

<Choice option="programming.platform" value="cs" color="none">
  ローカルアプリケーションで、`Views` > `Shared` > `Layout.cshtml`を読み込みます。これは、ASP.NETアプリケーションが読み込まれるとレンダリングされるビジュアルコンポーネントになります。ページの先頭に以下のコードを挿入します。

  ```csharp theme={null}
  @using System.Security.Claims;

  @if (User.Identity.IsAuthenticated)
  {
      <p class="navbar-text">Hello, @User.Identity.Name</p>
  }
  else
  {
      <a asp-controller="Account" asp-action="SignIn">Sign In</a>
  }
  ```

  Oktaにログインしているユーザーがアクセスすると、Helloというメッセージが表示されます。ログインしていない場合は、サインインリンクが表示されます。

  `<a asp-controller="Account" asp-action="SignIn">Sign In</a>`という行に含まれる`asp-controller="Account"`は、作成予定のAccountコントローラでリクエストを処理することを意味します。また、`asp-action="SignIn"`は、このコントローラの`SignIn`メソッドが実行されることを意味します。このファイルを保存して閉じます。

  `Controllers`ディレクトリ内に、`AccountController.cs`という新しいファイルを作成します。これは、サインインリンクがクリックされたときに実行されるコントローラになります。

  以下の基本的なアプリケーション構造をファイルにコピーします。

  ```csharp theme={null}
  using System.IO;
  using System.Linq;
  using System.Threading.Tasks;
  using Microsoft.AspNetCore.Mvc;
  using Microsoft.AspNetCore.Authorization;
  using Okta.AspNetCore;
  using Box.V2;
  using Box.V2.Config;
  using Box.V2.JWTAuth;
  using Box.V2.Models;

  public class AccountController : Controller
  {
      public IActionResult SignIn()
      {
          if (!HttpContext.User.Identity.IsAuthenticated)
          {
              return Challenge(OktaDefaults.MvcAuthenticationScheme);
          }

          return RedirectToAction("Profile", "Account");
      }

      [Authorize]
      [Route("~/profile")]
      public IActionResult Profile()
      {
          // TODO: HANDLE ROUTE
      }
  }
  ```

  ユーザーがサインインリンクをクリックすると、このコントローラの`SignIn`メソッドが実行されます。ユーザーがまだ認証されていない場合は`Challenge`に送信され、ユーザーはログインするためにOktaにリダイレクトされます。この機能はルーティングフレームワークによって処理されるため、追加で実行するコードは必要ありません。ユーザーが認証済みの場合は`Profile`ルーティングメソッドにリダイレクトされます。
</Choice>

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

## アプリケーションルートの設定

今度は、メインのルート (`/`) に入ったときに実行されるコードを定義する必要があります。

<Choice option="programming.platform" value="node" color="none">
  メインのルート内の`// TODO: HANDLE ROUTE`を以下のコードに置き換えます。

  ```js theme={null}
  if (req.userContext && req.userContext.userinfo) {
      const tokenSet = req.userContext.tokens;
      const userInfo = req.userContext.userinfo;

      // If Okta ID is present, pass to Box user validation
      if (userInfo.sub) {
          box.validateUser(userInfo, res);
      } else {
          console.log('No Okta ID identified');
      }
  } else {
      res.redirect('/login');
  }
  ```

  上記のコードでは、まず、OIDCコネクタから入手できるOktaのユーザー情報があるかどうかを確認しています。ユーザーがログインすると、このコネクタにより、Oktaのユーザーと構成の情報が`req.userContext`内のルートで使用できるようになります。

  ユーザー情報が存在する場合、つまりユーザーがOktaにログインしている場合は、ユーザー情報をExpressのレスポンスオブジェクトとともに`box.validateUser`に渡して、関連付けられたBoxユーザーが存在するかどうかを確認します。これは次の手順で定義します。

  ユーザー情報が存在しない場合、ユーザーは`/login`にリダイレクトされます。OIDCコネクタは自動的にこのルートを処理し、そのユーザーにOktaログインを強制します。
</Choice>

<Choice option="programming.platform" value="java" color="none">
  メインのルート内の`// TODO: HANDLE MAIN ROUTE`を以下のコードに置き換えます。

  ```java theme={null}
  // Validate OIDC user against Box
  return validateUser(user);
  ```

  Java OIDCコネクタは、手間のかかる部分のほとんどを代わりに処理してくれます。ログアウトしたユーザーがこのルートにアクセスすると、自動的にOktaログインにプッシュされます。ログインすると、OIDCユーザーオブジェクトがこのルートに使用できるようになります。そのユーザーオブジェクトは、次の手順で定義する`validateUser`関数に渡します。
</Choice>

<Choice option="programming.platform" value="python" color="none">
  メインのルート内の`// TODO: HANDLE BEFORE REQUEST`を以下のコードに置き換えます。

  ```python theme={null}
  if oidc.user_loggedin:
      g.user = okta_client.get_user(oidc.user_getfield('sub'))
  else:
      g.user = None
  ```

  これにより、OIDCユーザーが存在するかどうか、つまりユーザーがすでにOktaにログインしているかどうかが確認されます。存在する場合は、Oktaクライアントオブジェクトを使用してユーザーオブジェクトを設定し、存在しない場合はユーザーオブジェクトを`None`に設定します。

  次に、メインのルート内の`// TODO: HANDLE ROUTE`を以下のコードに置き換えます。

  ```python theme={null}
      return redirect(url_for(".box_auth"))
  ```

  このコードに入ると、ユーザーはOktaにログインしていない場合に、ログインするためにOIDCコネクタによってOktaにリダイレクトされます。ログイン後 (またはユーザーがすでにログインしている場合) は、`box_auth`ルートコードに転送されます。

  最後に、`box_auth`ルート内の`// TODO: HANDLE BOX AUTH ROUTE`を以下のコードに置き換えます。

  ```python theme={null}
      box = Box()
      return box.validateUser(g)
  ```

  これにより、Oktaユーザーオブジェクトが渡されることで、Boxクラスの新しいインスタンスが作成され、`validateUser`メソッドが呼び出されます。このクラスとメソッドは次の手順で定義します。
</Choice>

<Choice option="programming.platform" value="cs" color="none">
  メインのルート内の`// TODO: HANDLE ROUTE`を以下のコードに置き換えます。

  ```csharp theme={null}
  var subClaim = HttpContext.User.Claims.First(c => c.Type == "sub");
  var sub = subClaim.Value;

  var nameClaim = HttpContext.User.Claims.First(c => c.Type == "name");
  var name = nameClaim.Value;

  Task userSearch = validateUser(name, sub);

  Task.WaitAll(userSearch);

  return Content(name);
  ```

  このブロックでは、Oktaユーザーアカウントのsub (一意のID) とnameをキャプチャし、次の手順で作成する`validateUser`メソッドにそのデータを送信して、一致するBoxユーザーを検出します。
</Choice>

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

## まとめ

* Oktaのスケルトンルートと構成を設定しました。
* Boxユーザーの確認に渡すメインのルートハンドラを設定しました。

<Observe option="box.app_type" value="use_own,create_new_">
  <Next>Oktaログインを設定しました</Next>
</Observe>
