由於我們的程式是服務類型的因此Client Credentials Flow就是為了沒有「Resource Owner(這裡指的是人)」介入的情況而使用的OAuth流程,但是!!就是這個但是!!微軟的IMAP、POP、SMTP都不支持Client Credentials Flow!!


2022/06/30 新增IMAP、POP、SMTP的Client Credentials Flow!直接使用就對了!【點我前往參考連接

💡 微軟給的解答是對Microsoft 365組織內的所有Mail郵箱做特定的權限後使用Microsoft Graph API進行Mail信件的取得,但是這種做法跟原本IMAP的服務的流程已經完全不一致如果這樣做程式勢必要大改一番(IMAP全部的過程都要拆掉),因此這個方式就不考慮了

另外微軟官方給的兩種方式,因此我們只剩下下面兩種選擇

  1. OAuth2授權碼流程(Authorization Code Flow)點我前往
  2. OAuth2設備授權流程(Device Auth Flow)點我前往


前置準備1 - 添加Office 365 Exchange Online IMAP權限

使用Client Credentials Flow之前需要先做一些額外的設定,首先要添加Office 365 Exchange Online的API權限



選擇我的組織使用的API」頁簽並在搜尋欄裡面輸入Office 365的關鍵字後即可找到Office 365 Exchange Online」



確定點選的是Office 365 Exchange Online之後選擇應用程式權限並且找到IMAP將它打勾



添加完成之後記得要要通過管理員授權同意」哦!



前置準備2 - 使用Exchange Online進行應用程式註冊和郵箱授權

💡 注意!Exchange Online需要使用Power Shell和Azure AD的管理員帳號進行設定

使用Exchange Online之前需要安裝ExchangeOnlineManagement【點我前往安裝方式】,安裝完成後要匯入EXO V2的模組

Import-Module ExchangeOnlineManagement

匯入完成後就可以使用以下指令進行登入
Connect-ExchangeOnline -UserPrincipalName <管理者帳號>


登入成功後要註冊應用程式到Exchange Online的Service Principal裡面,註冊方式可以使用以下指令
New-ServicePrincipal -AppId <應用程式識別碼> -ServiceId <物件識別碼> -DisplayName <隨便取名即可>
AppIdServiceId分別對應到的是應用程式識別碼物件識別碼可以到Azure AD底下的企業應用程式」找到對應的代碼
DisplayName則是方便您日後的查找因此隨便命名即可


如果要查看有註冊過哪些服務可以使用以下指令查看
Get-ServicePrincipal


最後要讓應用程式有權限可以存取指定的郵箱可以使用以下指令設定
Add-MailboxPermission -Identity "指定的郵箱帳號" -User <ServiceId> -AccessRights FullAccess
ServiceId跟上面一樣對應到應用程式的物件識別碼,另外也可以使用Get-ServicePrincipal」指令查詢


添加完成後可以使用指令查看這個郵箱帳號添加了哪些人可以存取以及他的權限等級
Get-EXOMailboxPermission -Identity "指定的郵箱帳號"



元件準備

💡 這裡和Authorizaiton Code Flow時使用的元件都是相同的
  • TsgcHTTP_OAuth2_Client

另外加上一個TButton,畫面直接奉上


在Use Client Credentials Flow - Button的OnClick事件啟動TsgcHTTP_OAuth2_Client元件

procedure TFormIMAPTest.btn_Client_Credentials_FlowClick(Sender: TObject);
begin
  DoLog('Start Client Credentials Flow');
  OAuth2_Client_Credentials.Start;
end;



實作Client Credentials Flow做授權驗證

TsgcHTTP_OAuth2_Client需要設定一下OAuth的屬性

  • OAuth2Options:
    • Client ID:用戶識別碼,直接帶入Azure AD的應用程式識別碼
    • Client Secret:用戶識別密碼,直接帶入Azure AD的應用程式密碼
    • Grant Type:授權方式,直接選擇auth2ClientCredentials
      💡 目前這個元件只支持Authorization Code Flow和Client Credentials Flow
    • Password:無需設定
    • Username:無需設定
  • AuthorizationServerOptions:
    • AuthURL:無需設定,Client Credentials不需要Auth驗證的流程因此不需要AuthURL
    • Scope:請求的權限,我們要取得IMAP的權限因此設定「https://outlook.office365.com/.default」這組即可
      💡 在Azure AD上使用Client Credentails的Scope不管要取得什麼權限幾乎都是XXXX/.default結尾,不是這組Scope的話會回傳給你400錯誤
    • TokenURL:驗證成功後取得Token的URL,需要包含TenantID
      💡 https://login.microsoftonline.com/<TenantID>/oauth2/v2.0/token



屬性設定完成後接著是事件,走Client Credentials Flow的話就只會有回傳AccessToken的動作這裡就跟Authorization Code Flow不太一樣直接加入OnAfterAuthorizeCode,另外如果想要知道錯誤的話也可以加入OnErrorAuthorizeCode和OnErrorAccessToken

procedure TFormIMAPTest.OAuth2_Client_CredentialsAfterAccessToken(
  Sender: TObject; const Access_Token, Token_Type, Expires_In, Refresh_Token,
  Scope, RawParams: string; var Handled: Boolean);
begin
  DoLog('AccessToken: ' + Access_Token + CRLF +
        'Token_Type: ' + Token_Type + CRLF +
        'Expires_In: ' + Expires_In + CRLF +
        'Refresh_Token: ' + Refresh_Token + CRLF +
        'Scope: ' + Scope);
  TIdSASLXOAuth(xOAuthSASL.SASL).Token := Access_Token;
  TIdSASLXOAuth(xOAuthSASL.SASL).ExpireTime := Expires_In;
  TIdSASLXOAuth(xOAuthSASL.SASL).User := '你剛剛使用Exchange Online設定的郵件帳號';
end;





執行Client Credentials Flow

設定完成後啟動程式執行

完成!是不是走Client Credentials簡單很多呢XD




總結

雖然我覺得Client Credentials Flow是再適合不過我們目前的需求的OAuth流程,但是既然微軟不支援那也沒辦法了XDD

由於微軟開放了Client Credentials Flow對於IMAP的驗證支援,我們的Client端只需要ClientID和Client Secret就可以操作指定的郵箱,對比ROPC(Resource Owner Password Credentials)還是需要使用者的密碼來說真的安全很多,只是Client Credentials Flow的前置設定很麻煩就是了XD



參考資料:

程式碼下載:Delphi-OAuth2IMAP