LoginSignup
2
1

【Entra ID】SharePoint APIを実行するためのaccessTokenを取得する

Posted at

やりたいこと

REST APIで、人の手を介さず、SharePoint Onlineのファイルやフォルダを操作したい。

上のリファレンスを読むに、以下リクエストのaccessTokenの値を取得すれば良さそう。

GET https://{site_url}/_api/web/GetFolderByServerRelativeUrl('/Shared Documents')
Authorization: "Bearer " + accessToken
Accept: "application/json;odata=verbose"

……このアクセストークンの取得に思いのほか手間取ったので、備忘録として残します。

注意
SharePoint API accessToken等で検索すると、Azure ACSを使った記事が多くヒットします。
具体的には、以下URLのように、SharePointのAppRegNew.aspxでクライアントIDやクライアントシークレットを登録し、SharePoint上で直接アクセストークンを発行する方法です。
https://ホスト名.sharepoint.com/XXXXX/_layouts/15/AppRegNew.aspx

この方法は、Azure ACS(Access Control Service)を使いますが、Azure ACSは廃止予定のため、推奨しません。

As part of this evolution of Microsoft 365 solutions we will be retiring the use of Azure ACS (Access Control Services) for SharePoint Online auth needs and believe Microsoft 365 customers will be better served by modern auth offered via Microsoft Entra ID. Azure ACS will stop working for new tenants as of November 1st, 2024 and it will stop working for existing tenants and will be fully retired as of April 2nd, 2026.
https://learn.microsoft.com/ja-jp/sharepoint/dev/sp-add-ins/retirement-announcement-for-azure-acs

やること(概要)

Azure ACSではなく、Microsoftが推奨しているEntra ID (旧Azure AD)を使った方法でアクセストークンを取得する。
そのために……

  1. Entra IDの検証環境を用意する。(既にある場合は不要)
  2. Entra IDにSharePointアクセストークン発行用のアプリを登録する
  3. SharePointのアクセストークン取得時に必要な証明書を準備する(理由は後述)
  4. 証明書を使用し、SharePointのアクセストークンを取得する
  5. 取得したアクセストークンで、SharePoint APIを実行する

1. Entra ID環境の用意(既にある場合は不要)

Microsoft 365 開発者プログラムに参加すると、無料・クレカ登録無しで、Entra ID含むMicrosoft365の環境が手に入ります。超便利。

登録後に、以下URLにアクセスすると、Entra IDが利用できます。
https://portal.azure.com/
ライセンスは、Microsoft Entra ID P2です。
image.png

参考:
https://www.softbanktech.co.jp/special/blog/cloud_blog/2022/0120/

2. Entra IDにSharePointアクセストークン発行用のアプリを登録する

① Entra IDの左メニュー「アプリの登録」を選択し、左上の「+新規登録」をクリックする。
image.png

② アプリの名前を適当に付けて「登録」をクリックする。(リダイレクトURIは省略)
image.png

アプリケーション(クライアント)IDと、ディレクトリ(テナント)IDを記録しておく。
以下では見切れていますが、アプリケーション(クライアント…が、アプリケーション(クライアント)IDです。
image.png

④「APIのアクセス許可」を選択し、「アクセス許可の追加」をクリックする。
image.png
SharePointを選択し、
image.png
以下を設定し「アクセス許可の追加」をクリックする。
(今回はフル権限を与えているが、場合によって適した権限にする)

アプリケーションに必要なアクセス許可の種類 -> アプリケーションの許可
アクセス許可を選択 -> Sites.FullControl.All, TermStore.ReadWrite.All, User.ReadWrite.All

「XXXに管理者の同意を与えます」をクリックして、「はい」を押下する。
image.png
image.png

上の「状態」が「XXXに付与されました」の緑のチェックマークになっていればOKです。

3. SharePointのアクセストークン取得時に必要な証明書を準備する

なぜ、証明書?
通常、アクセストークンの取得は、クライアントIDとクライアントシークレットの認証で行われることが多いです。
しかし、クライアントIDとクライアントシークレットの認証でSharePoint API用のアクセストークンを取得すると、SharePoint API実行時に、以下のようなエラーが返ってきます。

Unsupported app only token.

どうも、SharePointに関しては、クライアントシークレットではなく、X.509の証明書を用いた認証でアクセストークンを取得しないと、APIが使えないようになっているようです。
参考:
https://blog.mastykarz.nl/azure-ad-app-only-access-token-using-certificate-dotnet-core

① 以下手順を参考に、自己署名証明書を準備する。

PowerShellで以下を実行し、パスワード付の証明書を作成します。(version 7で実行)

# XXXXXには、任意の証明書名やパスワードをセットしてください。
#
# 証明書生成
$certname = "XXXXX"
$cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256
Export-Certificate -Cert $cert -FilePath "C:\work\$certname.cer"   ## Specify your preferred location

$mypwd = ConvertTo-SecureString -String "XXXXX" -Force -AsPlainTextt-PfxCertificate -Cert $cert -FilePath "C:work\$certname.pfx" -Password $mypwd   ## Specify your preferred location

② Entra IDに証明書を登録する。

Entra IDの「アプリの登録」から、SharePoint用に登録したアプリを選択し、右上の「証明書またはシークレットの追加」をクリックします。
image.png
「証明書のアップロード」を選択し、
image.png
②で作成した.cerファイルをアップロードし「追加」をクリックします。
image.png

証明書が登録されていればOKです。
image.png

4. 証明書を使用し、SharePointのアクセストークンを取得する

以下の「2番目のケース:証明書を使ったアクセストークン要求」に証明書を使用したアクセストークン取得方法が記載されています。

アクセストークン取得リクエストには、以下のパラメータが必要と記載されています。

パラメーター 説明
tenant d27abxxx-xxxx-xxxx-xxxx-xxxxxxxxxx 上の2.⑤のディレクトリ(テナント)ID
client_id 067cbxxx-xxxx-xxxx-xxxx-xxxxxxxxxx 上の2.⑤のアプリケーション(クライアント)ID
scope https://<ホスト名>.sharepoint.com/.default ホスト名は、自分のMicrosoft 365環境の値
client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer 固定値
client_assertion 証明書から生成したJWT 後述
grant_type client_credentials 固定値

……え? client_assertionにJWT? アクセストークン(JWT)を取得するためのリクエストに、さらにJWTが必要?と不可解な気もしますが、必要なようです。

① 証明書からJWTを生成する
Microsoftが提供するC#のライブラリで恐縮ですが……

NuGetパッケージマネージャーから、以下4つのパッケージをインストールして、さらに下のC#ファイルを実行します。

  1. Microsoft.IdentityModel.Abstractions
  2. Microsoft.IdentityModel.JsonWebTokens
  3. Microsoft.IdentityModel.Logging
  4. Microsoft.IdentityModel.Tokens
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace GetJwtToken
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string ClientID = "上の2.⑤のアプリケーション(クライアント)ID";
            string CertPassWord = "上で作成した証明書のパスワード";
            string aud = "https://login.microsoftonline.com/上の2.⑤のディレクトリ(テナント)ID/v2.0/";
            // 上で作成した証明書の.pfxファイルを指定
            string CertificatePath_Pfx = "C:\\work\\cert.pfx";

            X509Certificate2 cert = new X509Certificate2(CertificatePath_Pfx, CertPassWord);
            var claims = new System.Collections.Generic.Dictionary<String, Object>();
            claims["aud"] = aud;
            claims["iss"] = ClientID;
            claims["sub"] = ClientID;
            claims["jti"] = Guid.NewGuid().ToString("D");

            var signCred = new Microsoft.IdentityModel.Tokens.X509SigningCredentials(cert);
            var securityTokenDescriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor();
            securityTokenDescriptor.Claims = claims;
            securityTokenDescriptor.SigningCredentials = signCred;

            Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler tokenHandler = new Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler();
            var clientAssertion = tokenHandler.CreateToken(securityTokenDescriptor);
            Console.Write(clientAssertion);
            Console.ReadKey();

        }
    }
}

(探せば、PowerShellやシェルスクリプトで実行できるものもあるとは思いますが、力尽きて……)

正常に実行完了すると以下のようなJWTがコンソールに出力されます。

eyJhbGciOiJSUzI1NiIsImtpZC[...]XLeLvTe9yude0_ePDlaj8WJ4ZCuDA

② 上で取得したJWTをセットから、SharePointのアクセストークンを取得する

################################################
# Entra_ID側の環境変数
#
# 対象Microsoft 365のホスト名
$host_name = "Microsofrt 365のホスト名"
# Entra_IDのテナントID
$tenant_id = "上の2.⑤のディレクトリ(テナント)ID"
# Entra_IDのアプリケーションID
$client_id = "上の2.⑤のアプリケーション(クライアント)ID"


###############################################
# 固定変数
#
# grant_type
$grant_type = "client_credentials"
# スコープ(今回はsharepoint)
$scope = "https://" + $host_name + ".sharepoint.com/.default"

$client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
$client_assertion = "上で生成したJWT"

#################################################
# アクセストークン取得関数
function getAccessToken{
    $headers=@{}
    $headers.Add("accept", "application/json")
    $headers.Add("content-type", "application/x-www-form-urlencoded")
    $get_url = 'https://login.microsoftonline.com/' + $tenant_id + '/oauth2/v2.0/token'
    $body = 'grant_type=' + $grant_type + '&client_id=' + $client_id + '&client_assertion_type =' + $client_assertion_type + '' + '&client_assertion=' + $client_assertion + '&scope=' + $scope 

    try {
        $response = Invoke-WebRequest -Uri $get_url -Method POST -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body
    }catch{
        Write-Host "StatusCode:" $_
        exit
    }
    $response_json = ConvertFrom-Json $response
    return $response_json.access_token
}

# アクセストークン取得(有効期限は1時間)
$access_token = getAccessToken
Write-Host $access_token

正常に実行完了すると以下のようなアクセストークンがコンソールに出力されます。これで、SharePoint APIが実行できます。

eyJ0eXAiOiJKV1Q[...]5vhTUh14dwYV3tGp1SZLK1e8PvQwSzPqxUG7CVw

5. 取得したアクセストークンで、SharePoint APIを実行する

上で取得したアクセストークンを使い、SharePointのapitestというサイトのtestフォルダのtest_document.txtの内容を取得するAPIが実行できました。

curl -i -X GET \
   -H "Authorization:Bearer eyJ0eXAiOiJKV1Q[...]5vhTUh14dwYV3tGp1SZLK1e8PvQwSzPqxUG7CVw" \
   -H "Accept:application/json;odata=verbose" \
   -H "Content-Type:application/json; odata=verbose" \
   'https://ホスト名.sharepoint.com/sites/apitest/_api/web/GetFolderByServerRelativeUrl('test')/Files('test_document.txt')/$value'

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1