Java CLIアプリケーションからAzure ADB2Cにアクセスして認証する

Java CLIアプリケーション（not Webアプリケーション）からADB2Cにアクセスして認証する方法 3つ。

実装には[MSAL for Java](https://github.com/AzureAD/microsoft-authentication-library-for-java/)を使う。

[MSAL for .NET](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) を使えば.NETアプリやUnityからも同様に認証できるはず。

----

## 1. インタラクティブなトークン取得

認証には「OAuth 2.0 認可コード フロー + PKCE」が使用される。

MSAL4JがローカルホストでWebサーバーを動かして、それがADB2Cが連携することで、ブラウザーベースの認証が行われる。

認証するときにブラウザーが開かれ、ADB2Cのログイン画面が表示される。

ユーザーIDとパスワードを入力すると、ローカルホストのWebサーバーに認可コードが渡されるので、MSAL4JはトークンエンドポイントにアクセスしてIDトークンとアクセストークンを取得する。

### ADB2Cの設定

- ユーザーフロー
  - 「サインイン」か「サインアップとサインイン」を構成する
- アプリケーションの登録
  - アプリを登録する際に「パブリック クライアント フローを許可する」を有効にする
    - クライアントシークレットは作成しなくていい
  - プラットフォーム構成は「モバイル アプリケーションとデスクトップ アプリケーション」を選ぶとよい
    - リダイレクトURIは `http://localhost` とする（ポート番号無し、末尾スラッシュ無しでよい）
  - 「暗黙的な許可およびハイブリッド フロー」では、IDトークンとアクセストークンの両方にチェックを入れておく（たぶん必要）
  - APIの公開（スコープ）は、設定してもしなくてもいい
    - 設定しない場合も、Javaコードで指定するスコープに値を入れる必要がある（後述）
    - 設定する場合は、「APIの公開」で任意のスコープを登録し、「APIのアクセス許可」で、登録したスコープを許可する
      - アクセス許可の設定ページの一番下にある「エンタープライズ アプリケーション」へのリンクをたどり、「（ADB2C テナント名）に管理者の同意を与えます。」ボタンを押して、管理者として同意しておくこと

### Javaコード

https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Acquiring-Token-Interactively

- `com.microsoft.aad.msal4j.PublicClientApplication` を使用する
  - クライアントシークレットを使わない
- Authority は `https://<yourdomain>.b2clogin.com/<yourdomain>.onmicrosoft.com/<policyname>/` (サインインフローなど)
- `InteractiveRequestParameters` を引数にして `acquireToken` を呼び出す
- scopesには アプリのクライアントIDを指定するか、APIの公開で設定したスコープを指定する
  - MSAL4Jが必ずアクセストークンを要求するため、APIスコープを設定しない場合は回避策としてクライアントIDを渡す。 https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Known-Issues-and-Workarounds#b2c
  - APIスコープを設定している場合は、 `https://<yourdomain>.onmicrosoft.com/<application id>/<scope.name>` のような形式

----

## 2. プログラムからユーザーIDとパスワードを渡してトークンを取得

認証には「OAuth 2.0 リソース所有者パスワード資格情報フロー（ROPC）」が使用される。

ブラウザーは必要ないので便利だが、プログラムがユーザーのパスワードを直接扱うため、一般的にはリスクがあり推奨されない方法とされる。
(たとえば、悪意のあるOSSライブラリーが組み込まれたプログラムを起動してしまうと、実行ユーザーの権限で、メモリ上のユーザーIDとパスワードが盗まれ、外部に送信してしまうかもしれない。また、MFAを有効にできないので、盗まれたユーザーIDとパスワードが悪用されやすい。)

### ADB2Cの設定

https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/add-ropc-policy?tabs=app-reg-ga&pivots=b2c-user-flow

- ユーザーフロー
  - 「リソース所有者のパスワード資格情報 (ROPC) を使用してサインインする」を構成する
- アプリケーションの登録
  - アプリを登録する際に「パブリック クライアント フローを許可する」を有効にする
    - クライアントシークレットは作成しなくていい
  - プラットフォーム構成は必要ない
  - 暗黙のフローを有効にする必要があるが、プラットフォーム構成をせずに暗黙のフローを有効にするには、「マニフェスト」からオンラインエディターで `"oauth2AllowImplicitFlow": true,` と書き換えて保存する。
  - APIの公開（スコープ）は、設定してもしなくてもいい
    - 設定しない場合も、Javaコードで指定するスコープに値を入れる必要がある（後述）
    - 設定する場合は、「APIの公開」で任意のスコープを登録し、「APIのアクセス許可」で、登録したスコープを許可する
      - アクセス許可の設定ページの一番下にある「エンタープライズ アプリケーション」へのリンクをたどり、「（ADB2C テナント名）に管理者の同意を与えます。」ボタンを押して、管理者として同意しておくこと

### Javaコード

https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Acquiring-Tokens-with-username-and-password

- `com.microsoft.aad.msal4j.PublicClientApplication` を使用する
  - クライアントシークレットを使わない
- Authority は `https://<yourdomain>.b2clogin.com/<yourdomain>.onmicrosoft.com/<policyname>/` (ROPCフロー)
- `UserNamePasswordParameters` を引数にして `acquireToken` を呼び出す
- scopesには アプリのクライアントIDを指定するか、APIの公開で設定したスコープを指定する
  - MSAL4Jが必ずアクセストークンを要求するため、APIスコープを設定しない場合は回避策としてクライアントIDを渡す。 https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Known-Issues-and-Workarounds#b2c
  - APIスコープを設定している場合は、 `https://<yourdomain>.onmicrosoft.com/<application id>/<scope.name>` のような形式

----

## 3. ユーザーではなく、デーモンアプリとしてトークンを取得する

認証には「OAuth 2.0 クライアント資格情報フロー」が使用される。

クライアントIDとクライアントシークレットだけで認証し、ADB2Cに登録したアプリケーション自身のトークンを取得することになる。

IDトークンは得られず、アクセストークンだけが得られる。

トークンに含まれる sub/oidクレームの値は、アプリケーションのサービスプリンシパルのオブジェクト IDである。（「概要」の、「ローカル ディレクトリでのマネージド アプリケーション」から表示可能。）

### ADB2Cの設定

https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/client-credentials-grant-flow?pivots=b2c-user-flow

- ユーザーフロー
  - 「サインイン」か「サインアップとサインイン」を構成する
    - ユーザーのサインインを伴わないはずなので、別の構成でもよいかもしれない
- アプリケーションの登録
  - パブリック クライアント フローを許可しない
  - クライアントシークレットを追加しておく
  - プラットフォーム構成は必要ない
  - 暗黙的な許可の設定は必要ない
  - APIの公開（スコープ）は、設定してもしなくてもいい
    - 設定しない場合も、「APIの公開」でアプリケーション ID の URIを設定しておく（デフォルトはクライアントIDの値）
    - 設定する場合は、「APIの公開」で任意のスコープを登録し、「APIのアクセス許可」で、登録したスコープを許可する
      - アクセス許可の設定ページの一番下にある「エンタープライズ アプリケーション」へのリンクをたどり、「（ADB2C テナント名）に管理者の同意を与えます。」ボタンを押して、管理者として同意しておくこと

### Java コード

https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Client-Credentials

- `com.microsoft.aad.msal4j.ConfidentialClientApplication` を使用する
  - クライアントシークレットを使う
- Authority は `https://<yourdomain>.b2clogin.com/<yourdomain>.onmicrosoft.com/<policyname>/`
- `ClientCredentialParameters` を引数にして `acquireToken` を呼び出す
- scopesには APIの公開で設定したスコープを指定する
  - アプリケーション ID URIだけ設定している場合は、 `https://<yourdomain>.onmicrosoft.com/<application id>/.default` 
  - APIスコープを設定している場合は、 `https://<yourdomain>.onmicrosoft.com/<application id>/<scope.name>` のような形式