Azure AD との OIDC / OAuth 2.0 連携時の簡易的な検証のやり方

本記事は [Microsoft Azure Tech Advent Calendar 2022](https://qiita.com/advent-calendar/2022/microsoft-azure-tech) 11 日目の記事です。

# はじめに
初めまして。 

年齢のせいか勉強してもすぐに忘れてしまうことが多くなってきたなぁ、、、と痛感している今日この頃です。 
こういうのはあまり長続きしたことはないのですが、自分自身へのメモの意味も込めて Qiita に記事を残していこうかな…と思い立ちました。 

どういう内容の記事にしようか考えていたのですが、栄えある初回の投稿は、現在自分が専門としている Azure AD とアプリケーションに関連した記事にしようかと思います。 
Azure AD とアプリケーションと言っても範囲がかなり広いため、今回は、アプリケーションの登録と、普段は各種ライブラリで吹っ飛ばしてしまう Azure AD との OpenID Connect / OAuth 2.0 での認証認可のやり方について記事にしようと思います。 

自分もそうですけど、初めて触るもの、よく分からないもの、って怖いですよね。 
Azure AD にアプリケーションを登録するって怖い！認証認可ってよく分からないから怖い！って自分も昔は思っていました。 
同じように感じている方がこの記事をきっかけに次のステップに進んでいただければとってもうれしいです。 

# Azure AD へのアプリケーションの登録
Azure AD にアプリケーションを登録する場合、以下の項目から登録することができます。 

- [Azure ポータル] &gt; [Azure Active Directory] &gt; [アプリの登録]
- [Azure ポータル] &gt; [Azure Active Directory] &gt; [エンタープライズ アプリケーション] 

それぞれの違いについてここでは詳細は触れませんが、いずれの方法でアプリケーションを登録したとしても、実はすべてのアプリケーションで Azure AD と OIDC / OAuth 2.0 での認証認可が可能だったりします。
今回はテストのやり方の紹介が目的のため、[アプリの登録] を利用した方法を記載します。 
[エンタープライズ アプリケーション] についてはまた別の機会にでも。 

なお、Azure AD に登録したアプリケーションはいらなくなったら削除することができるため、いくらでも検証用にアプリケーションを登録することができますよ。 
Visual Studio 特典など、検証できるテナントをお持ちだったらぜひ試してみてください。

## [アプリの登録] を利用した登録
[アプリの登録] からは以下の手順でアプリケーションを登録できます。
1. [Azure ポータル] にグローバル管理者などでサインインします
1. [Azure Active Directory] &gt; [アプリの登録] の順に遷移します
1. [+ 新規登録] をクリックします 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/89443a1c-bc91-fbe7-0f05-422a7f8e6d1a.png)
1. 任意の内容で登録します ("リダイレクト URI" は "Web" を選択して任意の URL を入力します (ここで入力した内容は後で利用するためメモします))
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/9c02119d-f93b-fcb7-6152-e44e7a47ba9b.png)
1. 登録が完了すると、[概要] ページが表示されるため、ここに表示されている "アプリケーション (クライアント) ID" と "ディレクトリ (テナント) ID" をメモします 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/35662cfe-25b6-3dcd-c967-f6e8935459cf.png)
1. 理由は触れませんが今回の方法ではアクセス トークン取得時にクライアント シークレットが必要になります。このため、左側のメニューから [証明書とシークレット] を選択し、[+ 新しいクライアント シークレット] からシークレットを追加します 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/fcef44f9-f429-9c91-5850-0995cbcf5151.png)
1. "値" に表示されている内容をメモします 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/3c4108a3-491e-c9c6-5001-408d8872c834.png)

まとめると、アプリケーションを登録する際に以下の四点はメモしておいてください。
- アプリケーション (クライアント) ID
- ディレクトリ (テナント) ID
- クライアント シークレット
- リダイレクト URI

# 認可フロー
OAuth 2.0 ではいくつかのフローが定義されています。
本記事ではそれぞれのフローの詳細については記載しません。もし詳細を知りたかったら [OAuth 2.0 全フローの図解と動画](https://qiita.com/TakahikoKawasaki/items/200951e5b5929f840a1f) などがとってもわかりやすいなって個人的には思いました。ご興味があればぜひ。

- 認可コード フロー
- インプリシット フロー
- リソース オーナー パスワード クレデンシャルズ フロー
- クライアント クレデンシャルズ フロー
 
本記事では、上記のうち Web アプリケーションでも良く利用するであろう "認可コード フロー" のテストのやり方についてご紹介します。

## 認可コード フロー
認可コード フローは、その名のとおり、認可コードという情報を認可サーバーから受け取り、その認可コードをアクセス トークンと引き換えるフローです。 
今回は Azure AD との連携となるため、マイクロソフトの資料では以下が参考になるかと思います。

Microsoft ID プラットフォームと OAuth 2.0 認証コード フロー
https://learn.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-auth-code-flow

認可コード フローは大まかには以下のような流れとなります。
1. 認可エンドポイント (本記事の場合 Azure AD のエンドポイント) へ認可リクエストを送信します
1. ユーザー名やパスワードが要求されたら入力します
1. 認可エンドポイントから認可コードが発行されます
1. トークン エンドポイントへ認可コードを提示してアクセス トークンをリクエストします
1. トークン エンドポイントからアクセス トークンが発行されます
1. リソースへのアクセス時にアクセス トークンを提示します

上記の流れのうち、ポイントは "認可コードの取得" と "アクセス トークンの取得" です。 
本記事では簡易的にその動作を確認するための方法として、それぞれ以下の方法で取得する方法をご紹介します。 
と言っても、いつも自分もやっている方法ではありますけどね。

- 認可コードの取得 : ブラウザーを利用する
- アクセス トークンの取得 : PowerShell を利用する

# 認可コードとアクセス トークンの取得
では、早速ですが、いつも自分がやっている方法を紹介します。
もっと楽で良い方法があるとは思うのですが、なるべくツールなどはインストールせずに、Windows に既定でインストールされているものだけで完結するように考えた結果、このような方法となっています。

## 認可コードの取得
まずは認可コードを取得してみましょう。 

Azure AD から認可コードを取得するには以下のような GET リクエストを送信します。
{ディレクトリ (テナント) ID} や {アプリケーション (クライアント) ID}、{リダイレクト URI} は、事前にメモしていた内容で置き換えます。なお、{リダイレクト URI} は、URL エンコードした内容とします。 

    GET https://login.microsoftonline.com/{ディレクトリ (テナント) ID}/oauth2/v2.0/authorize? 
    client_id={アプリケーション (クライアント) ID}
    &response_type=code
    &redirect_uri={リダイレクト URI}
    &scope={リソースへのアクセスに必要なアクセス許可の一覧}

以下はそれぞれの情報を埋め込み、改行を削除した URL の例です。
なお、今回は後述の処理を行うために scope に "User.Read" を指定してみました。

    https://login.microsoftonline.com/ac7b3f7e-0000-1111-2222-6ce0f67a4d44/oauth2/v2.0/authorize?client_id=ca8f4006-1deb-4ba4-9dc1-b1353702c294&response_type=code&redirect_uri=http%3a%2f%2flocalhost&scope=User.Read

ブラウザーを Guest モードなどで起動し、上記で用意した URL をアドレス バーにペーストしてアクセスしてみましょう。 Azure AD のサインイン画面が表示されたら、早速ユーザー名とパスワードを入力して次に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/d9323735-11c5-f576-9947-03d0c159406c.png)

すると、以下のように "要求されているアクセス許可" の画面が表示されたのではないかと思います。
この "要求されているアクセス許可" が表示される理由を話し出すととても長くなってしまいますため、ここでは割愛します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/4b7da648-b758-70a2-6a87-a642c48708e3.png)

[承諾] ボタンを押すと、以下のように http://localhst/?code=xxxxxxxxxx といった URL で画面遷移することが確認できるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2320657/5ee2a5e7-ceab-02de-6175-7c3757d7d397.png)

私のローカル環境では IIS が動作しているため、IIS のページが表示されましたが、環境によっては「ページを表示できません」が表示されるかと思います。これはこれで問題ありません。
重要なのは、アドレス バーに表示されている URL であり、この URL をコピーしてメモ帳などにペーストしてみましょう。
そうすると、以下のように code というパラメーターが付与されていることがわかると思います。

http://localhost/?<font color="red">code=0.AT0Afj97rJxwSEqE_Wzg9npNR...HaxtiRKYVf_0pACuowEWszorfmZbg</font>&session_state=cf83585a-7b8c-4029-b790-e8f20f950233#

実は、この "code=" から続く文字列が、**認可コード**の正体です。この認可コードを使うことでアクセス トークンを取得することができるのです。この "code=" から続く文字列をコピーしましょう。

    0.AT0Afj97rJxwSEqE_Wzg9npNR...HaxtiRKYVf_0pACuowEWszorfmZbg

## アクセス トークンの取得
次に、認可コードを使ってアクセス トークンを取得してみましょう。

Azure AD からアクセス トークンを取得するには、Azure AD のトークン エンドポイントに対して以下のような POST リクエストを送信します。
{ディレクトリ (テナント) ID} や {アプリケーション (クライアント) ID}、{リダイレクト URI}、{取得した認可コード}、{クライアント シークレット} は、事前にメモしていた内容で置き換えます。なお、{リダイレクト URI} は、URL エンコードしなくて問題ありません。 

    POST https://login.microsoftonline.com/{ディレクトリ (テナント) ID}/oauth2/v2.0/token
    Host: https://login.microsoftonline.com
    Content-Type: application/x-www-form-urlencoded
    
    client_id={アプリケーション (クライアント) ID}
    &scope={リソースへのアクセスに必要なアクセス許可の一覧}
    &code={取得した認可コード}
    &redirect_uri={リダイレクト URI}
    &grant_type=authorization_code
    &client_secret={クライアント シークレット}

以下はそれぞれの情報を埋め込んだ例です。

    https://login.microsoftonline.com/ac7b3f7e-0000-1111-2222-6ce0f67a4d44/oauth2/v2.0/token

    client_id=ca8f4006-1deb-4ba4-9dc1-b1353702c294
    &scope=User.Read
    &code=0.AT0Afj97rJxwSEqE_Wzg9npNR...HaxtiRKYVf_0pACuowEWszorfmZbg
    &redirect_uri=http://localhost
    &grant_type=authorization_code
    &client_secret=zlm8Q~CTbLQmIjr3a...BhvvLCo8KQngc3YTb63

上記の POST リクエストを送信することでアクセス トークンが得られます。
ただ、残念ながら認可コードを取得するときのようにブラウザーのアドレス バーにペーストするだけでは POST リクエストを送信することができません。
ブラウザー上で POST リクエストを送信するには &lt;form&gt; という要素が含まれた html を作成しなければならず、html を知らない人にとっては容易ではありません。 

このため、何か良い方法がないかと思案した結果、Postman や PowerShell などでは割と簡単に POST リクエストを送信可能なことが分かり、自分はそれ以来、検証したいときはこの方法を利用しています。
ここでは PowerShell を活用した方法を紹介しますが、もし使い言慣れたツールがあればそちらでも構いません。

### PowerShell を活用した方法
まず初めに、メモ帳 (notepad.exe) などのテキスト エディターを起動します。 
以下の内容をコピーしてペーストします。

    $tenantId = ''
    $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    $getatkbody = @{
        grant_type='authorization_code';
        client_id='';
        scope='';
        redirect_uri='';
        client_secret='';
        code=''
    }
    $ret = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $getatkbody
    $ret

認可コードの取得のところで用意した内容をもとに空欄部分を埋めます。 
必要な情報を埋め込んだ後、すべてを選択してコピーします。

    $tenantId = 'ac7b3f7e-0000-1111-2222-6ce0f67a4d44'
    $tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
    $getatkbody = @{
        grant_type='authorization_code';
        client_id='ca8f4006-1deb-4ba4-9dc1-b1353702c294';
        scope='User.Read';
        redirect_uri='http://localhost';
        client_secret='zlm8Q~CTbLQmIjr3a...BhvvLCo8KQngc3YTb63';
        code='0.AT0Afj97rJxwSEqE_Wzg9npNR...HaxtiRKYVf_0pACuowEWszorfmZbg'
    }
    $ret = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $getatkbody
    $ret

次に、PowerShell を起動し、コピーしていた内容をペーストして Enter キーで実行します。
処理に成功 (アクセス トークンの取得に成功) すると、以下のように取得したアクセス トークンの情報が表示されるかと思います。

    token_type     : Bearer
    scope          : User.Read profile openid email
    expires_in     : 4788
    ext_expires_in : 4788
    access_token   : eyJ0eXAiOiJKV1QiLCJub2...fOFGeLy57ML4IuX6Tprl3jhGqu5Uu-klVaA

### 取得したアクセス トークンを確認する
以下のコマンドを実行し、取得したアクセス トークンをクリップ ボードにコピーします。

    $ret.access_token | clip

ブラウザーで https://jwt.ms に接続し、コピーしたアクセス トークンを "Enter token blow" 欄にペーストすると、アクセス トークンの内容を確認いただけます。
アクセス トークンの内容の詳細は以下の資料が参考になるかと思います。

Microsoft ID プラットフォーム アクセス トークン
https://learn.microsoft.com/ja-jp/azure/active-directory/develop/access-tokens

## Microsoft Graph API を呼び出してみる
せっかくなので、取得したアクセス トークンを利用して Microsoft Graph API を呼び出してみましょう。
PowerShell は起動したままと思いますため、まずは以下をそのまま実行してみてください。

    Invoke-RestMethod -Method Get -Uri 'https://graph.microsoft.com/v1.0/me'

以下のようなエラーが出力されたのではないでしょうか？
これは、Microsoft Graph API を呼び出すために必要なアクセス トークンが提示されていないために発生しています。

    Invoke-RestMethod : The remote server returned an error: (401) Unauthorized.
    At line:1 char:1
    + Invoke-RestMethod -Method Get -Uri 'https://graph.microsoft.com/v1.0/ ...
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
        + FullyQualifiedErrorId : 
    WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

このため、取得したアクセス トークンを利用して Microsoft Graph API を呼び出すように改造します。

    $headers = @{ Authorization="Bearer $($ret.access_token)" }
    Invoke-RestMethod -Method Get -Uri 'https://graph.microsoft.com/v1.0/me' -Headers $headers

今度はきちんと情報が取得できたのではないでしょうか。

    @odata.context    : https://graph.microsoft.com/v1.0/$metadata#users/$entity
    businessPhones    : {}
    displayName       : XXXXXXXX YYYYYYYY
    givenName         : YYYYYYYY
    jobTitle          :
    mail              : admin@zzzzzzzz.onmicrosoft.com
    mobilePhone       :
    officeLocation    :
    preferredLanguage : ja-JP
    surname           : XXXXXXXX
    userPrincipalName : admin@zzzzzzzz.onmicrosoft.com
    id                : f186cc0e-849f-4602-9dba-0bfda7cb4b58

今回実行した Microsoft Graph API は、自分自身の情報を取得するための API です。
この API の実行に必要なアクセス許可が "User.Read" だったため、認可コードおよびアクセス トークンを取得するときの scope パラメーターに "User.Read" を指定したのでした。

ユーザーの取得
https://learn.microsoft.com/ja-jp/graph/api/user-get?view=graph-rest-1.0&tabs=http

いかがでしたでしょうか。
いつもならライブラリを使ってサクッと実装する処理を、一つずつ手動でリクエストを送信して確認する方法を紹介しました。
少しでも皆さんの理解につながればうれしいです。

また、どこかでお会いしましょう :-)