Forum Discussion
Connecting to Office 365 online mail with OAUTH 2.0 authentication always failed !
JLund75 this may have to do with the way the app is setup in Azure and Exchange. I use this script all the time (it's from MS), it will create the app, setup a group, add the user (identity sending the email) to the group, and permission it to use Exchange and send mail (the only user that can use it will be in the group). You need to run it as a Global Admin. In the end, it will give you the App ID and client secret. I want to get my opencart site setup with this, let me know if this helps or if I'm way off base.
#------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Script to create AzureADApplication 'Microsoft Identity Manager Service Mailbox client' for Exchange Online authorization in MIM.
#------------------------------------------------------------------------------------------------------
Param
(
[PSCredential] $Credential,
[Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
[string] $TenantId,
[string] $AzureEnvironmentName,
[Parameter(Mandatory=$True, HelpMessage='Mailbox Account Email')]
[string] $MailboxAccountEmail
)
# Create a password that could be used as an application key
Function ComputePassword
{
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
$aesManaged.GenerateKey()
return [System.Convert]::ToBase64String($aesManaged.Key)
}
# Create an application key
Function CreateAppKey([DateTime] $fromDate, [double] $durationInYears, [string]$pw)
{
$endDate = $fromDate.AddYears($durationInYears)
$keyId = (New-Guid).ToString();
$key = New-Object Microsoft.Open.AzureAD.Model.PasswordCredential
$key.StartDate = $fromDate
$key.EndDate = $endDate
$key.Value = $pw
$key.KeyId = $keyId
return $key
}
# Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
# The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
# described in $permissionType
Function AddResourcePermission($requiredAccess, `
$exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
{
foreach($permission in $requiredAccesses.Trim().Split("|"))
{
foreach($exposedPermission in $exposedPermissions)
{
if ($exposedPermission.Value -eq $permission)
{
$resourceAccess = New-Object Microsoft.Open.AzureAD.Model.ResourceAccess
$resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
$resourceAccess.Id = $exposedPermission.Id # Read directory data
$requiredAccess.ResourceAccess.Add($resourceAccess)
}
}
}
}
# Exemple: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
{
# If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
if ($servicePrincipal)
{
$sp = $servicePrincipal
}
else
{
$sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
}
$appid = $sp.AppId
$requiredAccess = New-Object Microsoft.Open.AzureAD.Model.RequiredResourceAccess
$requiredAccess.ResourceAppId = $appid
$requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]
# $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
if ($requiredDelegatedPermissions)
{
AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
}
# $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
if ($requiredApplicationPermissions)
{
AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
}
return $requiredAccess
}
Function ConfigureApplications
{
if ([string]::IsNullOrWhiteSpace($AzureEnvironmentName))
{
$AzureEnvironmentName = "AzureCloud"
}
switch ($AzureEnvironmentName)
{
"AzureCloud"
{
$url = "https://login.microsoftonline.com";
$connectionUri = "https://outlook.office365.com/powershell-liveid/";
$exchangeEnvName = "O365Default";
break
}
"AzureChinaCloud"
{
$url = "https://login.chinacloudapi.cn";
$connectionUri = "https://partner.outlook.cn/PowerShell";
$exchangeEnvName = "O365China";
break
}
"AzureUSGovernment"
{
$url = "https://login.microsoftonline.us";
$connectionUri = "https://outlook.office365.us/powershell-liveid";
$exchangeEnvName = "O365USGovGCCHigh";
break
}
default
{
throw "[$AzureEnvironmentName] was not found in the availbale list of azure environments"
}
}
try
{
if ([string]::IsNullOrWhiteSpace($Credential) -and [string]::IsNullOrWhiteSpace($TenantId))
{
$creds = Connect-AzureAD -AzureEnvironmentName $AzureEnvironmentName
}
elseif ([string]::IsNullOrWhiteSpace($Credential) -and ![string]::IsNullOrWhiteSpace($TenantId))
{
$creds = Connect-AzureAD -TenantId $TenantId -AzureEnvironmentName $AzureEnvironmentName
}
elseif (![string]::IsNullOrWhiteSpace($Credential))
{
$creds = Connect-AzureAD -Credential $Credential -AzureEnvironmentName $AzureEnvironmentName
}
else
{
$creds = Connect-AzureAD -TenantId $TenantId -Credential $Credential -AzureEnvironmentName $AzureEnvironmentName
}
}
catch [System.Exception]
{
Write-Error $PSItem.ToString()
throw "Could not get credentials"
}
if ([string]::IsNullOrWhiteSpace($TenantId))
{
$TenantId = $creds.Tenant.Id
}
$tenant = Get-AzureADTenantDetail
$tenantName = ($tenant.VerifiedDomains | Where { $_._Default -eq $True }).Name
# Get the user running the script
$user = Get-AzureADUser -ObjectId $creds.Account.Id
$isAppExists = Get-AzureADApplication -Filter "DisplayName eq 'Microsoft Identity Manager Service Mailbox client'"
if ($isAppExists)
{
throw "Application 'Microsoft Identity Manager Service Mailbox client' already exists. Please delete it before creating a new one."
}
# Create the service AAD application
Write-Host "Creating the AAD application (Microsoft Identity Manager Service Mailbox client)"
# Get a 2 years application key for the service Application
$pw = ComputePassword
$fromDate = [DateTime]::Now;
$key = CreateAppKey -fromDate $fromDate -durationInYears 2 -pw $pw
$serviceAppKey = $pw
# Create application with Redirect URl to Office365 Admin Portal
$serviceAadApplication = New-AzureADApplication -DisplayName "Microsoft Identity Manager Service Mailbox client" `
-ReplyUrls "https://admin.microsoft.com/AdminPortal/" `
-PasswordCredentials $key `
-Oauth2AllowImplicitFlow $true `
# add the user running the script as an app owner if needed
$owner = Get-AzureADApplicationOwner -ObjectId $serviceAadApplication.ObjectId
if ($owner -eq $null)
{
Add-AzureADApplicationOwner -ObjectId $serviceAadApplication.ObjectId -RefObjectId $user.ObjectId
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($serviceAadApplication.DisplayName)'"
}
$requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.RequiredResourceAccess]
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Office 365 Exchange Online" `
-requiredApplicationPermissions "full_access_as_app" `
$requiredResourcesAccess.Add($requiredPermissions)
Set-AzureADApplication -ObjectId $serviceAadApplication.ObjectId -RequiredResourceAccess $requiredResourcesAccess
# create mail-enabled security group and add user to this group
Write-Host "Connecting to Exchange Online"
Connect-ExchangeOnline -Credential $Credential -ConnectionUri $connectionUri -ExchangeEnvironmentName $exchangeEnvName
Write-Host "Looking for MIMMailboxAccess"
$group = Get-DistributionGroup -Identity "MIMMailboxAccess" -ErrorAction "SilentlyContinue" #Do not throw an error as group will be created further if it was not found
if (!$group)
{
Write-Host "MIMMailboxAccess was not found. Creating it"
$group = New-DistributionGroup -Type Security -Name "MIMMailboxAccess"
}
Write-Host "Group found: " $group
$distributionGroupMemberEmail = $MailboxAccountEmail
if(!$distributionGroupMemberEmail)
{
$distributionGroupMemberEmail = $user.UserPrincipalName
}
Write-Host "Looking for distribution group member with email" $distributionGroupMemberEmail
$mimMailBoxGroupMembers = Get-DistributionGroupMember -Identity "MIMMailboxAccess"
$mimMailBoxGroupMemberExists = $False
foreach($mimMailBoxGroupMember in $mimMailBoxGroupMembers)
{
if($mimMailBoxGroupMember.PrimarySmtpAddress -eq $distributionGroupMemberEmail)
{
$mimMailBoxGroupMemberExists = $True
break
}
}
if($mimMailBoxGroupMemberExists -eq $False)
{
Write-Host "Distribution group member was not found. Adding it"
Add-DistributionGroupMember -Identity "MIMMailboxAccess" -Member $distributionGroupMemberEmail -BypassSecurityGroupManagerCheck
}
else
{
Write-Host "Distribution group member with email" $distributionGroupMemberEmail "already exists"
}
Write-Host "Creating new access policy for the group"
# add restrict policy for this group
$groupName = "MIMMailboxAccess@" + $tenantName;
New-ApplicationAccessPolicy -AccessRight RestrictAccess -AppId $serviceAadApplication.AppId -PolicyScopeGroupId $groupName -Description "Restrict access to application."
Write-Host "Checking if user $distributionGroupMemberEmail has access to the current application"
$testResult = Test-ApplicationAccessPolicy -Identity $distributionGroupMemberEmail -AppId $serviceAadApplication.AppId
if ($testResult.AccessCheckResult -eq "Granted")
{
Write-Host "Access is granted"
}
else
{
Write-Error $testResult.AccessCheckResult
}
$consentURL = "$url/$TenantId/adminconsent?client_id=$($serviceAadApplication.AppId)"
Write-Host "`nWaiting 30 seconds until replication in Azure is done"
Start-Sleep -Seconds 30
Write-Host "Starting browser to accept permissions for the application"
Start-Process -FilePath $consentURL
Write-Warning "If you get an error in a browser then try this link a bit later: $consentURL"
Write-Host "`n****************************************************************"
Write-Host "ApplicationId: " $serviceAadApplication.AppId
Write-Host "TenantId: " $TenantId
Write-Host "ClientSecret: " $serviceAppKey
Write-Host "****************************************************************"
Write-Host "Please copy client secret as it won't be saved"
}
# Pre-requisites
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Write-Host "Enabled TLS 1.2"
# Adding current user folder with modules as all modules will be installed in CurrentUser scope
$currentUserPath= $env:USERPROFILE + "\Documents\WindowsPowerShell\Modules"
$currentUserPathFiler = "*" + $currentUserPath + "*";
if ($env:PSModulePath -notlike $currentUserPathFiler)
{
Write-Host "Adding " $currentUserPath " to PSModulePath"
$env:PSModulePath = $env:PSModulePath + ";" + $currentUserPath
}
# Installing required modules. Check if these (or newer) modules has been installed before
if ((Get-InstalledModule -Name "PowerShellGet" -MinimumVersion 2.2.5 -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Installing PowerShellGet 2.2.5 as it was not found in the gallery"
Install-Module PowerShellGet -Scope CurrentUser -RequiredVersion 2.2.5 -AllowClobber
}
if ((Get-InstalledModule -Name "AzureAD" -MinimumVersion 2.0.2.118 -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Installing AzureAD 2.0.2.118 as it was not found in the gallery"
Install-Module -Name AzureAD -Scope CurrentUser -RequiredVersion 2.0.2.118 -AllowClobber
}
if ((Get-InstalledModule -Name "ExchangeOnlineManagement" -MinimumVersion 2.0.3 -ErrorAction SilentlyContinue) -eq $null)
{
Write-Host "Installing ExchangeOnlineManagement 2.0.3 as it was not found in the gallery"
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -RequiredVersion 2.0.3 -AllowClobber
}
Import-Module PowerShellGet
Import-Module AzureAD
Import-Module ExchangeOnlineManagement
# Run interactively (will ask for the tenant ID and credentials if they are null)
ConfigureApplications -Credential $Credential -tenantId $TenantId -AzureEnvironmentName $AzureEnvironmentName -MailboxAccountEmail $MailboxAccountEmail
Yes, Microsoft actually updated their documentation a couple of days after my post and after I have had some interaction with Microsoft support team.
They provide a cmdlet on their documentation now (I assume your script is similar)
https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth