使用 MSAL.NET 的 Resource Owner Password Credential ROPC 架構來取得存取權杖但是無須透過瀏覽器來進行身分驗證
想要能夠解決這樣的問題,在這篇文章中,將會使用 Microsoft identity platform 也就是 Microsoft 身分識別平台 (前身為適用於開發人員的 Azure Active Directory) 來進行取得所需要的 Access Token,在官方網站的定義說明為: Microsoft 身分識別平台是 Azure Active Directory (Azure AD) 開發人員平台的演化。 它可讓開發人員建置應用程式以登入所有 Microsoft 身分識別,並取得權杖以呼叫 Microsoft Graph 等 Microsoft API,或開發人員所建置的 API。若對於這個平台所提供的技術還不太孰悉的話,可以參考 Microsoft identity platform 這裡的相關說明文件內容。
大部分的時候,在要進行身分驗證過程,將會使用 OAuth 2.0 和 OpenID Connect 的方式,此時,將會需要透過瀏覽器的幫忙,由使用者輸入其身分憑證,也就是帳號與密碼。
此時,開發者將會需要知道 Microsoft 身分識別平臺支援資源擁有者密碼認證 ROPC 這個技術,授與 讓應用程式可以直接處理其密碼來登入使用者。 ROPC 流程需要高程度的信任和使用者暴露, 而且您應該只在有其他、更安全的流程無法使用時, 才使用此流程。
為了要解決之前提到的需求,將會分成兩篇文章,在這裡,第一篇文章中將會先來說明如何透過使用者的帳號與密碼,就背景取得 Microsoft Graph 的 Access Token,在下一篇文章中 使用 Microsoft.Graph 來存取 Office 365 行事曆的功能,將會說明如何透過這裡取得的 Access Token 來呼叫 Office 365 的相關功能。
在這篇文章所提到的專案原始碼,可以從 GitHub 下載。
建立測試專案
在這篇文章中,將會使用一個 Console 主控台應用程式類型的專案,來進行設計這樣的需求,也就是說,在無需與使用者互動,或者當時電腦環境沒有瀏覽器軟體的環境下,可以透過使用者帳號與密碼的方式,取得存取權杖 Access Token。因此,請先建立一個名為 ROPCAuthentication 的 Console 專案,在這裡使用的是 .NET Framework 開發框架。
安裝 MSAL.NET NuGet 套件
接著要來安裝 Microsoft.Identity.Client 套件,如此,開發者無須涉入複雜與繁瑣的 HTTP 通訊協定,就可以使用 MSAL.NET 這個套件所提供的 API,就可以獲得通過身分驗證之後的存取權杖。
- 滑鼠右擊此專案
- 選擇 [管理 NuGet 套件] 選項
- 點選瀏覽標籤頁次
- 在搜尋文字輸入盒內輸入這個文字 Microsoft.Identity.Client
- 點選 安裝 按鈕
建立 Azure Active Directory 的應用程式
現在使用的 Microsoft 身分識別平台 (v2.0),想要使用這樣的服務,就需要 使用 Microsoft 身分識別平台來註冊應用程式,因此,請準備好一個 Office 365 的帳號與密碼,準備來建立此應用程式。
請使用瀏覽器開啟 App registration 這個網址,這樣將會看到如下圖的畫面,當然,也可以直接進入到 Microsoft Azure 網頁中,在最左邊的功能清單中,找到 [Azure Active Directory] 這個選項並且點選他,也會看到相同的網頁內容。
- 請點選 [新增註冊] 按鈕連結
- 此時將會顯示 [註冊應用程式] 畫面
- 請在名稱欄位輸入: 我的自動帳密登入該名稱為這次要設計的應用程式名稱,當然,開發者可以輸入任何可以代表這個應用程式的代表名稱
- 對於 [支援的帳戶類型]請選擇 [僅此組織目錄中的帳戶 (僅 VulcanLab - 單一租用戶)] 這個選項
- 最後請點選 [註冊] 按鈕
- 整個操作過程如同底下螢幕截圖所示
設定 Azure Active Directory 的應用程式
現在需要把剛剛建立的應用程式進行相關設定,以滿足當初需求,此時,瀏覽器的畫面應該會顯示到剛剛建立的應用程式設定畫面上,類似下圖的螢幕截圖。
在這裡將會需要點選 [概觀] 、 [驗證] 、 [API 權限] 這三個標籤頁次,分別來進行操作
- 請點選 [概觀] 標籤頁次
- 在其右邊將會看到 [應用程式 (用戶端) 識別碼] 與其底下的一串 GUID 代碼請將這個代碼妥善儲存到適當的地方,等下寫程式的時候會用到
- 在其右邊將也會看到 [目錄 (租用戶) 識別碼] 與其底下的一串 GUID 代碼請將這個代碼妥善儲存到適當的地方,等下寫程式的時候會用到
- 請點選 [驗證] 標籤頁次
- 將右半部畫面內容,使用滑鼠捲動到最下方
- 此時將會看到 [預設用戶端類型] 設定
- 請將該設定項目切換成為 [是] 選項
- 最後點選上方的 [儲存] 按鈕
- 請點選 [API 權限] 標籤頁次
- 將會看到如下圖的 API 權限畫面
- 在右半部下方的有個 [代表 VulcanLab 授予管理員同意] 的按鈕,現在是灰色的,這部分的操作等下會在後便有說明。
- 請點選右半部上方的 [新增權限] 按鈕
- 當 [要求 API 權限] 畫面出現之後
- 請使用滑鼠稍微往下捲動,直到看到 Microsoft Graph 選項出現,如下面截圖
- 請點選 [Microsoft Graph] 這個項目
- 當出現了 委派的權限 (您的應用程式必須是登入的使用者身分,才能存取 API。) 與 應用程式權限 (您的應用程式正以背景服務或精靈執行,而且不是登入的使用者身分。) 兩個選項
- 請點選 [委派的權限]
- 現在將會出現所有 Microsoft Graph API 可以選用的權限清單
- 請在 [選取權限] 下方的文字輸入盒輸入: Calendar
- 如此,將會看到與 Office 365 行事曆相關的 API 出現
- 在此需要點選 [Calendars.ReadWrite] 這個選項
- 最後,點選 [新增權限] 按鈕
- 完成這個應用程式的所有設定步驟
不過,現在螢幕畫面的最上方出現了 Permissions have changed, please wait 10 seconds before granting admin consent. Users and/or admins will have to consent even if they have already done so previously. 這段文字,看樣子是剛剛建立與設定的應用程式,需要通過管理者的同意才能夠使用。
為了要解決此一問題,此時需要請具有 Office 365 內擁有 Global Admin 權限的人幫忙處理。請管理者先登入到 Office 365 的系統上,接著請使用瀏覽器開啟 App registration 這個網址
當 Azure 網頁顯示出來之後,請點選左方的 [應用程式註冊] 選項,如此就會在右方看到剛剛建立的應用程式。
請點選這個應用程式項目
接著點選左邊清單選項中的 [API 權限] ,現在將會看到最下方的 [代表 VulcanLab 授與管理者同意] 按鈕不再是灰色的了
請點選 [代表 VulcanLab 授與管理者同意] 這個按鈕
當出現對話窗,顯示這樣內容
要代表 VulcanLab 中的所有帳戶對要求的權限授與同意嗎? 這會更新此應用程式現有的系統管理員同意記錄,以與下列記錄吻合。
請點選 [是] 按鈕
此時,在右半部的上方,將會顯示出 已成功對要求的權限授與系統管理員同意 文字,並且在 API/權限名稱 清單中,將會看到 Calendars.ReadWrite API 權限已經在清單內,最後則是狀態欄位中,看到亮起綠燈,顯示出 已授與 VulcanLab 文字,這表示了這個 API 已經可以使用了。
開始設計獲取 Token 的程式碼
想要透過 Microsoft identity platform 獲取到 Access Token ,使這個應用程式可以呼叫 Microsoft Graph 相關 API,在這裡將會是要存取指定使用者的行事曆,並且無須透過瀏覽器網頁來進行身分驗證,可以透過底下的程式碼來做到。
在程式一開始,需要先來定義四個參數,那分別是
- clientId : 請將建立這個應用程式時候,所看到的 [應用程式 (用戶端) 識別碼] 內容填入到這個變數
- authority : 請將建立這個應用程式時候,所看到的 [目錄 (租用戶) 識別碼] 內容填入到這個變數
- account : Office 365 使用者的電子郵件信箱
- password : Office 365 使用者的密碼
設定完成之後,就可以使用
PublicClientApplicationBuilder.Create
方法來產生出一個 IPublicClientApplication 的執行個體 app;接下來,需要將所提供的密碼,使用 SecureString 以加密的方式儲存,最後將會使用 app.AcquireTokenByUsernamePassword
方法,採用將使用者帳號與密碼的資訊方式,進行獲取存取權杖的動作。
若成功獲得的存取權杖,將會回傳一個非空值的 AuthenticationResult result 物件,而該物件的 Account.Username 就是這個登入驗證成功的使用者電子郵件信箱,而 AccessToken 屬性將會是我們想要取得的存取權杖。
該存取權杖是一個 JWT 的 Token,透過 jwt.io 網站來解碼解析出這個 Token,將會看到類似底下的內容。
{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/0e...........b/",
"iat": 1568871825,
"nbf": 1568871825,
"exp": 1568875725,
"acct": 0,
"acr": "1",
"aio": "42FgYFin+P+gdmXXvoKbcdF7J839FvaA673cbVvu5TKlYhsNJRsA",
"amr": [
"pwd"
],
"app_displayname": "我的自動帳密登入",
"appid": "32b..........a7",
"appidacr": "0",
"family_name": "account",
"ipaddr": "1.......1",
"name": "account",
"oid": "222381e8-d70a-4918-971a-2ece91be672e",
"platf": "3",
"puid": "100320006E98B137",
"scp": "Calendars.ReadWrite User.Read profile openid email",
"sub": "YRgQR-gtHrtY_vGaxA_LzC_O5n8Dfs_YvEJgCSMiMAo",
"tid": "0e..........db",
"unique_name": "account@MyVulcan.onmicrosoft.com",
"upn": "account@MyVulcan.onmicrosoft.com",
"uti": "emLgFi0oVUGaQ6LmjXdXAA",
"ver": "1.0",
"xms_st": {
"sub": "MD3a8TYO3PvHerAmzQyLxoMBZ5hcAJWB80pNUSUueuo"
},
"xms_tcdt": 1568696878
}
這是這篇文章使用到的程式碼
class Program
{
static string clientId = "應用程式 (用戶端) 識別碼";
static string authority = "目錄 (租用戶) 識別碼";
static string account = "Office 365 使用者的電子郵件信箱";
static string password = "Office 365 使用者的密碼";
static void Main(string[] args)
{
GetMicrosoftGraphAccessTokeyAsync().Wait();
}
static async Task GetMicrosoftGraphAccessTokeyAsync()
{
string[] scopes = new string[] { "user.read" };
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/{authority}")
.Build();
AuthenticationResult result = null;
try
{
#region 將所提供的密碼,使用 SecureString 以加密的方式儲存
// SecureString 代表應該將文字保密,例如於不再使用時將它從電腦記憶體刪除。
var securePassword = new SecureString();
foreach (char c in password)
securePassword.AppendChar(c);
#endregion
// 使用使用者的帳號與密碼憑證,來獲取存取權杖
result = await app.AcquireTokenByUsernamePassword(scopes, account, securePassword)
.ExecuteAsync();
}
catch (MsalException ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine(result.Account.Username);
Console.WriteLine($"Access Token : {result.AccessToken}");
foreach (var item in result.Scopes)
{
Console.WriteLine($"Scope :{item}");
}
}
}
更多資訊請點選 : Microsoft 驗證程式庫 (MSAL) 概觀
當想要設計一個系統服務或者要在如 IoT 的環境下,設計一個身分驗證服務,並且接著可以呼叫 Microsoft Graph 所提供的相關 API,將會遇到這樣的問題,因為當要為某個使用者進行身分驗證並且取得 Access Token 存取權杖的時候,因為當時的環境沒有或者無法使用瀏覽器,因此,將會變成不可行;那麼,究竟要如何來解決此一問題呢?這裡的需求那就是想要設計一個程式,他是在背景執行,可以幫使用者,代表該使用者來存取 Office 365 中的相關服務,例如:可以存取行事曆、可以存取信箱內的郵件、或者可以代表某個使用者來發送電子郵件。