Exchange Online の先進認証に対応しました(2)

f:id:kizashi1122:20201110142108j:plain

id:kizashi1122 です。

さて。

Exchange Online の先進認証に対応しました(1) - インゲージ開発者ブログ

前回のエントリでSMTPとPOP3の先進認証対応のため Azure AD 側にアプリを作るところまでは終わりました。

今度はサービス側に設定をしていきましょう。Ruby on Rails を使っている前提で解説します。他の言語の場合は使うライブラリが違ってくるなどはあるでしょうが、OAuth 2.0 のフローは言語依存しませんし、設定しないといけない項目は同じなので参考になるかと思います。

omniauth-oauth2 gem の利用

Ruby で OAuth 2.0 といえば、だいたい相場が決まっていて omniauth-oauth2 gem を直接使うか、omniauth-oauth2 gem を継承した認可サーバーに特化した gem を使うかになります。 Office 365 の場合は、こんな gem が見つかりました。

github.com

設定はこんな感じと README に書いています。

Rails.application.config.middleware.use OmniAuth::Builder do
  provider :microsoft_office365, ENV['OFFICE365_KEY'], ENV['OFFICE365_SECRET'],
  {
    authorize_params: { prompt: 'consent'},
    scope: 'offline_access https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send'
  }
end

スコープをどう設定するか?

もうここで気づいた人がいるかもしれません。

通常、認可サーバ側に設定するスコープ(パーミッション)と、クライアント側から指定するスコープは同じです。
前回のエントリで設定したスコープはこちら。

f:id:kizashi1122:20201124173245p:plain:w300

しかしクライアントに設定するスコープは上述の設定どおり

になります。

ここは海外のサイトを見てもたくさんの人がハマっているので要注意です。

そんなのどこに書いてるんだッ!と思いますがちゃんと書いています。

docs.microsoft.com

よく見ると、Azure AD で設定するアクセス許可の適用範囲は POP.AccessAsUser.All だけど、OAuth2のフローでのアクセス許可のスコープ文字列は https://outlook.office.com/POP.AccessAsUser.All であるということが読み取れます。

また今回はスコープ以外のパラメータとして prompt: consent も指定しています。
これは OAuth の同意画面を表示し、許可を求める画面を表示するような設定です。

他に設定できる値についてはこちらを確認してください。

docs.microsoft.com

まだハマる

このあと適切に Controller を設定し、Azure AD で設定したコールバックURLも動くようにすればいいだけですが、なんと動きません。 OAuth の同意画面のあとのフローでエラーになります。正確に言うとトークンの取得後でエラーになります。これはハマります。

これは omniauth-oauth2 gem の特性が原因です。

https://github.com/murbanski/omniauth-microsoft-office365/blob/e7b0a8640795321274bded8a94f7008c6c9ccf36/lib/omniauth/strategies/microsoft_office365.rb#L40-L42

このトークン取得後、この raw_info メソッドが自動的に認可に使用したユーザの情報を取得しようと API を叩きます。が、これは失敗します。そりゃそうだろ User.Read とか email とかスコープを指定してないじゃないかと言うかもしれませんが、これを指定しても動きません。

これはいまだに謎で、オフィシャルな情報は当たれていないのですが、こんな情報がありました。

IMAP, SMTP scopes are targeted for Exchange resource and not Graph. Whereas User.Read, Mail.ReadWrite are meant for Graph resource.

We do not support generation of tokens that are meant for two resources

techcommunity.microsoft.com

SMTP.Send は Exchange のリソースへのアクセスをするスコープであり、User.Read は Graph リソースへのアクセスをするスコープである、と。そして、2つのリソースにアクセスできるトークンの発行はできない、と。User.Read と同様に email スコープも同じなのでしょう。メールアドレスも取得できません。

結局は私たちは、 omniauth-microsoft-office365 gem を使うのを辞めて、自分たちで作ることにしました。といっても、ほとんど同じでトークンの取得後自身の情報取得のためにAPIを叩かないようにしただけです。

認可につかった「メールアドレス」が通常SMTPやPOP3の認証IDなのでこれが取得できないのはかなり残念な仕様です。

ここまでで

ハマりポイントは多いです。1つ目はOffice 365 の特性でしょうが、オフィシャルのドキュメントにはしっかりと記述はされています。2つ目については Ruby の gem の特性と自分の情報を取得できないという Office 365の特性のコンビネーションでした。

私は事前に Gmail での先進認証対応を先にしていたので、その仕様が頭に入っており、そっちに引っ張られてドハマりしてしまったという感じです。

さて、次は、トークンを使って SMTPプロトコルで認証する、POP3プロトコルで認証するというところに入ります。 ここでもハマります・・・。