Imports DigitalData.Modules.Base Imports DigitalData.Modules.Logging Imports Microsoft.Identity.Client Namespace Mail Public Class OAuth2 Inherits BaseClass Private ReadOnly _tenantId As String Private ReadOnly _clientId As String Private ReadOnly _clientSecret As String Public Const O365_SERVER As String = "outlook.office365.com" Public Const O365_SCOPE As String = "https://outlook.office365.com/.default" Public Const O365_AUTHORITY_PREFIX As String = "https://login.microsoftonline.com/" Public Sub New(pLogConfig As LogConfig, tenantId As String, clientId As String, clientSecret As String) MyBase.New(pLogConfig) _tenantId = tenantId _clientId = clientId _clientSecret = clientSecret End Sub Public Function GetAccessToken() As String ' input hardening and better diagnostics Dim tenantId = If(_tenantId, String.Empty).Trim() Dim clientId = If(_clientId, String.Empty).Trim() Dim clientSecret = If(_clientSecret, String.Empty).Trim() If String.IsNullOrWhiteSpace(tenantId) Then Logger.Error("OAuth2: tenantId is empty.") Throw New ArgumentException("tenantId is empty") End If If String.IsNullOrWhiteSpace(clientId) Then Logger.Error("OAuth2: clientId is empty.") Throw New ArgumentException("clientId is empty") End If If String.IsNullOrWhiteSpace(clientSecret) Then Logger.Error("OAuth2: clientSecret is empty.") Throw New ArgumentException("clientSecret is empty") End If ' common misconfiguration: using the Secret ID (GUID) instead of the Secret VALUE Dim tmpGuid As Guid If Guid.TryParse(clientSecret, tmpGuid) Then Logger.Error("OAuth2: clientSecret looks like a GUID (likely the secret ID). Use the secret VALUE instead.") Throw New ApplicationException("Invalid client secret: looks like Secret ID (GUID), not the secret value.") End If Try Dim authority = O365_AUTHORITY_PREFIX & tenantId Dim app = ConfidentialClientApplicationBuilder.Create(clientId). WithClientSecret(clientSecret). WithAuthority(authority). Build() Dim scopes = New String() {O365_SCOPE} Dim result = app.AcquireTokenForClient(scopes).ExecuteAsync().GetAwaiter().GetResult() Logger.Info("OAuth2 token acquired for tenant {0}. ExpiresOn={1:u}", tenantId, result.ExpiresOn.UtcDateTime) Return result.AccessToken Catch ex As MsalServiceException ' richer diagnostics without depending on specific MSAL version members Logger.Error("MSAL service error. Code={0}. Message={1}", ex.ErrorCode, ex.Message) ' Try to log StatusCode, ResponseBody if available (via reflection to avoid version dependency) Try Dim t = GetType(MsalServiceException) Dim scProp = t.GetProperty("StatusCode") If scProp IsNot Nothing Then Dim sc = scProp.GetValue(ex, Nothing) If sc IsNot Nothing Then Logger.Error("MSAL StatusCode={0}", sc) End If Dim rbProp = t.GetProperty("ResponseBody") If rbProp IsNot Nothing Then Dim rb = rbProp.GetValue(ex, Nothing) If rb IsNot Nothing Then Logger.Error("MSAL ResponseBody={0}", rb) End If Dim ciProp = t.GetProperty("Claims") If ciProp IsNot Nothing Then Dim claims = ciProp.GetValue(ex, Nothing) If claims IsNot Nothing AndAlso claims.ToString().Length > 0 Then Logger.Error("MSAL Claims={0}", claims) End If Catch ' ignore reflection diagnostics failures End Try Throw Catch ex As Exception Logger.Error(ex) Throw End Try End Function End Class End Namespace