アクセストークンが必要なWebAPIへのPowerShellでのアクセス方法

#はじめに
　WebAPIの利用する際にWebAPIのアクセスに対するセキュリティを担保するために、アクセストークンを利用します。本ドキュメントでは、アクセストークンが必要なWebAPIにアクセスするために必要なアクセストークンをAADより入手する方法と、WebAPIにアクセストークンをセットして呼び出す方法に関して記述しています。

#アクセストークンの取得
アクセストークンをAAD（Azure Active Directory）より取得します。PowerShellで通常はバッチ処理などを作成すると思います。普通のアプリケーションの場合、ユーザーの認証をAADで行い、そのユーザーのアクセストークンを利用して各種リソースにアクセスしています。PowerShellのバッチの場合、このユーザーが存在しません。このため、PowerShellの場合、AADのアプリケーションを作成してもらい、そのアプリケーションのアプリケーションIDとシークレットキー（パスワードみたいなもの）をつかって、アクセストークンを利用して、リソースアプリケーション（WebAPIを提供するアプリケーション）にアクセスを行います。
　このため、PowerShellからアクセスを行う場合、あらかじめ下記の情報を管理者より入手しておきます。

表1-1　事前に入手する情報

|項目|内容|備考|
|:---|:---|:--|
|テナントID|認証を行うテナントIDです。今回は、シングルテナントを想定しています。||
|アプリケーションID|PowerShellがアクセストークンを取得するために作成したアプリケーションのID||
|シークレットキー|パスワードみたいなもの ||
|スコープID|利用するリソースアプリケーションのスコープIDを取得します。※１||

※１　アプリケーションを作成する際に、利用するリソースアプリケーションのスコープ、アプリケーションロールに作成したアプリケーション「API許可」を行っておく必要があります。APIの許可と必要な場合、管理者の承認を実施しておいてもらいます。

##アクセストークンの入手
```powershell
#アクセストークンを取得するURLを入力します。
$path = "https://login.microsoftonline.com/{TenantId}/oauth2/token"
```
```powershell
#ヘッタの情報（これは固定値）
$contentType = 'application/x-www-form-urlencoded'

#Bodyの情報を作ります。
$body = @{grant_type='client_credentials'
    scope='{スコープ名}/.default'
    client_id='クライアントID'
    client_secret='シークレットキー'
}
```
アクセストークンを取得するためにRestAPIを呼び出します。

```powershell
#アクセストークンの取得（RestAPIを呼び出します。）
$Result = Invoke-WebRequest -Method POST -Uri $path -body $body -ContentType $contentType
```
アクセス結果よりトークンを取り出します。

```powershell
#アクセストークンの取り出し
$Content = ConvertFrom-Json $Result.Content
$AccessToken = $Content.access_token
```
　アクセストークンは、Base64でコンバートされたJSONデーターです。このため、取得したクライアント側でも内容を見ることができます。（Ver2のトークン）

　取得したアクセストークンは、下記URLで見ることができます。
　https://jwt.ms/

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526427/63293871-aaaf-9075-8cff-c7bac710a093.png)

##WebAPIの呼び出し
取得したアクセストークンを利用して、WebAPIを呼び出します。　APIを呼び出すためには、APIのインタフェース仕様書が必要です。
　今回は、テスト用に動作させている、ローカルマシンにアクセスしています。

```powershell
#アクセスするアプリケーションのURLを入力
$apiurl = "https://localhost:44363/api/Values"

#アクセストークンを設定します。
$header = @{
    "Authorization" = "Bearer $AccessToken"
    "Content-Type" = "application/json"
}
```
WebAPIを呼び出します。

```powershell
#APIを呼び出します。
$Response = Invoke-WebRequest -Method Get -Uri $apiurl -Headers $header
```
今回は、Getアクセスなので、URIにパラメーター等が設定します。
もし、アクセストークンの有効期限などが切れている場合、Invoke-WebRequestコマンドは、エラーを出します。エラーが発生した場合、$Responseに値は、入りません。

```powershell
#PowerShellのエラーメッセージ
PS C:\work> $Response = Invoke-WebRequest -Method Get -Uri $apiurl -Headers $header
Invoke-WebRequest : {"Message":"この要求に対して承認が拒否されました。"}
発生場所 行:1 文字:13
+ $Response = Invoke-WebRequest -Method Get -Uri $apiurl -Headers $head ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
```

##実際の利用時の注意
通常のPowerShellの場合、１時間も２時間も処理をすることは少ないと思います。上記で取得したベアラートークンは、発行から１時間で有効期限が切れます。このため、長い処理を行う可能性がある場合、トークンの時間切れに対応する必要があります。
　しかしながら、WebAPIを呼び出すたびにアクセストークンを取得するコードは、お勧めしません。

だめな例

```powershell
$oDataAllay  | % {
	#アクセストークンの取得

	#WebAPIの呼び出し
}
```
このように、呼び出すたびにアクセストークンを取得するコードは、自身のパフォーマンスとAADへの負荷を考えると適切ではありません。
少なくともこのような処理の場合、ループの外側で１回アクセストークンを取得するべきです。
そこで、以下に取得したアクセストークンから、アクセストークンの有効期限（切れる日付）を取得する方法を記載します。

```powershell
$BreakToken = $AccessToken.Split(".")
$byte = [System.Convert]::FromBase64String($BreakToken[1])
$Tokenpayroad = ConvertFrom-Json([System.Text.Encoding]::Default.GetString($byte))

#expDay Get
$Secound =  $Tokenpayroad.exp
$ExpDate = ([datetime]"1970/1/1").AddSeconds($Secound)
```
上記コードで、$ExpDateにこのアクセストークンの有効期限を取得することができます。注意事項として、このアクセストークンの有効期限は、UTC時間なので、動作しているマシンのタイムゾーンを意識するか、「(Get-Date).ToUniversalTime()」を利用して現在時間を取得してアクセストークンの有効期限を比較してください。