Search code examples
outlookoauthmicrosoft-graph-apioffice365phpmailer

Getting "invalid_client" when using obtained oauth token (Microsoft) with phpmailer


Can't seem to get this working. Any idea why?

This is my MS configuration:

authentication

api/permissions

token configuration (empty)

"expose an api" (empty)

Code obtaining token (works fine):

new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([
    'clientId'                  => "my_id...",
    'clientSecret'              => "my_secret...",
    'redirectUri'               => "https://example.com/get/get_oauth_token.php",
    'accessType'                => 'offline',
    // Optional (what is this doing??)
    'urlAuthorize'              => 'https://login.windows.net/common/oauth2/authorize',
    'urlAccessToken'            => 'https://login.windows.net/common/oauth2/token',
    'urlResourceOwnerDetails'   => 'https://outlook.office.com/api/v1.0/me'
    ]);
$authUrl = $provider->getAuthorizationUrl([
    'scope' => ['wl.basic', 'wl.signin', 'wl.emails'] // no idea what I need here and what not - does it have to align with the app permissions?
]);
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);

Using token to send mail:

$mail = new PHPMailer();
$mail->isSMTP();
$mail->Timeout = 10;
$mail->Host = "smtp.office365.com"; // smtp.office365.com, outlook.office.com, smtp-mail.outlook.com which one is it even?? Doesnt make a difference
$mail->Port = 587;
$mail->SMTPSecure = "tls";
$mail->SMTPAuth = true;
$mail->AuthType = 'XOAUTH2';

// (omitting all mail content stuff here)

$provider = new \Stevenmaguire\OAuth2\Client\Provider\Microsoft([
    'clientId'                  => "my_id...",
    'clientSecret'              => "my_secret...",
    'redirectUri'               => "https://example.com/get/get_oauth_token.php" // is this necessary here?
]);

//Pass the OAuth provider instance to PHPMailer
$mail->setOAuth(
    new OAuth([
        'provider' => $provider,
        'clientId' => "my_id...",
        'clientSecret' => "my_secret...",
        'refreshToken' => "my_refreshToken...",
        // 'oauthToken' => $token, // ? are we completely ignoring the actual access token in all of this? do we ever need to update our refresh token?
        'userName' => 'me@mycompany.onmicrosoft.com' // supposed to be user that gave consent? 
    ])
);

$mail->send();
$mail->SmtpClose();

Getting this error:

2022-09-22 17:49:08 SERVER -> CLIENT: 220 FR0P281CA0070.outlook.office365.com Microsoft ESMTP MAIL Service ready at Thu, 22 Sep 2022 17:49:07 +0000

2022-09-22 17:49:08 CLIENT -> SERVER: EHLO example.com

2022-09-22 17:49:08 SERVER -> CLIENT: 250-FR0P281CA0070.outlook.office365.com Hello [85.13.129.169]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-STARTTLS250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8

2022-09-22 17:49:08 CLIENT -> SERVER: STARTTLS

2022-09-22 17:49:08 SERVER -> CLIENT: 220 2.0.0 SMTP server ready

2022-09-22 17:49:08 CLIENT -> SERVER: EHLO example.com

2022-09-22 17:49:08 SERVER -> CLIENT: 250-FR0P281CA0070.outlook.office365.com Hello [85.13.129.169]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-AUTH LOGIN XOAUTH2250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8

Bad Request
/www/htdocs/w014af32/me/vendor/stevenmaguire/oauth2-microsoft/src/Provider/Microsoft.php(79)
    #0 /www/htdocs/w014af32/me/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(628): Stevenmaguire\OAuth2\Client\Provider\Microsoft->checkResponse(Object(GuzzleHttp\Psr7\Response), Array)
    #1 /www/htdocs/w014af32/me/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(537): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request))
    #2 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/OAuth.php(115): League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object(League\OAuth2\Client\Grant\RefreshToken), Array)
    #3 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/OAuth.php(128): PHPMailer\PHPMailer\OAuth->getToken()
    #4 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/SMTP.php(598): PHPMailer\PHPMailer\OAuth->getOauth64()
    #5 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(2190): PHPMailer\PHPMailer\SMTP->authenticate('', '', 'XOAUTH2', Object(PHPMailer\PHPMailer\OAuth))
    #6 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(2003): PHPMailer\PHPMailer\PHPMailer->smtpConnect(Array)
    #7 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(1662): PHPMailer\PHPMailer\PHPMailer->smtpSend('Date: Thu, 22 S...', 'test content\r\n')
    #8 /www/htdocs/w014af32/me/vendor/phpmailer/phpmailer/src/PHPMailer.php(1500): PHPMailer\PHPMailer\PHPMailer->postSend()
    #9 /www/htdocs/w014af32/me/includes/helpers/mail.php(182): PHPMailer\PHPMailer\PHPMailer->send()
    #10 /www/htdocs/w014af32/me/includes/helpers/mail.php(113): mailSendSingle(Object(PHPMailer\PHPMailer\PHPMailer), Array, true)
    #11 /www/htdocs/w014af32/me/get/test4.php(25): mailSend(Array)
    #12 {main}
    # Args: 
        checkResponse: [{},{"error":"invalid_client","error_description":"The client does not exist or is not enabled for consumers. If you are the application developer, configure a new application through the App Registrations in the Azure Portal at https:\/\/go.microsoft.com\/fwlink\/?linkid=2083908.","correlation_id":"4dc377bd-0d5d-4444-bdd4-82b862f86177"}]
        getParsedResponse: [{}]
        getAccessToken: [{},{"refresh_token":"my refreshToken..."}]
        authenticate: ["","","XOAUTH2",{}]
        smtpConnect: [{"ssl":{"verify_peer":false,"verify_peer_name":false,"allow_self_signed":true}}]
        smtpSend: ["Date: Thu, 22 Sep 2022 19:49:08 +0200\r\nTo: John Doe <me@example.com>\r\nFrom: John Doe <me@mycompany.onmicrosoft.com>\r\nReply-To: John Doe <me@mycompany.onmicrosoft.com>\r\nSubject: test mail\r\nMessage-ID: <AAtH5DrcPQ9Fn4PysOdjIt0OnDbByC4eaRYVnJh33M@example.com>\r\nX-Mailer: PHPMailer 6.6.4 (https:\/\/github.com\/PHPMailer\/PHPMailer)\r\nMIME-Version: 1.0\r\nContent-Type: text\/html; charset=utf-8\r\n\r\n","test content\r\n"]
        mailSendSingle: [{"Priority":null,"CharSet":"utf-8","ContentType":"text\/html","Encoding":"7bit","ErrorInfo":"","From":"me@mycompany.onmicrosoft.com","FromName":"John Doe","Sender":"me@mycompany.onmicrosoft.com","Subject":"test mail","Body":"test content","AltBody":"","Ical":"","WordWrap":0,"Mailer":"smtp","Sendmail":"\/usr\/sbin\/sendmail","UseSendmailOptions":true,"ConfirmReadingTo":"","Hostname":"","MessageID":"","MessageDate":"","Host":"smtp.office365.com","Port":"587","Helo":"","SMTPSecure":"tls","SMTPAutoTLS":true,"SMTPAuth":true,"SMTPOptions":{"ssl":{"verify_peer":false,"verify_peer_name":false,"allow_self_signed":true}},"Username":"","Password":"","AuthType":"XOAUTH2","Timeout":10,"dsn":"","SMTPDebug":2,"Debugoutput":"html","SMTPKeepAlive":false,"SingleTo":false,"do_verp":false,"AllowEmpty":false,"DKIM_selector":"","DKIM_identity":"","DKIM_passphrase":"","DKIM_domain":"","DKIM_copyHeaderFields":true,"DKIM_extraHeaders":[],"DKIM_private":"","DKIM_private_string":"","action_function":"","XMailer":""},{"from_mail":"me@mycompany.onmicrosoft.com","from_name":"John Doe","smtp_profile":3,"from_user":1,"to_mail":"me@example.com","to_name":"John Doe","to_type":"staff","to_id":3,"template":0,"debug":true,"testing":true,"subject":"test mail","type":"test","date":"2022-09-22 19:49:08","content":"test content","reply_to":"me@mycompany.onmicrosoft.com"},true]
        mailSend: [{"from_mail":"me@mycompany.onmicrosoft.com","from_name":"John Doe","smtp_profile":3,"from_user":1,"to_mail":"me@example.com","to_name":"John Doe","to_type":"staff","to_id":3,"template":0,"debug":true,"testing":true,"subject":"test mail","type":"test","date":"2022-09-22 19:49:08","content":"test content","reply_to":"me@mycompany.onmicrosoft.com"}]
2022-09-22 17:49:08 CLIENT -> SERVER: QUIT

2022-09-22 17:49:08 SERVER -> CLIENT: 221 2.0.0 Service closing transmission channel

I know it's a lot of code but there isn't really much to say, I could ommit the whole token obtaining because it works. I assume the error lies somewhere in the azure configuration? The user I've obtained the token with is just a "member" (Role "Global administrator"), the "owner" of the app is another one.


Solution

  • using these options for the provider (in both places, aquiring token and sending mail) did it (it's the tenant id):

    'urlAuthorize' => 'https://login.microsoftonline.com/b3095h58-5987-4489-a1dd-b132f2f38113/oauth2/authorize',
    'urlAccessToken' => 'https://login.microsoftonline.com/b3095h58-5987-4489-a1dd-b132f2f38113/oauth2/token'
    

    It seemed irrelevant whether to use windows.net or microsoftonline.com

    However now I'm at the same point as described here. Funnily even using Thunderbird to test the oauth authentification fails for sending (receiving is fine though).