SalesforceからMicrosoft Graph API – 外部指定ログイン情報を利用


以前「[SalesforceからMicrosoft Graph API を利用してみました](https://qiita.com/CodeWhiz_24/items/373c718259f0600501f4)」という記事を投稿させていただきました。ところが最近、別の Microsoft 365 環境とSalesforce 組織に同様の設定をしたらうまく動きません。Apex から Graph API を呼び出すと 403 エラーが返ってくる現象が発生してしまいました。結局根本的理由は分からないままなのですが、回避方法がありましたので、備忘録も兼ねて共有いたします。以下が変更の概要です：

* 「認証プロバイダ」の代わりに「外部指定ログイン情報」を利用
* 「（従来の）指定ログイン情報」の代わりに新しい「指定ログイン情報」を利用
* 権限セットとプロファイルでアクセス権を適用

Salesforce も、「従来の指定ログイン情報」ではなく、新しい指定ログイン情報を使うことを[推奨](https://help.salesforce.com/s/articleView?id=sf.named_credentials_legacy_about.htm&type=5)しているので、回避方法とはいえ正しい方向ではないかと思います。
以下前回の記事になぞり、全体の手順を追って説明します

## 1.	Microsoft365 にアプリケーションの登録
## 2.	Salesforce に Microsoft のサイトを登録
手順 1, 2 については前回と変更ありません。
## 3.	外部ログイン情報の登録
ここで以前は「認証プロバイダ」を登録していましたが、代わりに「外部ログイン情報」を利用します。Salesforce設定から「セキュリティ」「指定ログイン情報」を選択すると、リストの上部に「外部ログイン情報」のタブがありますので選択し、「新規」ボタンをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768107/dd609308-c8dd-ea01-6475-b15d69452933.png)
 
* 表示ラベル・名前は適当なもので大丈夫です
* 認証プロトコル: 「OAuth 2.0 」
* 認証フロー種別: 「クライアントの秘密によるクライアントログイン」
* 範囲: 「https://graph.microsoft.com/.default offline_access openid」
* IDプロバイダーURL: 「https://login.microsoftonline.com/<テナントID>/oauth2/v2.0/token」
<テナントID>は前回の記事を参照ください
* コールアウトオプション：なし

以上で一旦「保存します」。
中段に「プリンシパル」って欄がありますので、「新規」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768107/7e80d821-0fb6-73c1-52c2-6c5e4474f427.png)
 
* パラメータ名: ここでは「app」としておきます(なんでも大丈夫です)
* 連番: 「１」
* クライアントID:　&lt;Microsoft365アプリケーション登録のもの&gt;
* クライアントシークレット: &lt;Microsoft365アプリケーション登録のもの&gt;

以上で、保存します。

## 4.	指定ログイン情報の登録
Salesforce設定「セキュリティ」「指定ログイン情報」を再度選択し、「指定ログイン情報」のタブで「新規」をクリックします。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768107/4e76c1d3-d21c-2010-222f-cffca375898a.png)

* 表示ラベル：任意の名前で結構です
* 名前：Apex から利用される名前になります
* URL：「https://graph.microsoft.com/v1.0」
* コールアウトに対応：「ON」
* 外部ログイン情報：先ほど作成した外部ログイン情報を指定します
* クライアント証明書：空
* 認証ヘッダーを生成：「チェック」
* その他：空

以上で保存します。外部ログイン情報・指定ログイン情報の作成は終了です。認証プロバイダの代わりに、外部指定ログイン情報を使うようになった感じですが、コールアウトURLの設定など必要がなくなっています。

### 4-a. 権限セットとプロファイルでアクセス権を適用

外部指定ログイン情報では、アクセス権の設定という作業が必要になります。(先ほど入力したクライアントシークレットを含むプリンシパル情報にセキュリティがかかっており、そこへアクセスするための権限だと理解しています) 
Salesforce のユーザーに外部指定ログイン情報のアクセス権を与えるのですが、2通り方法があります
* プロファイルでアクセス権を追加
* 権限セットでアクセス権を追加

ただ、将来はプロファイルから権限セットに移行される予定とのことなので、今回は権限セットを利用することにしました。
Salesforce設定「ユーザー」「権限セット」を選択し、(適当な権限セットをお持ちでなければ)新規作成します。このとき、Salesforce のライセンス情報を1つ選ぶ必要があるので注意してください。このライセンス選択は後から変更することができません。また権限セットの割り当ては、選択した Salesforceライセンスのユーザーのみ指定可能になります。私の場合、管理者と一般ユーザーでライセンスが異なっていたため、権限セットは一般ユーザーのものを指定し、管理者はプロファイルによって別途アクセス権を与えることにしました。サンドボックスやスクラッチ組織も同様の配慮が必要です。
権限セットを用意したら、「外部ログイン情報プリンシパルアクセス」を選択し、先ほど作成した外部ログイン情報(のプリンシパル)を有効にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3768107/245988e4-cda8-a3e4-a0ee-5393133da805.png)

プロファイルからの場合は、プロファイル設定から「有効な外部ログイン情報プリンシパルアクセス」で同様の設定を行います。

## 5.	Salesforce Apex から Graph API の呼び出し
ここは基本代わりありません。以前の指定ログイン情報の名前の代わりに、新しい指定ログイン情報を callout で利用することを忘れないでください
~~~ apex
    private static String graphUrl = 'callout:MSGraphNamedCredential';
    public static HttpResponse callGraphApi(String method, String api, Map<String, String> header, String body){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint(graphUrl + api);
        req.setMethod(method);
        req.setHeader('Content-Type', 'application/json');
        req.setHeader('Accept', 'application/json');
        if (header != null){
            for (String key : header.keyset()){
                req.setHeader(key, header.get(key));
            }
        }
        if (body != null)
            req.setBody(body);
        HttpResponse res = h.send(req);
        return res;
    }
~~~
## 6.	SFDXと新しい組織作成時の手順
以下が前回に対して今回変更になったファイルです。権限セットのライセンスはデプロイする頻度の高いスクラッチ組織用のものに変更してチェックインしたほうがよいと思います。スクラッチ組織で利用できないライセンスが指定してあるとデプロイエラーになります
~~~ apex
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>GraphAPI</members>
        <name>ApexClass</name>
    </types>
    <types>
        <members>MSGraphExternalCredential</members>
        <name>ExternalCredential</name>
    </types>
    <types>
        <members>MSGraphNamedCredential</members>
        <name>NamedCredential</name>
    </types>
    <types>
        <members>UsersPermissionSet</members>
        <name>PermissionSet</name>
    </types>
</Package>
~~~

設定のおおよそはソースとして管理されますので、新規組織へデプロイ可能ですが、以下はソースに含まれないので、都度手動での設定が必要です：
* 外部ログイン情報プリンシパルのクライアントIDとクライアントシークレット
* 権限セットのメンバー割り当て。ただし、スクラッチ組織作成の場合はユーザーリストが異なりますので、権限セットではなくプロファイルからシステム管理者や必要なプロファイルに外部ログイン情報プリンシパルのアクセス権を付与したほうがよいかと思います

本日の記事は以上となります。ご意見ご感想アドバイスなど、コメントお待ちしております。