0
votes

I’m converting an existing PHPMailer app with Basic (userid and password) Authentication to use OAUTH2. Setup was easy but the app fails on authentication.

2020-05-23 15:14:59 Connection: opening to smtp.office365.com:587, timeout=300, options=array()
2020-05-23 15:14:59 Connection: opened
2020-05-23 15:14:59 SMTP INBOUND: "220 LO2P265CA0358.outlook.office365.com Microsoft ESMTP MAIL Service ready at Sat, 23 May 2020 15:14:58 +0000"
2020-05-23 15:14:59 SERVER -> CLIENT: 220 LO2P265CA0358.outlook.office365.com Microsoft ESMTP MAIL Service ready at Sat, 23 May 2020 15:14:58 +0000
2020-05-23 15:14:59 CLIENT -> SERVER: EHLO [my domain name]
2020-05-23 15:14:59 SMTP INBOUND: "250-LO2P265CA0358.outlook.office365.com Hello [91.208.99.2]"
2020-05-23 15:14:59 SMTP INBOUND: "250-SIZE 157286400"
2020-05-23 15:14:59 SMTP INBOUND: "250-PIPELINING"
2020-05-23 15:14:59 SMTP INBOUND: "250-DSN"
2020-05-23 15:14:59 SMTP INBOUND: "250-ENHANCEDSTATUSCODES"
2020-05-23 15:14:59 SMTP INBOUND: "250-STARTTLS"
2020-05-23 15:14:59 SMTP INBOUND: "250-8BITMIME"
2020-05-23 15:14:59 SMTP INBOUND: "250-BINARYMIME"
2020-05-23 15:14:59 SMTP INBOUND: "250-CHUNKING"
2020-05-23 15:14:59 SMTP INBOUND: "250 SMTPUTF8"
2020-05-23 15:14:59 SERVER -> CLIENT: 250-LO2P265CA0358.outlook.office365.com Hello [91.208.99.2]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-STARTTLS250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8
2020-05-23 15:14:59 CLIENT -> SERVER: STARTTLS
2020-05-23 15:14:59 SMTP INBOUND: "220 2.0.0 SMTP server ready"
2020-05-23 15:14:59 SERVER -> CLIENT: 220 2.0.0 SMTP server ready
2020-05-23 15:14:59 CLIENT -> SERVER: EHLO [my domain name]
2020-05-23 15:14:59 SMTP INBOUND: "250-LO2P265CA0358.outlook.office365.com Hello [91.208.99.2]"
2020-05-23 15:14:59 SMTP INBOUND: "250-SIZE 157286400"
2020-05-23 15:14:59 SMTP INBOUND: "250-PIPELINING"
2020-05-23 15:14:59 SMTP INBOUND: "250-DSN"
2020-05-23 15:14:59 SMTP INBOUND: "250-ENHANCEDSTATUSCODES"
2020-05-23 15:14:59 SMTP INBOUND: "250-AUTH LOGIN XOAUTH2"
2020-05-23 15:14:59 SMTP INBOUND: "250-8BITMIME"
2020-05-23 15:14:59 SMTP INBOUND: "250-BINARYMIME"
2020-05-23 15:14:59 SMTP INBOUND: "250-CHUNKING"
2020-05-23 15:14:59 SMTP INBOUND: "250 SMTPUTF8"
2020-05-23 15:14:59 SERVER -> CLIENT: 250-LO2P265CA0358.outlook.office365.com Hello [91.208.99.2]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-AUTH LOGIN XOAUTH2250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8
2020-05-23 15:14:59 Auth method requested: XOAUTH2
2020-05-23 15:14:59 Auth methods available on the server: LOGIN,XOAUTH2
2020-05-23 15:14:59 CLIENT -> SERVER: AUTH XOAUTH2 [long string of chars ending in == - base 64 encoded?] 
2020-05-23 15:15:04 SMTP INBOUND: "535 5.7.3 Authentication unsuccessful [LO2P265CA0358.GBRP265.PROD.OUTLOOK.COM]"
2020-05-23 15:15:04 SERVER -> CLIENT: 535 5.7.3 Authentication unsuccessful [LO2P265CA0358.GBRP265.PROD.OUTLOOK.COM]
2020-05-23 15:15:04 SMTP ERROR: AUTH command failed: 535 5.7.3 Authentication unsuccessful [LO2P265CA0358.GBRP265.PROD.OUTLOOK.COM]
SMTP Error: Could not authenticate.
2020-05-23 15:15:04 CLIENT -> SERVER: QUIT

My composer.json is just:

"require": {
    "phpmailer/phpmailer": "~6.1",
    "stevenmaguire/oauth2-microsoft": "2.2.0"

which then also drags in the League oauth2-client and others.

To avoid the issue of OAUTH2 only working for free consumer accounts and not O365 (see Issue 3 - stevenmaguire/oauth2-microsoft on github - and I have replicated the issue to confirm it), I have edited /src/Provider/Microsoft.php as follows:

  protected $urlAuthorize = 'https://login.microsoftonline.com/common/oauth2/authorize';
  protected $urlAccessToken = 'https://login.microsoftonline.com/common/oauth2/token';

(This uses the Graph V1 endpoint. Note that the latest Graph endpoint V2 is accessed via ... oauth2/v2.0/token and ...oauth2/v2.0/authorize and that either of these uses different Scope parameters that are incompatible with the old 'wl' Windows Live ones)

My relevant PHPMailer code is:
    $mail = new PHPMailer;
    $mail->isSMTP();                           // Set mailer to use SMTP
    $mail->Host = 'smtp.office365.com';        // Specify main and backup SMTP servers
    $mail->SMTPAuth = true;                    // Enable SMTP authentication
    $mail->AuthType = 'XOAUTH2';               // omit this to send using basic authentication
    $username = [my email address - as Azure AD signin];    
    $clientId = [from Azure AD - redacted];
    $clientSecret = ‘[from Azure AD - redacted];
    $refreshToken = [long char string – from running get_auth_token];

    $provider = new Microsoft (
        [
        'clientId' => $clientId ,
        'clientSecret' => $clientSecret
        ]
    );


    $mail->setOAuth(
        new OAuth(
            [
            'provider' => $provider,
            'clientId' => $clientId,
            'clientSecret' => $clientSecret,
            'refreshToken' => $refreshToken’,
            'userName' =>$username
            ]
        )
    );


    //$mail->Username = blah;                  // SMTP username – not needed for OAUTH2
    //$mail->Password = blah;                  // SMTP password – not needed for OAUTH2
    $mail->SMTPSecure = 'tls';                 // Enable TLS encryption, `ssl` also accepted
    $mail->Port = 587;  
    $mail->SMTPDebug = SMTP::DEBUG_LOWLEVEL;

My get_oauth_token.php contains:

$clientId = [my client ID];
$clientSecret = [my client secret]';
$redirectUri = [the full URI of get_oauth_token.php];

I have created an app in MSFT Azure AD of type ‘Web app’, taken the client ID and client secret from there, and added API permissions for SMTP.Send, Mail.Send, offline_access, and openid

If I can find where in the code the Azure AD authentication codes and messages (vide https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes#lookup-current-error-code-information) are returned, I'll be a step forward! I have tried the AD Monitor for sign-ins, but these particular unsuccessful attempts are not logged - nor the successful getting of the refresh token.

2
Take a look at this issue; it's very similar to yours.Synchro
Tnx Synchro. It seems a bit bizarre that I can successfully get a refresh token (via get_auth_token.php) that checks that all the required scopes are compatible with what is specified in the app's AD entry but fails authentication. in PHPMailer using exactly the same ClientID, ClientSecret and ClientRefreshToken !decomplexity
Well, like the resolution of that issue did, set a breakpoint somewhere like here and have a look at the state of things.Synchro

2 Answers

0
votes
    $mail = new PHPMailer;
    $mail->isSMTP();                           // Set mailer to use SMTP
    $mail->Host = 'smtp.office365.com';        // Specify main and backup SMTP servers
    $mail->SMTPAuth = true;                    // Enable SMTP authentication
    $mail->AuthType = 'LOGIN';               // omit this to send using basic authentication
   
    $mail->Username = '[email protected]';                  // SMTP username – not needed for OAUTH2
    $mail->Password = 'fdssdf';                  // SMTP password – not needed for OAUTH2
    $mail->SMTPSecure = 'starttls';                 // Enable TLS encryption, `ssl` also accepted
    $mail->Port = 587;  
    $mail->SMTPDebug = SMTP::DEBUG_LOWLEVEL;

Be careful with phpmailer, I was using 5.2 and in class.smtp it was encoding usermail twice, so my solution was delete that base64_encode in following CASE:

case 'LOGIN':
                // Start authentication
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
                    return false;
                }
                if (!$this->sendCommand("Username", ($username), 334)) {
                    return false;
                }
                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
                    return false;
                }
                break;
0
votes

The problem was eventually traced to MSFT's quirky implementation of OIDC/OAuth2 authentication that I know from posts on Stackoverflow and elsewhere has confused many developers. In effect MSFT had not completed the job. I have described this - and the solution - at some length in https://github.com/decomplexity/SendOauth2/blob/main/MSFT%20OAuth2%20quirks.md