Support for Modern Authentication (OAuth) for Email Notifications in PingCastle

What is a one sentence summary of your feature request?

Request for Support of Modern Authentication (OAuth) for SMTP in PingCastle

Please describe your idea in detail. What is your problem, why do you feel this idea is the best solution, etc.

We would like to formally request the implementation of modern authentication (OAuth) support for email notifications in PingCastle.

As of October 2024, we raised a support ticket on this subject (ref: 00424048), highlighting the urgent need to move away from basic authentication when connecting to M365 mailboxes.

In alignment with Microsoft’s security recommendations, our organization will disable basic authentication within the next two months. Without support for modern authentication, we will no longer be able to send email notifications via SMTP using PingCastle, which could impact our security monitoring workflows.

We are requesting a solution that would allow authentication via an Azure AD application or a modern, OAuth-compliant method to maintain compatibility with Microsoft 365.

We believe this enhancement is essential to ensure continued secure operation of PingCastle in modern enterprise environments, and we hope it will be prioritized in upcoming product updates.

Thank you for considering this request.

How do you currently solve the challenges you have by not having this feature?

Basic authentication is currently allowed under an exception, but it will be prohibited in two months.

1 Like

Hi there @RC-1234,
Apologies for not making much progress on this when you logged last year. It was in our backlog but had not got around to it yet.

I have just completed some testing and looks like we may be able to release it as a 3.4 hotfix once we get the initial build out over the next couple of weeks.

I think our solution for this will be to use an Application Registration to authenticate to the GraphAPI with ClientCredentials flow, then use the SendMail endpoint to do the sending of the emails. This is the basics then there is some setup in exchange using the Role Based Access for Applications flow to assign only the Mail.Send permission to the shared-mailbox that would be used for PingCastle to send emails.

Does this sound like it should work for you and your environment?

1 Like

Research has been completed and product requirements written. It is now with the development team to work on the feature.

ClientCredential flow with either Secret or Certificate should be able to be used.

Note: This will not make the 3.4.1 initial release but we will likely be able to do a small hotfix with this in after the release.

2 Likes

Hello Joe,

I would like to sincerely thank you for your support on this matter.

The proposed solution seems to be fully compatible with our environment.

Using the ClientCredential flow with an Application Registration, combined with the SendMail endpoint and a configuration through Role Based Access for Applications, aligns with what we are able to implement on our side.

We are counting on you to keep us informed about the next steps, as well as any requirements for testing or implementation.

Regards,
Roger

No problem at all! Glad to hear were on the same page :slight_smile:

I will upload all the detail here and in our docs when we come to release. I have already fully documented the Entra/Exchange setup and have a script which seems to work well for it too. Just need to await the actual implementation to finalize with the specifics.

Thanks a lot for the update, great to hear everything is progressing smoothly!

It’s reassuring to know that the Entra/Exchange setup is already documented and that a working script is available.
We will keep an eye out for the release and will review the documentation as soon as it’s available.

Don’t hesitate to let us know if you need anything from our side in the meantime.

Looking forward to the next steps!

Hello,

I hope you’re doing well.

I’m reaching out to follow up. It’s been about a month since our last exchange, and I haven’t received any updates since then.

Just wanted to check if there’s any news or if the documentation and script mentioned earlier are available yet. Please don’t hesitate to let us know if anything is needed from our side to help move things forward.

Looking forward to your feedback!

Best regards,
Roger

Doing good thanks! :slight_smile: Hope you are well too!

We have completed the initial work on making this technically work and now bug fixing and some other improvements that came from quality control such as adding to the installer and making the configuration simpler.

I believe the documentation and script are in a good place. Below are the current PowerShell options and manual guide that will make it to the docs when we release this. Please let me know if you think I am missing anything!

Create and Export Certificate (For EntraID Certificate Authentication rather than client secret)
# Create Self-Signed Certificate for use with Entra App Registration for dev environments.
$Name = "PingCastle-Email"
$password = "ENTER PASSWORD"

# Create a self-signed certificate
$cert = New-SelfSignedCertificate -Subject "CN=PingCastle-Email" -CertStoreLocation "Cert:\LocalMachine\My" -KeyExportPolicy Exportable

# Create a password for the PFX
$pwd = ConvertTo-SecureString -String $password -Force -AsPlainText

# Export the certificate as PFX
Export-PfxCertificate -Cert $cert -FilePath "$env:USERPROFILE\$Name.pfx" -Password $pwd

# Export the certificate as CER for Entra
Export-Certificate -Cert $cert -FilePath "$env:USERPROFILE\PingCastle-Email.cer" -Type CERT

Write-Output "Certificate exported to: $env:USERPROFILE\$Name.pfx"

Email Creation Function
<#
.SYNOPSIS
    Advanced PowerShell function to automate RBAC for Applications setup in Exchange Online

.DESCRIPTION
    This function automates the complete process of creating an Azure AD app registration,
    shared mailbox, and configuring RBAC for Applications in Exchange Online.

    Specifically designed for PingCastle-Email configuration.

.PARAMETER TenantId
    The Azure AD tenant identifier (GUID) where the application and service principal will be created.

.PARAMETER ClientSecretExpiration
    The lifetime of the client secret in months. Defaults to 12.

.PARAMETER SharedMailboxDomain
    The SMTP domain portion for the new shared mailbox (e.g. “contoso.com”).

.PARAMETER CertificateAuth
    Switch to enable certificate-based authentication instead of client secret.

.PARAMETER CertificatePath
    File system path to the certificate (PFX) to use when CertificateAuth is enabled.

.PARAMETER AppName
    The display name of the Azure AD application to create. Defaults to “PingCastle-Email”.

.PARAMETER ServicePrincipalName
    The name of the service principal for the application. Defaults to “PingCastle-Email”.

.PARAMETER ManagementScopeName
    The name of the custom role scope to assign to the service principal. Defaults to “PingCastle-Email”.

.PARAMETER SharedMailboxName
    The local part of the shared mailbox alias. Defaults to “pingcastle-email”.

.PARAMETER SharedMailboxDisplayName
    The display name for the shared mailbox. Defaults to “PingCastle-Email”.

.EXAMPLE
    Set-PingCastleEmailRBAC -TenantId "your-tenant-id"

.EXAMPLE
    Set-PingCastleEmailRBAC -TenantId "your-tenant-id" -CertificateAuth -CertificatePath "C:\Certs\pingcastle.pfx"

.NOTES
    Author: Joe Dibley
    Version: 1.0
    Requires: Exchange Online Management Module, Microsoft Graph PowerShell Module
#>

function Set-PingCastleEmailRBAC {
    [CmdletBinding()]
    param(
    [Parameter(Mandatory = $true)]
    [string] $TenantId,

    [Parameter(Mandatory = $false)]
    [int]    $ClientSecretExpiration = 12,

    [Parameter(Mandatory = $true)]
    [string] $SharedMailboxDomain,

    [Parameter(Mandatory = $false)]
    [switch] $CertificateAuth,

    [Parameter(Mandatory = $false)]
    [string] $CertificatePath,

    [Parameter(Mandatory = $false)]
    [string] $AppName                  = "PingCastle-Email",

    [Parameter(Mandatory = $false)]
    [string] $ServicePrincipalName     = "PingCastle-Email",

    [Parameter(Mandatory = $false)]
    [string] $ManagementScopeName      = "PingCastle-Email-Scope",

    [Parameter(Mandatory = $false)]
    [string] $SharedMailboxName        = "pingcastle-Email",

    [Parameter(Mandatory = $false)]
    [string] $SharedMailboxDisplayName = "PingCastle-Email"
)

    $SharedMailboxAddress = "$SharedMailboxName@$SharedMailboxDomain"

    # Results object to store all configuration details
    $Results = @{
        Success = $false
        AppRegistration = @{}
        SharedMailbox = @{}
        ServicePrincipal = @{}
        ManagementScope = @{}
        RoleAssignment = @{}
        TestResults = @{}
        Errors = @()
    }

    Write-Host "Starting PingCastle-Email RBAC Configuration..." -ForegroundColor Cyan
    Write-Host "=============================================" -ForegroundColor Cyan

    try {
        # Step 1: Check and install required modules
        Write-Host "Step 1: Checking required PowerShell modules..." -ForegroundColor Yellow

        $RequiredModules = @("Microsoft.Graph.Applications", "Microsoft.Graph.Users", "ExchangeOnlineManagement")

        foreach ($Module in $RequiredModules) {
            if (!(Get-Module -ListAvailable -Name $Module)) {
                Write-Host "Installing module: $Module" -ForegroundColor Green
                Install-Module -Name $Module -Force -AllowClobber -Scope CurrentUser
            }
            Import-Module -Name $Module -Force
        }

        # Step 2: Connect to Microsoft Graph
        Write-Host "Step 2: Connecting to Microsoft Graph..." -ForegroundColor Yellow

        $GraphScopes = @(
            "Application.ReadWrite.All",
            "Directory.ReadWrite.All",
            "User.ReadWrite.All"
        )

        Connect-MgGraph -TenantId $TenantId -Scopes $GraphScopes

        # Step 3: Create Azure AD App Registration
        Write-Host "Step 3: Creating Azure AD App Registration..." -ForegroundColor Yellow

        $AppRegistration = New-MgApplication -DisplayName $AppName -SignInAudience "AzureADMyOrg"

        if ($AppRegistration) {
            Write-Host "App Registration created successfully" -ForegroundColor Green
            $Results.AppRegistration = @{
                ApplicationId = $AppRegistration.AppId
                ObjectId = $AppRegistration.Id
                DisplayName = $AppRegistration.DisplayName
            }
        }

        # Step 4: Create Service Principal
        Write-Host "Step 4: Creating Service Principal..." -ForegroundColor Yellow

        $ServicePrincipal = New-MgServicePrincipal -AppId $AppRegistration.AppId

        if ($ServicePrincipal) {
            Write-Host "Service Principal created successfully" -ForegroundColor Green
            $Results.ServicePrincipal = @{
                ObjectId = $ServicePrincipal.Id
                AppId = $ServicePrincipal.AppId
                DisplayName = $ServicePrincipal.DisplayName
            }
        }

        # Step 5: Create Authentication Credential
        Write-Host "Step 5: Creating Authentication Credential..." -ForegroundColor Yellow

        if ($CertificateAuth -and $CertificatePath) {
            # Certificate-based authentication
            if (Test-Path $CertificatePath) {
                $Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificatePath)
                $KeyCredential = @{
                    Type = "AsymmetricX509Cert"
                    Usage = "Verify"
                    Key = $Certificate.RawData
                }

                Update-MgApplication -ApplicationId $AppRegistration.Id -KeyCredentials $KeyCredential
                Write-Host "Certificate credential added" -ForegroundColor Green

                $Results.AppRegistration.AuthenticationType = "Certificate"
                $Results.AppRegistration.CertificateThumbprint = $Certificate.Thumbprint
            } else {
                throw "Certificate file not found at: $CertificatePath"
            }
        } else {
            # Client secret authentication
            $ClientSecret = Add-MgApplicationPassword -ApplicationId $AppRegistration.Id -PasswordCredential @{
                DisplayName = "PingCastle-Email Secret"
                EndDateTime = (Get-Date).AddMonths($ClientSecretExpiration)
            }

            Write-Host "Client secret created (expires in $ClientSecretExpiration months)" -ForegroundColor Green
            $Results.AppRegistration.ClientSecret = $ClientSecret.SecretText
            $Results.AppRegistration.SecretId = $ClientSecret.KeyId
            $Results.AppRegistration.AuthenticationType = "ClientSecret"
        }

        # Step 6: Connect to Exchange Online
        Write-Host "Step 6: Connecting to Exchange Online..." -ForegroundColor Yellow

        Connect-ExchangeOnline -ShowBanner:$false

        # Step 7: Create Shared Mailbox
        Write-Host "Step 7: Creating Shared Mailbox..." -ForegroundColor Yellow

        # Check if mailbox already exists
        $ExistingMailbox = Get-Mailbox -Identity $SharedMailboxAddress -ErrorAction SilentlyContinue

        if (!$ExistingMailbox) {
            $SharedMailbox = New-Mailbox -Shared -Name $SharedMailboxDisplayName -PrimarySmtpAddress $SharedMailboxAddress -Alias $SharedMailboxName

            if ($SharedMailbox) {
                Write-Host "Shared mailbox created successfully" -ForegroundColor Green
                $Results.SharedMailbox = @{
                    DisplayName = $SharedMailbox.DisplayName
                    PrimarySmtpAddress = $SharedMailbox.PrimarySmtpAddress
                    Alias = $SharedMailbox.Alias
                    Created = $true
                }
            }
        } else {
            Write-Host "! Shared mailbox already exists" -ForegroundColor Yellow
            $Results.SharedMailbox = @{
                DisplayName = $ExistingMailbox.DisplayName
                PrimarySmtpAddress = $ExistingMailbox.PrimarySmtpAddress
                Alias = $ExistingMailbox.Alias
                Created = $false
            }
        }

        # Step 8: Block shared mailbox sign-in
        Write-Host "Step 8: Blocking shared mailbox sign-in..." -ForegroundColor Yellow

        $MailboxUser = Get-Mailbox -Identity $SharedMailboxAddress
        if ($MailboxUser.ExternalDirectoryObjectId) {
            Update-MgUser -UserId $MailboxUser.ExternalDirectoryObjectId -AccountEnabled:$false
            Write-Host "Shared mailbox sign-in blocked" -ForegroundColor Green
        }

        # Step 9: Create Service Principal in Exchange Online
        Write-Host "Step 9: Creating Service Principal in Exchange Online..." -ForegroundColor Yellow

        $ExoServicePrincipal = New-ServicePrincipal -AppId $AppRegistration.AppId -ObjectId $ServicePrincipal.Id -DisplayName $ServicePrincipalName

        if ($ExoServicePrincipal) {
            Write-Host "Exchange Online Service Principal created" -ForegroundColor Green
        }

        # Step 10: Create Management Scope
        Write-Host "Step 10: Creating Management Scope..." -ForegroundColor Yellow

        $ManagementScope = New-ManagementScope -Name $ManagementScopeName -RecipientRestrictionFilter "EmailAddresses -eq '$SharedMailboxAddress'"

        if ($ManagementScope) {
            Write-Host "Management Scope created" -ForegroundColor Green
            $Results.ManagementScope = @{
                Name = $ManagementScope.Name
                RecipientFilter = $ManagementScope.RecipientFilter
            }
        }

        # Step 11: Create Role Assignment
        Write-Host "Step 11: Creating Role Assignment..." -ForegroundColor Yellow

        $RoleAssignment = New-ManagementRoleAssignment -Role "Application Mail.Send" -App $ServicePrincipal.Id -CustomResourceScope $ManagementScopeName

        if ($RoleAssignment) {
            Write-Host "Role Assignment created" -ForegroundColor Green
            $Results.RoleAssignment = @{
                Name = $RoleAssignment.Name
                Role = $RoleAssignment.Role
                RoleAssignee = $RoleAssignment.RoleAssignee
                CustomResourceScope = $RoleAssignment.CustomResourceScope
            }
        }

        # Step 12: Test Configuration
        Write-Host "Step 12: Testing Configuration..." -ForegroundColor Yellow

        Start-Sleep -Seconds 30  # Wait for replication

        $TestResult = Test-ServicePrincipalAuthorization -Identity $ServicePrincipal.Id -Resource $SharedMailboxAddress

        if ($TestResult) {
            $Results.TestResults = @{
                RoleName = $TestResult.RoleName
                GrantedPermissions = $TestResult.GrantedPermissions
                InScope = $TestResult.InScope
                AllowedResourceScope = $TestResult.AllowedResourceScope
            }

            if ($TestResult.InScope -eq $true) {
                Write-Host "Configuration test passed - Service Principal has access to shared mailbox" -ForegroundColor Green
                $Results.Success = $true
            } else {
                Write-Host "âś— Configuration test failed - Service Principal does not have access to shared mailbox" -ForegroundColor Red
                $Results.Errors += "Test failed: Service Principal not in scope for shared mailbox"
            }
        }

        # Step 13: Display Summary
        Write-Host "`n" -NoNewline
        Write-Host "Configuration Summary" -ForegroundColor Cyan
        Write-Host "=====================" -ForegroundColor Cyan
        Write-Host "App Name: $AppName" -ForegroundColor White
        Write-Host "Application ID: $($AppRegistration.AppId)" -ForegroundColor White
        Write-Host "Object ID: $($ServicePrincipal.Id)" -ForegroundColor White
        Write-Host "Shared Mailbox: $SharedMailboxAddress" -ForegroundColor White
        Write-Host "Management Scope: $ManagementScopeName" -ForegroundColor White
        Write-Host "Authentication Type: $($Results.AppRegistration.AuthenticationType)" -ForegroundColor White

        if ($Results.AppRegistration.AuthenticationType -eq "ClientSecret") {
            Write-Host "Client Secret: $($Results.AppRegistration.ClientSecret)" -ForegroundColor Yellow
            Write-Host "WARNING: Save the client secret securely - it cannot be retrieved again!" -ForegroundColor Red
        }

        if ($Results.AppRegistration.AuthenticationType -eq "Certificate") {
            Write-Host "Certificate Thumbprint: $($Results.AppRegistration.CertificateThumbprint)" -ForegroundColor White
        }

        Write-Host "`nConfiguration completed successfully!" -ForegroundColor Green

    } catch {
        $Results.Success = $false
        $Results.Errors += $_.Exception.Message
        Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red
        throw
    } finally {
        # Disconnect from services
        Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
        Disconnect-MgGraph -ErrorAction SilentlyContinue
    }

    return $Results
}

RBAC for Applications Setup Guide: PingCastle Email Configuration

This guide provides step-by-step instructions for configuring Role Based Access Control (RBAC) for Applications in Exchange Online, specifically for the PingCastle-Email application to send emails from the shared mailbox pingcastle@stealthbitslab.com.

“PingCastle-Email” is used throughout this configuration. This is a name and can be substituted for anything.

Prerequisites

Before starting this configuration, ensure you have:

  • Global Administrator or Exchange Administrator permissions
  • Application Developer permissions in Azure AD
  • Exchange Online PowerShell module installed or use the Cloud Management Shell.
  • Microsoft Graph PowerShell module installed (optional, for advanced scenarios)

Part 1: Create Azure AD App Registration

Step 1: Access Microsoft Entra Admin Center

  1. Open a web browser and navigate to https://entra.microsoft.com
  2. Sign in with your administrator account
  3. If you have access to multiple tenants, use the Settings gear icon in the top menu to switch to the correct tenant

Entra admin center homepage with Settings menu highlighted

Step 2: Navigate to App Registrations

  1. In the left navigation pane, expand Identity
  2. Click on Applications
  3. Select App registrations
  4. Click + New registration at the top of the page

App registrations page with New registration button highlighted

Step 3: Configure Application Registration

  1. In the Name field, enter: PingCastle-Email
  2. Under Supported account types, select Accounts in this organizational directory only
  3. Leave Redirect URI (optional) blank for now
  4. Click Register

Register an application form with fields filled in

Step 4: Create Client Secret

  1. In the left menu under Manage, click Certificates & secrets
  2. Click + New client secret
  3. Add a description: PingCastle-Email Secret
  4. Set expiration to 12 months (or as per your policy)
  5. Click Add
  6. Important: Copy the secret Value immediately - it won’t be shown again
  7. Paste it in Notepad or a password manager for future use in this tutorial.

If you misplace your secret you can just come back to this screen and generate a new one

Client secrets page

Part 2: Create Shared Mailbox

Step 5: Access Exchange Admin Center

  1. Navigate to https://admin.exchange.microsoft.com
  2. Sign in with your Exchange administrator account
  3. In the left navigation, expand Recipients
  4. Click Mailboxes

Exchange Admin Center navigation

Step 6: Create Shared Mailbox

  1. Click + Add a shared mailbox
  2. Fill in the following details:
  • Display Name: PingCastle
  • Email Address: pingcastle (the domain should auto-populate with your domain)
  • Alias: pingcastle (optional)
  1. Click Create

Add a shared mailbox form with fields filled

Step 7: Verify Shared Mailbox Creation

  1. Wait for the mailbox creation process to complete
  2. Verify the mailbox appears in the mailboxes list
  3. Note the full email address: pingcastle@stealthbitslab.com

Mailboxes list showing the new shared mailbox

Step 8: Block Shared Mailbox Sign-in

This should automatically be completed but make sure to double check it.

  1. Navigate to https://entra.microsoft.com/
  2. Go to Users > All Users
  3. Search for and select the user account corresponding to the shared mailbox
  4. Click Edit Properties
  5. Click on the Settings tab
  6. Un-tick the Account Enabled checkbox
  7. Click Save

User properties page with Account Enabled set to false

Part 3: Configure RBAC for Applications

Step 9: Connect to Exchange Online PowerShell

  • Open Windows PowerShell as Administrator
  • Run the following commands
# Install Exchange Online Management module if not already installed
Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber

# Import the module
Import-Module ExchangeOnlineManagement

# Connect to Exchange Online
Connect-ExchangeOnline

Step 10: Create Service Principal

Using the values from your app registration, create the service principal:

# Define variables (replace with your actual values)

$AppId = "YOUR_APPLICATION_CLIENT_ID"
$ObjectId = "YOUR_APPS_SERVICE_PRINCIPAL_OBJECT_ID" # Get this from the Enterprise Applications screen in Entra ID. This is not the ObjectID of your App Registration!

# Create Service Principal
New-ServicePrincipal -AppId $AppId -ObjectId $ObjectId -DisplayName "PingCastle-Email"

Step 11: Create Management Scope

Create a management scope that restricts access to only the PingCastle shared mailbox:

# Create Management Scope  
$EmailAddress = "pingcastle@stealthbitslab.com" # The email address of the shared mailbox
New-ManagementScope -Name "PingCastle-Email-Scope" -RecipientRestrictionFilter "EmailAddresses -eq '$EmailAddress'"

Step 12: Assign Application Role

Assign the Application Mail.Send role to the service principal with the custom scope:

# Create Role Assignment
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)
New-ManagementRoleAssignment -Role "Application Mail.Send" -App $ObjectId -CustomResourceScope "PingCastle-Email-Scope"

Part 4: Test Configuration

Step 13: Test Service Principal Authorization

Verify the configuration works correctly:

# Test Service Principal Authorization
$EmailAddress = "pingcastle@stealthbitslab.com" # The email address of the shared mailbox
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)
 
Test-ServicePrincipalAuthorization -Identity $ObjectId -Resource $EmailAddress

Expected Output:

  • RoleName: Application Mail.Send
  • InScope: True

Step 14: Verify Scope Restriction

Test that the service principal cannot access other mailboxes:

# Test Service Principal Authorization
$EmailAddress = "" # A random email that the application should not be able to send as.
$ObjectId = "" # The Exchange Service Principal Object Id (This is output in Step 10)
 
Test-ServicePrincipalAuthorization -Identity $ObjectId -Resource $EmailAddress
1 Like

Hello Joe,

Super, thanks a lot!

Thank you for your feedback and for sharing all these details.
We will review everything and get back to you as soon as possible.

Have a great day!

1 Like

Hello,

First of all, please accept my apologies for the delay in getting back to you. With the summer holidays and a complex restart afterwards, our response took a little longer than expected.

Thank you very much for all the information and guidance you have shared with us so far.
On our side, we have finalized all the required configurations.

We are now waiting for your update regarding the patch deployment.
Please let us know if you need any further details or clarifications from our side.

Best regards,
Roger

Assuming everything is smooth our end we will release a new build on Oct 2nd with this and some other enhancements in.

1 Like

Super, thanks Joe. We will check, deploy and test it!