BoxWorks 2024でコンテンツとAIの可能性について紹介します。

詳細を表示

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

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

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

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

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

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

スケルトンの設定

ローカルアプリケーションディレクトリで、手順1で作成したserver.jsファイルを読み込みます。

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

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統合のコネクタの構成に使用されます。

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

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

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

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

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

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

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

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を以下の内容に置き換えます。

@RequestMapping("/")
String home(@AuthenticationPrincipal OidcUser user) throws IOException {
  // TODO: HANDLE ROUTE
}

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

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

public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}

ローカルアプリケーションディレクトリで、手順1で作成したserver.pyファイルを読み込みます。

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

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のユーザー情報 (存在する場合) をキャプチャします。

# Fetch Okta user record if logged in
@app.before_request
def before_request():
  # TODO: HANDLE BEFORE REQUEST

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

# 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ルート内のコードが実行されます。

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

@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という新しいファイルを作成します。これは、サインインリンクがクリックされたときに実行されるコントローラになります。

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

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ルーティングメソッドにリダイレクトされます。

前の手順が完了していません

最初に、手順1でお好みの言語/フレームワークを選択してください。

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

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

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

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ログインを強制します。

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

// Validate OIDC user against Box
return validateUser(user);

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

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

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を以下のコードに置き換えます。

return redirect(url_for(".box_auth"))

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

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

box = Box()
return box.validateUser(g)

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

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

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ユーザーを検出します。

前の手順が完了していません

最初に、手順1でお好みの言語/フレームワークを選択してください。

まとめ

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