設定自訂電子郵件提供者一次性密碼傳送事件 (預覽)
適用於: 員工租用戶 外部租用戶 (深入了解)
本文提供一次性密碼 (OTP) 傳送事件類型的設定及設定自定義電子郵件提供者的指南。 啟動 OTP 電子郵件時會觸發此事件,它可讓您呼叫 REST API,藉由呼叫 REST API 來使用您自己的電子郵件提供者。
必要條件
- 熟悉和瞭解自定義驗證延伸模組中涵蓋的概念。
- Azure 訂用帳戶。 如果您沒有現有的 Azure 帳戶,請註冊免費試用帳戶,或是在建立帳戶時使用 Visual Studio 訂用帳戶權益。
- Microsoft Entra ID 外部租使用者。
- 針對 Azure Communications Services 使用者;
- Azure 通訊服務資源。 如果您沒有帳戶,請在快速入門: 使用新的或現有的資源群組建立和管理通訊服務資源 。
- 已建立並準備好布建網域的 Azure 電子郵件通訊服務資源。 如果您沒有,請參閱快速入門:建立和管理電子郵件通訊服務資源,並使用與 Azure 通訊服務 相同的資源群組。
- 使用電子郵件網域連線的作用中通訊服務資源。 請參閱快速入門:如何連線已驗證的電子郵件網域
- (選擇性) 使用 Azure 通訊服務 傳送電子郵件,以使用 Azure 通訊服務 測試傳送電子郵件給所需的收件者,同時驗證應用程式傳送電子郵件的組態。
- 針對 SendGrid 使用者:
- SendGrid 帳戶。 如果您還沒有帳戶,請從設定 SendGrid 帳戶開始。 如需設定指示,請參閱如何使用 SendGrid 搭配 Azure 傳送電子郵件的建立 SendGrid 帳戶一節。
步驟 1:建立 Azure 函數應用程式
本節說明如何在 Azure 入口網站 中設定 Azure 函式應用程式。 函式 API 是電子郵件提供者的閘道。 您可以建立 Azure 函式應用程式來裝載 HTTP 觸發程式函式,並在函式中設定設定。
提示
根據您開始使用的入口網站,本文中的步驟可能略有不同。
至少要以應用程式系統管理員和驗證系統管理員的身分登入 Azure 入口網站。
從 Azure 入口網站功能表或 [首頁] 頁面,選取 [建立資源]。
搜尋並選取 [函數應用程式],然後選取 [建立]。
在 [建立函式應用程式] 頁面上,選取 [取用],然後選取 [選取]。
在 [ 建立函式應用程式 (取用)] 頁面上的 [基本] 索引標籤中,使用下表中指定的設定來建立函式應用程式:
設定 建議的值 描述 訂用帳戶 您的訂用帳戶 將建立新函式應用程式的訂用帳戶。 資源群組 myResourceGroup 選取用來設定 Azure 通訊服務和電子郵件通訊服務資源的資源群組,作為必要條件的一部分 函數應用程式名稱 全域唯一的名稱 識別新函數應用程式的名稱。 有效的字元是 a-z
(不區分大小寫)、0-9
和-
。部署程式碼或容器映像 代碼 發佈程式碼檔案或 Docker 容器的選項。 在本教學課程中,請選取 [程式碼]。 執行階段堆疊 .NET 您慣用的程式設計語言。 在本教學課程中,請選取 [.NET]。 版本 8 (LTS) 進程內 .NET 執行階段的版本。 同處理序表示您可以在入口網站中建立和修改函式,這是本指南建議的作法 區域 慣用區域 選取的區域應靠近您或靠近函式能夠存取的其他服務。 作業系統 Windows 系統會根據您的執行階段堆疊選項預先選取作業系統。 選取 [檢閱 + 建立],以檢閱應用程式組態選項,然後選取 [建立]。 部署需要幾分鐘的時間。
部署後,請選取 [前往資源] 檢視新的函數應用程式。
1.1 建立 HTTP 觸發程序函式
建立 Azure 函數應用程式之後,請建立 HTTP 觸發程序函數。 HTTP 觸發程序可讓您透過 HTTP 要求叫用函式。 此 HTTP 觸發程式是由您的 Microsoft Entra 自定義驗證延伸模組所參考。
- 在函 式應用程式中,從功能表中選取 [函式]。
- 選取 [建立函式]。
- 在 [建立函式] 視窗的 [選取範本] 底下,搜尋並選取 HTTP 觸發程式範本。 選取 [下一步]。
- 在 [範本詳細數據] 底下,輸入 Function Name 屬性的 CustomAuthenticationExtensionsAPI。
- 針對 [授權層級],選取 [函數]。
- 選取 建立。
1.2 編輯函式
程式碼會從讀取傳入的 JSON 物件開始。 Microsoft Entra ID 會將 JSON 物件傳送至您的 API。 在此範例中,它會讀取電子郵件地址(標識符)和 OTP。 然後,程式代碼會將詳細數據傳送至通訊服務,以使用 動態範本傳送電子郵件。
本操作指南示範使用 Azure 通訊服務 和 SendGrid 的 OTP 傳送事件。 使用索引標籤來選取您的實作。
從功能表,選取 [程式碼 + 測試]。
以下列代碼段取代整個程序代碼。
using System.Dynamic; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Azure.Communication.Email; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; namespace Company.AuthEvents.OnOtpSend.CustomEmailACS { public class CustomEmailACS { private readonly ILogger<CustomEmailACS> _logger; public CustomEmailACS(ILogger<CustomEmailACS> logger) { _logger = logger; } [Function("OnOtpSend_CustomEmailACS")] public async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req) { _logger.LogInformation("C# HTTP trigger function processed a request."); // Get the request body string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); JsonNode jsonPayload = JsonNode.Parse(requestBody)!; // Get OTP and mail to string emailTo = jsonPayload["data"]!["otpContext"]!["identifier"]!.ToString(); string otp = jsonPayload["data"]!["otpContext"]!["onetimecode"]!.ToString(); // Send email await SendEmailAsync(emailTo, otp); // Prepare response ResponseObject responseData = new ResponseObject("microsoft.graph.OnOtpSendResponseData"); responseData.Data.Actions = new List<ResponseAction>() { new ResponseAction( "microsoft.graph.OtpSend.continueWithDefaultBehavior") }; return new OkObjectResult(responseData); } private async Task SendEmailAsync(string emailTo, string code) { // Get app settings var connectionString = Environment.GetEnvironmentVariable("mail_connectionString"); var sender = Environment.GetEnvironmentVariable("mail_sender"); var subject = Environment.GetEnvironmentVariable("mail_subject"); try { if (!string.IsNullOrEmpty(connectionString)) { var emailClient = new EmailClient(connectionString); var body = EmailTemplate.GenerateBody(code); _logger.LogInformation($"Sending OTP to {emailTo}"); EmailSendOperation emailSendOperation = await emailClient.SendAsync( Azure.WaitUntil.Started, sender, emailTo, subject, body); } } catch (System.Exception ex) { _logger.LogError(ex.Message); } } } public class ResponseObject { [JsonPropertyName("data")] public Data Data { get; set; } public ResponseObject(string dataType) { Data = new Data(dataType); } } public class Data { [JsonPropertyName("@odata.type")] public string DataType { get; set; } [JsonPropertyName("actions")] public List<ResponseAction> Actions { get; set; } public Data(string dataType) { DataType = dataType; } } public class ResponseAction { [JsonPropertyName("@odata.type")] public string DataType { get; set; } public ResponseAction(string dataType) { DataType = dataType; } } public class EmailTemplate { public static string GenerateBody(string oneTimeCode) { return @$"<html><body> <div style='background-color: #1F6402!important; padding: 15px'> <table> <tbody> <tr> <td colspan='2' style='padding: 0px;font-family: "Segoe UI Semibold", "Segoe UI Bold", "Segoe UI", "Helvetica Neue Medium", Arial, sans-serif;font-size: 17px;color: white;'>Woodgrove Groceries live demo</td> </tr> <tr> <td colspan='2' style='padding: 15px 0px 0px;font-family: "Segoe UI Light", "Segoe UI", "Helvetica Neue Medium", Arial, sans-serif;font-size: 35px;color: white;'>Your Woodgrove verification code</td> </tr> <tr> <td colspan='2' style='padding: 25px 0px 0px;font-family: "Segoe UI", Tahoma, Verdana, Arial, sans-serif;font-size: 14px;color: white;'> To access <span style='font-family: "Segoe UI Bold", "Segoe UI Semibold", "Segoe UI", "Helvetica Neue Medium", Arial, sans-serif; font-size: 14px; font-weight: bold; color: white;'>Woodgrove Groceries</span>'s app, please copy and enter the code below into the sign-up or sign-in page. This code is valid for 30 minutes. </td> </tr> <tr> <td colspan='2' style='padding: 25px 0px 0px;font-family: "Segoe UI", Tahoma, Verdana, Arial, sans-serif;font-size: 14px;color: white;'>Your account verification code:</td> </tr> <tr> <td style='padding: 0px;font-family: "Segoe UI Bold", "Segoe UI Semibold", "Segoe UI", "Helvetica Neue Medium", Arial, sans-serif;font-size: 25px;font-weight: bold;color: white;padding-top: 5px;'> {oneTimeCode}</td> <td rowspan='3' style='text-align: center;'> <img src='https://woodgrovedemo.com/custom-email/shopping.png' style='border-radius: 50%; width: 100px'> </td> </tr> <tr> <td style='padding: 25px 0px 0px;font-family: "Segoe UI", Tahoma, Verdana, Arial, sans-serif;font-size: 14px;color: white;'> If you didn't request a code, you can ignore this email. </td> </tr> <tr> <td style='padding: 25px 0px 0px;font-family: "Segoe UI", Tahoma, Verdana, Arial, sans-serif;font-size: 14px;color: white;'> Best regards, </td> </tr> <tr> <td> <img src='https://woodgrovedemo.com/Company-branding/headerlogo.png' height='20'> </td> <td style='font-family: "Segoe UI", Tahoma, Verdana, Arial, sans-serif;font-size: 14px;color: white; text-align: center;'> <a href='https://woodgrovedemo.com/Privacy' style='color: white; text-decoration: none;'>Privacy Statement</a> </td> </tr> </tbody> </table> </div> </body></html>"; } } }
選取 [取得函式 URL],然後複製 函式金鑰 URL,此 URL 已使用並稱為
{Function_Url}
。 關閉函式。
步驟 2:將 連接字串 新增至 Azure 函式
連接字串可讓通訊服務 SDK 與 Azure 連線並進行驗證。 針對 Azure 通訊服務 和 SendGrid,您接著必須將這些 連接字串 新增至 Azure 函式應用程式作為環境變數。
2.1:從 Azure 通訊服務 資源擷取 連接字串 和服務端點
您可以從 Azure 入口網站或以程式設計方式使用 Azure Resource Manager API,存取您的通訊服務連接字串和服務端點。
從 Azure 入口網站 的 [首頁] 頁面中,開啟入口網站功能表,搜尋並選取 [所有資源]。
搜尋並選取建立為本文必要條件一部分的 Azure 通訊服務。
在左窗格中,選取 [ 設定] 下拉式清單,然後選取 [ 金鑰]。
複製端點,然後從 [主要密鑰] 複製 [金鑰] 和 [連接字串] 的值。
2.2:將 連接字串 新增至 Azure 函式
流覽回您在建立 Azure 函式應用程式中建立的 Azure 函式。
從函式應用程式的 [概觀] 頁面,選取左側功能表中的 [設定>環境變數] 以新增下列應用程式設定。 新增所有設定之後,請選取 [套用],然後選取 [確認]。
設定 值 (範例) 描述 mail_connectionString https://ciamotpcommsrvc.unitedstates.communication.azure.com/:accesskey=A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u
Azure 通訊服務端點 mail_sender from.email@myemailprovider.com 來自電子郵件地址的 。 mail_subject CIAM 示範 電子郵件的主旨。
步驟 3:註冊自定義驗證延伸模組
在此步驟中,您會設定自定義驗證延伸模組,Microsoft Entra ID 用來呼叫您的 Azure 函式。 自訂驗證延伸模組包含您 REST API 端點的相關資訊、其從您的 REST API 剖析的宣告,以及如何向您的 REST API 進行驗證。 使用 Azure 入口網站 或 Microsoft Graph 來註冊應用程式,以向 Azure 函式驗證自定義驗證延伸模組進行驗證。
註冊自訂驗證延伸模組
至少要以應用程式系統管理員和驗證系統管理員的身分登入 Azure 入口網站。
搜尋並選取 [Microsoft Entra ID],然後選取 [企業應用程式]。
選取 [自訂驗證延伸模組],然後選取 [建立自訂延伸模組]。
在 [基本] 中,選取 EmailOtpSend 事件類型,然後選取 [下一步]。
在 [ 端點組態] 索引標籤中,填入下列屬性,然後選取 [下一步 ] 繼續。
- 名稱 - 自訂驗證延伸模組的名稱。 例如, 電子郵件 OTP 傳送。
- 目標 URL - Azure 函式 URL 的
{Function_Url}
。 瀏覽至 Azure 函式應用程式的 [概觀] 頁面,然後選取您建立的函式。 在函式 [概觀] 頁面中,選取 [取得函式 URL],並使用複製圖示來複製「customauthenticationextension_extension (系統金鑰)」URL。 - 描述 - 自訂驗證延伸模組的描述。
在 [ API 驗證] 索引標籤中,選取 [ 建立新的應用程式註冊 ] 選項,以建立代表函 式應用程式的應用程式註冊。
為應用程式命名,例如 Azure Functions 驗證事件 API,然後選取 [ 下一步]。
在 [ 應用程式] 索引標籤中,選取要與自訂驗證延伸模組相關聯的應用程式。 選取 [下一步]。 您可以選取方塊,將它套用至整個租使用者。 選取下一步以繼續。
在 [ 檢閱] 索引標籤中,檢查自訂驗證延伸模組的詳細數據是否正確。 記下 [API 驗證] 底下的 [應用程式識別碼],需要此識別碼才能在 Azure 函式應用程式中為 Azure 函式設定驗證 (部分機器翻譯)。 選取 建立。
授與管理員同意
建立自訂驗證延伸模組之後,請從入口網站開啟 [應用程式註冊] 下的應用程式,然後選取 [API 許可權]。
從 [ API 許可權] 頁面中,選取 [ 授與系統管理員同意的 “YourTenant” 按鈕,以授與系統管理員同意已註冊的應用程式,讓自定義驗證延伸模組能夠向 API 進行驗證。 自訂驗證延伸模組會使用 client_credentials
透過 Receive custom authentication extension HTTP requests
權限向 Azure Functions 應用程式進行驗證。
下列螢幕快照顯示如何授與許可權。
步驟 4:設定 OpenID Connect 應用程式以進行測試
您可以使用 https://jwt.ms 應用程式來取得權杖並測試自訂驗證延伸模組。 這是 Microsoft 擁有的 Web 應用程式,會顯示已解碼的權杖內容 (權杖內容永遠不會離開您的瀏覽器)。
遵循下列步驟來註冊 jwt.ms Web 應用程式:
4.1 註冊測試 Web 應用程式
- 以至少應用程式系統管理員的身分登入 Microsoft Entra 系統管理中心。
- 瀏覽至 [身分識別]> [應用程式]> [應用程式註冊]。
- 選取新增註冊。
- 輸入應用程式的 [名稱]。 例如,My Test application。
- 在 [支援的帳戶類型] 底下,選取 [僅在此組織目錄中的帳戶]。
- 在 [重新導向 URI] 的 [選取平臺] 下拉式清單中,選取 [Web],然後在 [URL] 文本框中輸入
https://jwt.ms
。 - 選取 [註冊] 以完成應用程式註冊。
- 在應用程式註冊的 [概觀] 下,複製稍後使用的應用程式 (用戶端) 識別碼,並稱為
{App_to_sendotp_ID}
。 在 Microsoft Graph 中,"appId" 屬性會參考它。
下列螢幕快照顯示如何註冊 我的測試應用程式。
4.1 取得應用程式識別碼
在您的應用程式註冊中,於 [概觀] 底下,複製 [應用程式 (用戶端) 識別碼]。 應用程式識別碼在後續步驟中稱為 {App_to_sendotp_ID}
。 在 Microsoft Graph 中,"appId" 屬性會參考它。
4.2 啟用隱含流程
jwt.ms 測試應用程式會使用隱含流程。 在 [我的測試] 應用程式註冊中啟用隱含流程:
- 在 [管理] 底下,選取 [驗證]。
- 在 [Implicit grant and hybrid flows] \(隱含授與和混合式流程\) 下,選取 [ID tokens (used for implicit and hybrid flows)] \(識別碼權杖 (用於隱含和混合式流程)\) 核取方塊。
- 選取 [儲存]。
注意
jwt.ms 應用程式會使用隱含流程來取得標識符令牌,而且僅供測試之用。 不建議對生產應用程式使用隱含流程。 針對生產應用程式,請使用授權碼流程。
步驟 5:保護您的 Azure Functions
Microsoft Entra 自訂驗證延伸模組會使用伺服器對伺服器流程,以取得在 HTTP Authorization
標頭中傳送至 Azure 函式的存取權杖。 將函式發佈至 Azure 時,特別是在生產環境時,您需要驗證在授權標頭中傳送的權杖。
若要保護您的 Azure 函式,請遵循下列步驟來整合 Microsoft Entra 驗證,以驗證隨 Azure Functions 驗證事件 API 應用程式註冊傳入的權杖。
注意
如果 Azure 函式應用程式裝載於與註冊自定義驗證延伸模組的租使用者不同的 Azure 租使用者中,請跳至 使用 OpenID Connect 識別提供者 步驟。
- 登入 Azure 入口網站。
- 瀏覽並選取您先前發佈的函式應用程式。
- 選取左側功能表中的 [驗證]。
- 選取 [新增識別提供者]。
- 從下拉功能表中選取 Microsoft 身分識別提供者。
- 在 [應用程式註冊->應用程式註冊類型] 下,選取 [挑選此目錄中的現有應用程式註冊],然後挑選您在註冊自定義電子郵件提供者時先前建立的 Azure Functions 驗證事件 API 應用程式註冊。
- 新增應用程式的客戶端密碼到期日。
- 在 [未驗證的要求] 底下,選取 [HTTP 401 未授權] 作為識別提供者。
- 取消選取 [權杖存放區] 選項。
- 選取 [新增] 以將驗證新增至您的 Azure Functions。
5.1 使用 OpenID Connect 識別提供者
如果您已設定 Microsoft 識別提供者,請略過此步驟。 否則,如果 Azure Functions 託管所在的租用戶與您註冊自訂驗證延伸模組時所在的租用戶不同,請遵循下列步驟來保護您的函數:
登入 Azure 入口網站,然後瀏覽並選取您先前發佈的函數應用程式。
選取 左窗格中的 [驗證 ]。
選取 [新增識別提供者]。
選取 [OpenID Connect] 作為識別提供者。
提供名稱,例如 Contoso Microsoft Entra ID。
在 [中繼資料項目] 底下,將下列 URL 輸入至 [文件 URL]。
{tenantId}
將 取代為您的 Microsoft Entra 租使用者識別碼,並以{tenantname}
不含 『onmicrosoft.com』 的租用戶名稱取代 。https://{tenantname}.ciamlogin.com/{tenantId}/v2.0/.well-known/openid-configuration
在 [應用程式註冊] 底下,輸入您先前建立之「Azure Functions 驗證事件 API」應用程式註冊的應用程式識別碼 (用戶端識別碼)。
在 Microsoft Entra 系統管理中心:
回到 Azure 函式,在 [應用程式註冊] 底下,輸入用戶端密碼。
取消選取 [權杖存放區] 選項。
選取 [新增] 以新增 OpenID Connect 識別提供者。
步驟 6:測試應用程式
若要測試您的自訂電子郵件提供者,請遵循下列步驟:
開啟新的私人瀏覽器,瀏覽至下列 URL 登入。
https://{tenantname}.ciamlogin.com/{tenant-id}/oauth2/v2.0/authorize?client_id={App_to_sendotp_ID}&response_type=id_token&redirect_uri=https://jwt.ms&scope=openid&state=12345&nonce=12345
將
{tenant-id}
更換為您的租用戶識別碼、租用戶名稱或其中一個已驗證的網域名稱。 例如:contoso.onmicrosoft.com
。將 取代
{tenantname}
為您的租用戶名稱,而不使用 『onmicrosoft.com』。將取代
{App_to_sendotp_ID}
為 [我的測試] 應用程式註冊標識符。請確定您使用電子郵件 一次性密碼帳戶登入。 然後選取 [ 傳送程序代碼]。 請確定傳送至已註冊電子郵件地址的程式代碼會使用上面註冊的自定義提供者。
步驟 7:回復至Microsoft提供者
如果延伸模組 API 內發生錯誤,根據預設,Entra ID 不會將 OTP 傳送給使用者。 您可以改為將錯誤的行為設定回Microsoft提供者。
若要啟用此功能,請執行下列要求。 將取代 {customListenerOjectId}
為稍早記錄的自定義驗證接聽程式識別碼。
- 您需要 EventListener.ReadWrite.All 委派權限。
PATCH https://graph.microsoft.com/beta/identity/authenticationEventListeners/{customListenerOjectId}
{
"@odata.type": "#microsoft.graph.onEmailOtpSendListener",
"handler": {
"@odata.type": "#microsoft.graph.onOtpSendCustomExtensionHandler",
"configuration": {
"behaviorOnError": {
"@odata.type": "#microsoft.graph.fallbackToMicrosoftProviderOnError"
}
}
}
}