pythonのmsalでConfidentialClientApplicationとPublicClientApplicationの違い、PublicClientApplicationの簡単な実装例

# はじめに
この記事ではConfidential/Public Client Applicationの違いについて簡単に説明した後で、msalでPublicClientApplicationの簡単な設定例をお伝えしようと思います。
実はPublic Client Applicationの設定する際に、なかなかうまくいかなかったので、同じように理解に苦しんだり、つまづいている方の助けになれば幸いと思い記事にさせていただきました。

# Confidential Client Application と Public Client Application の違い

MSAL における Public Client Application と Confidential Client Application のどちらを選ぶかは、アプリケーションの**運用環境**や求められる**セキュリティ要件**によって決まります。

- **Public Client Application** は、ユーザーのデバイス上で直接認証が行われるアプリケーション向けです。実装はシンプルで、モバイル・デスクトップ・SPA などユーザー向けのアプリケーションに最適ですが、バックグラウンド処理やサーバー側の認証には制約があります。

- **Confidential Client Application** は、サーバー内で安全に機密情報を保持し、ユーザーの介入なしでトークンを取得できるため、セキュリティ面や自動処理が要求されるサーバーサイドのアプリケーション（ウェブアプリケーション、Web API、サービス/デーモン）向けとなります。ただし、実装や運用がやや複雑になる点に注意が必要です。

## 1. 基本的な定義

### Public Client Application

- **概要**:  
  Public Client Application はデスクトップ、モバイル、シングルページアプリケーション（SPA）など、ユーザーのデバイス上で動作するアプリケーションです。
  ユーザーが直接操作する環境で実行されるため、ソースコードやバイナリから機密情報を隠すことが難しく、**クライアントシークレットや証明書などの機密情報を保持しません**。

- **主な認証フロー**:  
  ユーザーのインタラクティブなログインや、Device Code Flow のようにユーザーの操作を伴う認証に対応しています。直接ユーザーの資格情報を使ってトークンを取得し、Web API などにアクセスする用途に向いています。

ログイン機能を備えた一般的なアプリケーションで使われると考えるのが分かりやすいかもです。

### Confidential Client Application

- **概要**:  
  Confidential Client Application は主にサーバーサイドで実行されるウェブアプリケーション、Web API、サービス/デーモン（バックエンドプロセス）などが該当します。これらのアプリケーションは、安全なサーバー環境内で実行されるため、**クライアントシークレットや証明書**といった機密情報を安全に管理でき、アプリケーション自身の身元を証明することが可能です。

- **主な認証フロー**:  
  ユーザーの介入がなくても、クライアントクレデンシャルフロー（Client Credentials Flow）や認可コードグラントフローなどを用いて、バックグラウンドでトークンを取得することができます。

ユーザーによるログインのないアプリケーションですね。アプリケーション自体に証明書を持っていてリソースを使用することができます。
そんなのあるの？と思うかもしれませんが、世の中のアプリケーションはユーザーに紐づけて利用されるものだけではありません。

[Microsoft Learn - Public and confidential client apps (MSAL)](https://learn.microsoft.com/ja-jp/entra/identity-platform/msal-client-applications)  

---

## 2. 機能の比較

以下の表は、Public Client と Confidential Client の主要な機能や特性を比較したものです。

| 比較項目                | Public Client Application                                             | Confidential Client Application                                          |
|-------------------------|----------------------------------------------------------------------|--------------------------------------------------------------------------|
| **展開環境**            | デスクトップ、モバイル、シングルページアプリ（SPA）、ブラウザベース      | ウェブアプリケーション、Web API、サービス/デーモンアプリ                    |
| **機密情報の保持**       | クライアントシークレットや証明書を持たず、外部に漏らすリスクが低い       | サーバー上で安心して保持可能。アプリの身元を確実に証明するための情報がある      |
| **認証フロー**          | ユーザーのインタラクティブログインや Device Code Flow 等                | クライアントクレデンシャルフロー、認可コードグラントフローなど、様々なフローに対応 |
| **セキュリティの観点**    | ユーザーのデバイス上で実行されるため、設計上機密情報をもたせないことで安全性を確保  | サーバー内で安全に管理できるため、より高いセキュリティレベルを実現可能           |
| **実装の複雑さ**        | 実装がシンプルで、コード中に秘密情報を含める必要がない                   | 機密情報の管理や証明書の設定など、設定や開発がやや複雑になる                   |

---

## 3. メリット・デメリット

### Public Client Application

**メリット**
- **シンプルな実装**:  
  機密情報（クライアントシークレットなど）を管理する必要がないため、実装が比較的簡単です。  
- **利用環境が柔軟**:  
  ユーザーデバイス上で直接動作するため、モバイルアプリやデスクトップアプリ、ブラウザでの利用に適しています。  
- **セキュリティリスクが限定的**:  
  秘密情報を持たないため、ソースコードやアプリケーションが漏れても大きな影響を及ぼさないケースがほとんどです。

**デメリット**
- **認証フローの限界**:  
  ユーザー認証を前提とするため、ユーザーの介入が必要となり、バックグラウンド処理や自動認証が難しい場合があります。  
- **サーバーサイド処理には不向き**:  
  セキュリティ上、サーバー側で機密情報を保持できないため、サーバー間の通信や自動処理にはあまり適用できません。

---

### Confidential Client Application

**メリット**
- **高いセキュリティ**:  
  サーバー環境で実行されるため、クライアントシークレットや証明書などの機密情報を安全に保管し、厳格な認証を行うことができます。  
- **自動化やバックグラウンド処理に最適**:  
  ユーザーの介入なしに認証が行えるため、サービスやデーモン（バックエンドプロセス）での利用に適しています。  
- **柔軟な認証フロー**:  
  クライアントクレデンシャルフローなど、多様な認証方法を利用できるため、用途に応じた高度な認証処理が可能です。

**デメリット**
- **実装の複雑さ**:  
  機密情報の安全な管理が求められるため、設定や運用が複雑になります。  
- **運用環境の制約**:  
  サーバー側でのみ運用が可能なため、ユーザーのローカルデバイス上での認証処理やシンプルなクライアント用途には不向きです。

---


# Public Client Applicationの実装例
Confidential Client Application は以前の記事で[msalでアクセストークンを取得](https://qiita.com/mkuwan/items/31172145285fc42c4dbe#msal%E3%81%A7%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%8F%96%E5%BE%97)を書いているので参考にしてください。
今回はPulic Client Applicationです。


## PublicClientApplication
以下が簡単なソースコードです

```python
import msal

CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
TENANT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"

# 利用する API のスコープを指定
SCOPES = ["User.Read"]

# PublicClientApplication のインスタンスを作成
app = msal.PublicClientApplication(client_id=CLIENT_ID, authority=AUTHORITY)
```

クライアントIDとテナントIDはどこで取得するのでしょうか？

答えは[Azure Portal](https://portal.azure.com/)です

## アプリの登録
azureポータルからアプリの登録を行います。
はじめてazureポータルを触った時は、アプリの登録という概念自体が分からす、頭が混乱したものです。

スマホアプリやウェブサイト、デスクトップアプリを作るとします。
そのアプリでユーザーが Microsoft のアカウントでログインできるようにしたい場合、Azure AD がその「身元」を保障し、ログイン情報の管理を行います。
しかし、まず Azure AD にそのアプリがどんなアプリなのかを伝える必要があります。これが「アプリの登録」です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/3a8443e5-506b-4403-b7f1-b8e12b222f94.png)

アプリの登録をすると、このアプリケーションID(クライアントID)を使用して、Azureのリソースを使用することができるようになります。
テナントIDはこのAzureを管理しているIDです。テナント内の〇〇というアプリケーションですという証明になるわけです。

しかしこれだけ証明するのは安全ではないので、そこに認証を追加します。
このとき、アプリケーション自体にシークレットキーを付与して認証するものと、アプリケーションを利用するユーザーによる認証のいずれかを使用するわけです。

さて設定をすると以下のようにクライアントIDとテナントをIDを取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/10b10dc0-3c47-4ab7-b344-c6e890d2a798.png)

## 実装？
さてこれでさきほどのコードを実装してみましょう

```python
import msal

CLIENT_ID = "取得したクライアントID"
TENANT_ID = "取得したテナントID"
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"

# 利用する API のスコープを指定
SCOPES = ["User.Read"]

# PublicClientApplication のインスタンスを作成
app = msal.PublicClientApplication(client_id=CLIENT_ID, authority=AUTHORITY)

# キャッシュ内に保存されたアカウントがあればサイレントにトークン取得を試みる
accounts = app.get_accounts()
if accounts:
    result = app.acquire_token_silent(scopes=SCOPES, account=accounts[0])
else:
    result = None
    # キャッシュにトークンがない、もしくはサイレント取得に失敗した場合はインタラクティブにログイン
    print("インタラクティブなログインが必要です。ブラウザが起動しますので、ログインしてください。")
    result = app.acquire_token_interactive(scopes=SCOPES)

    # print("initiate_flow、ログインしてください。")
    # flow = app.initiate_device_flow(scopes=SCOPES)
    # result = app.acquire_token_by_device_flow(flow)

pprint(f"取得した結果: {result}")

# 結果の判定
if "access_token" in result:
    print("アクセストークンの取得に成功しました:")
    print(result["access_token"])
else:
    print("アクセストークンの取得に失敗しました:")
    print("エラー:", result.get("error"))
    print("詳細:", result.get("error_description"))
```

このようにログインできていると認証できたという表示がwebブラウザに表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/b78be585-45ac-4377-8994-eefa4ded26f6.png)

ところが、コンソールでは以下のようなエラーになってしまいました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/125dbc05-51ca-4264-b956-ac7f0bfe7edd.png)

clinent_secretを使用しないようにするためのPublicClientApplicationなのに、client_secretが必要？本当か？となるわけです。
実際に似たような疑問をもって質問しているのをStack OverFlowなどでも見かけました。

## 何が悪かったのか？
パブリッククライアントフローが「はい」になっていてもうまくいかなかったのですが。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/015f46e7-07e4-46ce-9b4d-babd1ed39e24.png)

いろいろと検索した結果分かりました。
リダイレクト設定でした。
モバイルアプリケーションとデスクトップアプリケーションを選択して、リダイレクトにhttp : //localhostを入力しましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/85433e84-f241-4e9d-a93c-56a75d77fd2b.png)
ここにhttp : //localhostを入力しましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/8c00aca1-d804-491e-bc82-59a21b4562f7.png)

## 再度実行
こんな感じでトークン取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/956246/09490cfb-5444-4e03-912e-bb46f3439786.png)

できてしまえばあっという間です。しかしここまでたどり着くのでいろいろと調べました。
そして今回の回答を得たのは以下からでした。

https://learn.microsoft.com/en-us/answers/questions/2242738/fix-error-aadsts7000218-when-trying-to-get-authent

ということで同じように？？？となっている人がいたらお試しください。