Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Azure XOAUTH2 help and examples #2793

Merged
merged 2 commits into from
Oct 16, 2022

Conversation

greew
Copy link
Contributor

@greew greew commented Oct 7, 2022

I hope it's quite self explanatory :)

@greew
Copy link
Contributor Author

greew commented Oct 7, 2022

Fixes #2788

@Synchro
Copy link
Member

Synchro commented Oct 7, 2022

Bravo, thank you very much. I'm sure that many will appreciate your considerable efforts here. I've pinged a few people who were running into Azure problems to see if they will give it a test before I merge it.

@greew
Copy link
Contributor Author

greew commented Oct 8, 2022

I forgot to add the example file - it has been committed now as well :)

@Synchro Synchro merged commit 3e38b34 into PHPMailer:master Oct 16, 2022
@Synchro
Copy link
Member

Synchro commented Oct 16, 2022

I didn't receive any feedback, however, you've done a great job here so I'm merging it anyway – if anyone has issues to report, I'm sure they will turn up eventually!

@greew
Copy link
Contributor Author

greew commented Oct 16, 2022

Well, Packagist says 25 installs overall, and I may have the first 5-10 during development.. so at least 15 people have started using it, and if neither you nor I have heard anything yet, let's hope it works well 😀

@adrianalumne
Copy link

Hello, I have been behind this topic of sending via PHPMailer, SMTP and Microsoft OAuth for a while without success.

I recently found your guide on how to correctly configure Azure Active Directory for sending emails and after carefully following your guide and testing the code I ran into several errors and it never works.

If I follow all the steps and keep this scope 'https://outlook.office.com/SMTP.Send' it doesn't give me any type of error, just this:

Auth method requested: XOAUTH2

Auth methods available on the server: LOGIN,XOAUTH2

CLIENT -> SERVER: QUIT

So I continued to investigate and found that I could use this scope: 'https://graph.microsoft.com/SMTP.Send' but it returns the following error:

AUTH command failed: 535 5.7.3 Authentication unsuccessful

What is this error due to? Is it a misconfiguration of Azure? Can outlook and office365 accounts send SMTP through Azure? How are those scopes different? I have also seen some scopes similar to this 'wl.smtp_send'. What does wl mean? Are they outdated?

I know there are a lot of questions but we have been looking for a solution for several months and we have only found your guide. Thanks greetings!

PS: The PHP code is similar to the one you use in your guide

@Synchro
Copy link
Member

Synchro commented Oct 21, 2022

I suspect you may only have SMTPDebug = 1 set, so you're only seeing client-side messages. Set it to 3 and see if you get some more clues.

@decomplexity
Copy link
Contributor

What permissions have you set in AAD against Graph resource? You need SMTP.Send.
And your client scope should indeed be https://outlook.office.com/SMTP.Send (preceded by offline_access to get a refresh token)

@greew greew deleted the ticket_2788/azure_xoauth2 branch October 21, 2022 19:09
@greew
Copy link
Contributor Author

greew commented Oct 21, 2022

@adrianalumne Both @Synchro and @decomplexity have already given some great pointers.

Give it a go - if it still fails, I'll be happy to see if I can help you further.

@adrianalumne
Copy link

Hello again! Thanks for your help @greew , regarding @Synchro's comment, we have changed SMTPDebug = 4 to SMTPDebug = 3 and this is the log it returns:

image

I don't really see any error messages and everything seems to be working fine.

It is true that by putting the scope mentioned by @decomplexity (https://outlook.office.com/SMTP.Send) the error that some messages indicated above no longer appears (AUTH command failed: 535 5.7.3 Authentication unsuccessful)

These are the permissions set in AAD:

image

Actually in the PHP code we only request these:

use Greew\OAuth2\Client\Provider\Azure;

$params = [
    'clientId' => $clientId,
    'clientSecret' => $clientSecret,
    'redirectUri' => $redirectURL,
    'tenantId'=> $tenantId,
    'accessType' => 'offline',
];
$provider = new Azure($params);
$options = [
    'scope' => [
        'offline_access',
        'https://outlook.office.com/SMTP.Send'
    ]
];

Any idea what to try? For more information, we are using:

  • CakePHP 2.10.24
  • PHP 8.0
  • PHPMailer 6.6.5

On the same server we have managed to get SMTP sending using Google's OAuth to work, so it is understood that the server is correctly configured for sending emails.
Thank you all!

@greew
Copy link
Contributor Author

greew commented Oct 24, 2022

Hi @adrianalumne

Have you managed to get a refresh_token successfully from Microsoft with before sending the mail?

@adrianalumne
Copy link

Hi @greew
Yes, we get an alphanumeric refresh_token and it starts like this:
0.AXoA4HdsoNQi_EOoT...

@decomplexity
Copy link
Contributor

Trivial point to check, but are you sure that this is a refresh token and not an authorization code? They both start in a similar way.

@adrianalumne
Copy link

Trivial point to check, but are you sure that this is a refresh token and not an authorization code? They both start in a similar way.

//Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken(
    'authorization_code',
    [
        'code' => $_GET['code']
    ]
);
//Use this to interact with an API on the users behalf
//Use this to get a new access token if the old one expires
$refreshToken = $token->getRefreshToken();
debug($refreshToken);
//0.AXoA4HdsoNQi_EOoT...

I guess that is correct, initially I retrieved the AccessToken and later the RefreshToken. It is right?

@decomplexity
Copy link
Contributor

Looks OK. Have a look at the Signin log in AAD :
select Enterprise Applications => =>Sign-in logs => User sign-ins (non-interactive) => [open an Aggregate if there is is one] =>select the latest one of two signins =>note the resource ID => [on the list top line select the "..." more => select Additional details => note the Oauth Scope Info.
Please let us know the two noted items. There might also be an error shown instead of Success

@adrianalumne
Copy link

Here are the requested data

00000002-0000-0ff1-ce00-000000000000

Oauth Scope Info: ["SMTP.Send"]

@decomplexity
Copy link
Contributor

Hmm.... those are both correct.
Could I then suggest that you display and copy the access token you are actually using. Then decode with e.g. jwt.io and check that the salient fields, especially 'aud' and 'scp' are the same as the ones in AAD signin.

To be sure it is the one really being used, you could add the following highlighted three lines temporarily to PHPMailer module OAuth.php:
protected function getToken()
{
$accesstoken = $this->provider->getAccessToken(
$this->getGrant(),
['refresh_token' => $this->oauthRefreshToken]
);
var_dump($accesstoken);
return $accesstoken;

}

and then execute a PHPMailer send.

@adrianalumne
Copy link

We have modified the file but without success, it seems that it does not reach that point. We have made a var_dump of $mail and it returns us the correct data and in the immediately following line is the $mail->send() but it seems that it is not executed.

Anyway, I wanted to ask you, on what occasion do you use the accessToken? we use the code that we receive through $_GET['code'], then with $token = $provider->getAccessToken() we obtain an object that, among other data, contains a refreshToken that is obtained with $token->getRefreshToken() and accessToken which is obtained with $token->getToken() but we do not use the latter and in your examples it is not mentioned either.

Perhaps we are mixing name and variables, everything has a similar name

@decomplexity
Copy link
Contributor

decomplexity commented Oct 24, 2022

You are correct. The refresh token is obtained in the way you suggest and plugged into PHPMailer via:
$mail->setOAuth(
new OAuth(
['refreshToken' => $refreshToken,
etc... ]
)
);

@adrianalumne
Copy link

Hi again,
We have been debugging the PHPMailer code to see at what point the connection fails and we have found the following.

In the src/Provider/AbstractProvider.php file, within the getParsedResponse() function we have retrieved the data from the $parsed variable (line 635) and it returns the following:

array(
	'error' => 'invalid_grant',
	'error_description' => 'AADSTS65001: The user or administrator has not consented to use the application with ID 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' named 'My PHPMailer app'. Send an interactive authorization request for this user and resource.
	Trace ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
	Correlation ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
	Timestamp: 2022-10-25 14:40:52Z',
	'error_codes' => array(
		(int) 0 => (int) 65001
	),
	'timestamp' => '2022-10-25 14:40:52Z',
	'trace_id' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
	'correlation_id' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
	'suberror' => 'consent_required'
)

What is this error due to? Is it a DAA thing? How could we solve it?

We are still stuck and unable to move forward, thanks for your help

@greew
Copy link
Contributor Author

greew commented Oct 25, 2022

@adrianalumne

That's weird... the very next thing after the getParsedResponse() is the checkResponse() method, which should throw an exception if the error index is set in the response.

@decomplexity
Copy link
Contributor

decomplexity commented Oct 25, 2022

Obvious point but has the user principal (usually the email address of the user who registered the app to AAD) granted Admin consent to the app in the permissions box (AAD => [select the app] => API permissions =>Configured permissions, then under Microsoft Graph you should have offline_access and SMTP.Send ]
You could try selecting Grant Admin Consent for your organisation (in case the user principal is not the one you logged on with but another account within the same tenant), but is not strictly necessary for these two permissions.

@greew
Copy link
Contributor Author

greew commented Oct 25, 2022

@decomplexity Great point!

In the walkthrough I made in the wiki article, I used the same user for both user principal and for oauth2 authentication.

@adrianalumne Let us know, if it helps using the "Grant Admin Consent" and I will update the wiki article with the updated data :)

@adrianalumne
Copy link

Hello! We have tried it, we have clicked on the button and the 'Status' column has been updated but nothing has changed, it is still showing the same error.

image

In the following path: /vendor/league/oauth2-client/src/Provider/AbstractProvider.php (line 636) we have added a debug of the response and the following appears:

object(GuzzleHttp\Psr7\Response) {
	[private] reasonPhrase => 'Bad Request'
	[private] statusCode => (int) 400
	[private] headers => array(
		'Cache-Control' => array(
			(int) 0 => 'no-store, no-cache'
		),
		'Pragma' => array(
			(int) 0 => 'no-cache'
		),
		'Content-Type' => array(
			(int) 0 => 'application/json; charset=utf-8'
		),
		'Expires' => array(
			(int) 0 => '-1'
		),
		'Strict-Transport-Security' => array(
			(int) 0 => 'max-age=31536000; includeSubDomains'
		),
		'X-Content-Type-Options' => array(
			(int) 0 => 'nosniff'
		),
		'P3P' => array(
			(int) 0 => 'CP="DSP CUR OTPi IND OTRi ONL FIN"'
		),
		'x-ms-request-id' => array(
			(int) 0 => 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
		),
		'x-ms-ests-server' => array(
			(int) 0 => '2.1.14006.8 - WEULR2 ProdSlices'
		),
		'X-XSS-Protection' => array(
			(int) 0 => '0'
		),
		'Set-Cookie' => array(
			(int) 0 => 'fpc=XXXXXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX; expires=Fri, 25-Nov-2022 07:45:50 GMT; path=/; secure; HttpOnly; SameSite=None',
			(int) 1 => 'x-ms-gateway-slice=estsfd; path=/; secure; samesite=none; httponly',
			(int) 2 => 'stsservicecookie=estsfd; path=/; secure; samesite=none; httponly'
		),
		'Date' => array(
			(int) 0 => 'Wed, 26 Oct 2022 07:45:50 GMT'
		),
		'Content-Length' => array(
			(int) 0 => '604'
		)
	)
	[private] headerNames => array(
		'cache-control' => 'Cache-Control',
		'pragma' => 'Pragma',
		'content-type' => 'Content-Type',
		'expires' => 'Expires',
		'strict-transport-security' => 'Strict-Transport-Security',
		'x-content-type-options' => 'X-Content-Type-Options',
		'p3p' => 'P3P',
		'x-ms-request-id' => 'x-ms-request-id',
		'x-ms-ests-server' => 'x-ms-ests-server',
		'x-xss-protection' => 'X-XSS-Protection',
		'set-cookie' => 'Set-Cookie',
		'date' => 'Date',
		'content-length' => 'Content-Length'
	)
	[private] protocol => '1.1'
	[private] stream => object(GuzzleHttp\Psr7\Stream) {
		[private] stream => resource
		[private] size => (int) 604
		[private] seekable => true
		[private] readable => true
		[private] writable => true
		[private] uri => 'php://temp'
		[private] customMetadata => array()
	}
}

The first two lines catch my attention, it seems that an error has occurred when making the request. Any ideas?

After this debug is the one that happened to you yesterday, it seems that the one from yesterday is a consequence of this.

PS: I have consulted and it seems that the account we are using is the same one that created the Azure Active Directory, so I understand that we are administrators

@decomplexity
Copy link
Contributor

After granting admin consent, have you regenerated the refresh token (whose settings in turn generate the access token)?
It is just a thought, because I cannot see why you should get a 'bad request' from an access token with insufficient privileges.
One other thing I can think of worth trying is to swap the provider for TheNetworg/oauth2-azure (Jan Hajek's) provider and see if you get the same error.

@adrianalumne
Copy link

Hello again, we have been reading all these messages and we have continued working to find a solution, but we are still the same, stuck in the same problem as @JosephAlonzo.

We have been trying the solutions that you proposed to him, but nothing, maybe he and we have the same problem?

I still think it may be a misconfiguration of the Azure account, some kind of subscription to send emails through SMTP or a failure to create the microsoft account. I am referring to the account itself, not the Azure portal, because it is rare to have administrator permissions missing, but that account is the administrator of the Azure portal. Can you think of any way to solve it?

@decomplexity
Copy link
Contributor

decomplexity commented Oct 28, 2022

Worth checking the following:
AAD portal (headed Overview) => Properties => (at bottom of panel) Access management for Azure resources.
For straightforward AAD use, your logon name should set to Yes below “can manage access to all Azure subscriptions and management groups in this tenant”

@adrianalumne
Copy link

Reviewed! It is the same email, it is correct so that is not the fault.

Now that it is mentioned, do we have to create groups or roles? we have not managed absolutely none of that, apart from that, is any extra configuration needed outside of AAD?

I mean in home page. Following your guide doesn't mention any extra steps outside of AAD, but just in case we're missing something...

@decomplexity
Copy link
Contributor

There is no need to create groups or roles for authorization_code grant flows under Delegated Permissions.

@JosephAlonzo
Copy link

JosephAlonzo commented Oct 28, 2022

I tried with another azure account and i don't have anymore the error of permission,
maybe something with azure config?
I'm using an azure student account

@joe-oems
Copy link

The 3rd link was broken on the wiki page, I went and fixed it myself thinking Github would stop me but it didn't. Maybe there should be some permissions? or is this an acceptable and trustful standard I just haven't seen before

@Synchro
Copy link
Member

Synchro commented Nov 18, 2022

The wiki has flipped in and out of being publicly writeable over the years, but for the most part they have been ok being open. The wiki is fully versioned, so changes can always be reversed. Leaving it open means that not everything is left to maintainers (i.e. me!)

@dataconnectnl
Copy link

dataconnectnl commented Nov 22, 2022

Got a question. I've got this working. However something isnt clear in my mind. If I generate a secret token it's only the token for the account which i'm logged in with to generate the token. So for info@ it's a different token then support@. I know it's the case because I already tested it and with both I can mail with their own tokens. But why not one token for all mail accounts that are granted access (owners) in de APP in Azure. Something I missed? Or is it just how it works?

Hope that microsoft is thinking of replacing that stupid credentials screen for code solution...

@witpok
Copy link

witpok commented Mar 7, 2023

@adrianalumne could you please respond if you have managed to get it working? I am setting everything exactly as specified on the wiki page, yet no success with sending emails. 535 5.7.3 Authentication unsuccessful
Application created by the same user as sender, global admin. User sign-ins (not-interactive) list correctly each attempt to send email, status success, additional details:
Root Key Type Unknown
Oauth Scope Info ["SMTP.Send"]
All permissions set as described, user.read, offline_access, SMTP.send, mail.send, even mail.send.shared, all delegated.
If anyone has working example and is able to send emails via smtp and ms accounts, I would be grateful to have a look. Thanks

@decomplexity
Copy link
Contributor

A scope of "SMTP.Send" will force your resource API to be Graph. And Graph does not support SMTP.Send itself.
Try a scope of:

"offline_access https://outlook.office.com/SMTP.Send"

If this sounds bizarre, it is! See the explanation in "Microsoft OAuth2 SMTP issues" in the PHPMailer Wiki
And don't feel tempted to add any other permissions to the Scope; it can be done, but you need to understand some undocumented MSFT rules, one of which is that the order of scope permissions matters

@witpok
Copy link

witpok commented Mar 7, 2023

Thank you for such a quick response. I have:
$provider = new Azure( [ 'clientId' => $clientId , 'clientSecret' => $clientSecret, 'tenantId' => $tenantId ] ); $provider->scope = 'offline_access https://outlook.office.com/SMTP.Send';
Is this a correct way to set a scope with Greew\OAuth2? The result is still the same, Authentication unsuccessful.

@decomplexity
Copy link
Contributor

And what operands are you using in your global PHPMailer code when instantiating OAuth in (e.g.)

               new OAuth(
                   [ ?? 
                   ]

@witpok
Copy link

witpok commented Mar 7, 2023

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

@decomplexity
Copy link
Contributor

Try:

  • specifing your app to AAD as multitenant. If you are specifying the tenant ID, double-check the tenant value

    • sign in to AAD
    • select Applications
    • select App registrations
    • select your app
    • look in the central area near the top and copy Directory (tenant) ID
  • changing your provider from Greew to TheNetworg – this is the one we use for all our sites

  • double checking that the token you are copying from the initial authorization-flow run to plug into PHPMailer is actually a refresh token and not an access token or an authorization code. Access tokens can be decoded with jwt.io or jwt.ms

@witpok
Copy link

witpok commented Mar 8, 2023

Here is my full script for email sending, removed some specific internal parts that are not needed here.

chdir("phpmailer/vendor");
require 'autoload.php';

use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\OAuth;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;

use Greew\OAuth2\Client\Provider\Azure;
//use TheNetworg\OAuth2\Client\Provider\Azure;
//use Stevenmaguire\OAuth2\Client\Provider\Microsoft; <-- no matter which one used, I can connect via provider (so clientId, tenantId and secret are correct) but SMTP authentication unsuccessful error still appears

date_default_timezone_set('Etc/UTC');

$host = "smtp.office365.com";

$smtp = new PHPMailer();
$smtp->isSMTP();
$smtp->Host = $host;             
$smtp->Port = 587;  
$smtp->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$smtp->SMTPAuth = true;
$smtp->AuthType = 'XOAUTH2'; 
$username = 'username@microsoft.com'; //both creator of the app, global administrator and email sender, to simplify the use case

$clientId = 'client id';
$clientSecret = 'client secret';
$tenantId = 'tenant id';

$redirectUri = 'https://mydomain/phpmailer/get_oauth_token.php';

$refreshToken = '0.ATsAJUw1LGBtTUgf2acU....'; // received under $redirectUri  with the same $clientId, $clientSecret, $tenantId  as above

$provider = new Azure(
     [
     'clientId' => $clientId ,
     'clientSecret' => $clientSecret,
     'tenantId' => $tenantId
     ]
);
$provider->scope = 'offline_access https://outlook.office.com/SMTP.Send'; // scope or scopes, no difference. In Azure.php in greew library there are defaults as below:
/* $defaultScopes = [
        'https://outlook.office.com/SMTP.Send',
        'offline_access'
    ];
*/


//$provider->urlAPI = 'https://outlook.office.com'; // tried with this one, no changes

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

$smtp->addAddress('recipients email address');
$smtp->isHTML(true);
$smtp->Subject = 'test subject';
$smtp->Body = 'email body';

$smtp->setFrom($username);

$mail = $smtp->send();
echo $mail->ErrorInfo;
echo 'sending... ';
?>

@witpok
Copy link

witpok commented Mar 8, 2023

I have just changed from single-tenant to multi-tenant:
obraz
Tenant ID is correct from Directory (tenant) ID
obraz
Changed to use TheNetworg\OAuth2\Client\Provider\Azure;
Debugged refreshToken on jwt.io, can't be debugged.

I am constantly trying again, as maybe some changes need time to propagate. I will try with creating new refresh token after change tu multi-tenant.

@decomplexity
Copy link
Contributor

Other things to check:

  1. Whether SMTP AUTH is ‘allowed’ in Admin Center / Active Users / [select user] / Mail tab / Manage email apps

  2. Your ISP is not blocking external SMTP endpoints such as smtp.office365.com. GoDaddy and other ISPs who use GoDaddy cpanel services block such SMTP access: they insist you go via their localhost. If you are blocked in this way, it should be apparent from the PHPMailer diagnostics in $this->mail->SMTPDebug = SMTP::DEBUG_LOWLEVEL is set (I believe you have this already set).

  3. If you haven’t looked already, it is worth accessing the AAD sign-in logs to see if there is anything of interest there. After you have signed in once, the MSFT authorization-code server should think that it has already passed you an authorization code when it calls the redirectURI (the code is suffixed to the URI).
    Try:

  • sign in to AAD
  • select Enterprise Applications
  • select your app
  • select Sign-in Logs (near the bottom of the vertical list)
  • select User-sign-ins (non-interactive)
  • select Refresh
  • select the Request ID ‘Aggregate’ item for today’s date and for Resource ‘Office 365 Exchange Online’, 'Graph' or other request you wish to check
  • select the event for the time of your last failed event (or any other event in which you are interested)
    This may have some useful information.

Then also:

  • select the ‘…’ on the RHS of the title
  • see the Oauth Scope Info field in the Additional Details tab
  1. To check if the access token itself causes the problem, you could try inserting the token you got from the initial authorization flow run directly into PHPMailer’s Oauth.php module. Since access tokens have a short life, it is best to do this fairly quickly.
    Something like:

a. Get the initial access token from

$token = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code']]

b. plug it (temporarily!) into Oauth.php:

protected function getToken()
    {
        return <your initial access token>;
   }

It is worth noting that whereas the authentication code (in the initial authorization flow run) is allowed to contain clashing scopes, multiple resources and so on, the token endpoint imposes more stringent criteria when generating an access token. Exactly why is known only to MSFT!
The implication is that you could have a successful initial authorization flow run that generates a refresh token that is more restrictive; but exactly how restrictive it is only becomes apparent later when it is exchanged for an access token (which Oauth2.php fails to authenticate)

@witpok
Copy link

witpok commented Mar 8, 2023

  1. In https://portal.office.com/Adminportal this user has Authenticated SMTP checked (every other option as well). This user did not previously had a mailbox created, but now it has and I have successfully sent an email from the mailbox on https://outlook.office365.com/. No changes with phpmailer unfortunately, even with new refreshtoken.
  2. debug was set to level 3, changed to yours and there is more information, such as Auth method requested: XOAUTH2, methods available on the server: LOGIN,XOAUTH2, then some call with CLIENT -> SERVER: AUTH XOAUTH2 [some long token here], and then SMTP INBOUND: "535 5.7.3 Authentication unsuccessful, then SERVER -> CLIENT: 535 5.7.3 Authentication unsuccessful, then SMTP ERROR: AUTH command failed: 535 5.7.3 Authentication unsuccessful, SMTP Error: Could not authenticate, Quit, Service closing transmission channel, closed. SMTP connect() failed. I don't think ISP is blocking anything, as we are still using the old way smtp to send emails from ms accounts and everything still works, even if MS was to shut it down in January.
  3. Yes, I have checked Sign-in logs, everyt attempt has status Success, in Additional details Oauth Scope Info ["Mail.Send","Mail.Send.All","Mail.Send.Shared","SMTP.Send","User.Read"]
    In authentication details and basic information everything looks fine to me
    obraz
  4. I will do the tests, decode the access token and comment below. Thank you for all the help, I really appreciate it.

@witpok
Copy link

witpok commented Mar 8, 2023

    • success! after I have copied the access token received from get_oauth_token.php into phpmailer/src/Oauth.php getToken, I got Authentication successful and I have received the email on recipients inbox. Now, what do we do with this to make it work?

@decomplexity
Copy link
Contributor

Why not echo the access token that is produced from the initial run (the run that produced the refresh token) and also echo the token normally used within Oauth.php (i.e. the token generated from the refresh token). Then run each through jwt.io or jwt.ms (you may need to debase64 them) and post the contents of the ‘aud’ and ‘scope’ fields – or since the tokens expire after 90 minutes or less, just post the tokens.

@witpok
Copy link

witpok commented Mar 8, 2023

I have compared two access tokens and to my surprise - with both of them authentication now passes through, emails are sent. Either the first correct token pasted into OAuth.php made the path open for following tokens or some previous changes made in the administration panels finally propagated. I'm baffled, can't explain it. The only differences in both tokens are times (iat, nbf, exp), and fields aio and uti. Scope is the same, everything else is the same. Aud is "https://outlook.office.com", scope is Mail.Send Mail.Send.All Mail.Send.Shared SMTP.Send User.Read.
I will repeat tests tomorrow, when tokens expire to be sure if it is really working.
If I were now send emails as other accounts, do I need to add my main account in https://admin.exchange.microsoft.com ->Manage Mailboxes->mailbox to be sent from->Delegation->Send as for each mailbox or set it up somewhere else? Right now I have 5.2.252 SendAsDenied if I try to change setFrom address, and Authentication unsuccessful if I try to change $username.

@decomplexity
Copy link
Contributor

Since you have an ‘aud’ of https://outlook.office.com/ in both tokens, that is half the battle of getting SMTP authentication to work! As I mentioned earlier in this thread, your client scope normally only needs to be:

"offline_access https://outlook.office.com/SMTP.Send"

Re the setup for allowing other users (within the tenant presumably) to ‘send as’ the base mailbox, two points:

  • the base mailbox becomes a shared mailbox, and shared mailboxes run disabled from login, which might not be what you want if the base mailbox is an admin one!
  • do the other users want to send as themselves (‘Send On Behalf Of’ – which is set using a cmdlet) or simply use the shared mailbox as a common address to send from (Send As) ?

Without experimenting, I really can’t say whether adding delegates to a shared mailbox in Send As mode and then hoping to allow each to use PHPMailer to send via SMTP will work even in principle. Logically it shouldn’t.
But anyway, there should be no salient difference to the email recipient in sending via SMTP from ANY user (whether in the tenant or not – as in a website Contact form for example) in the usual way PHPMailer is used as a central daemon application, as the one central shared email address is used by this and by Send As. To distinguish each different sender you should be able to use different email name fields as in <John Doe> Mydept@Deer.com, where there can be umpteen different John Does but only one Mydept@Deer.com

@witpok
Copy link

witpok commented Mar 9, 2023

After leaving it for the night, this morning "Send as" delegation started to work without a touch, so information for everyone - be patient, changes on MS side can take much more time than described in the panel (5 minutes). Thank you for all the help, I hope this will be of use for everyone else fighting with this.

@Piotr21
Copy link

Piotr21 commented Apr 26, 2023

Hello,
has anyone had a similar error? When calling the script from the file http://localhost:8080/get_oauth_token.php

Fatal error: Uncaught League\OAuth2\Client\Provider\Exception\IdentityProviderException: Unauthorized in...
...p_carrer__form-php8/api/form/vendor/greew/oauth2-azure-provider/src/Provider/Azure.php on line 89

@decomplexity
Copy link
Contributor

decomplexity commented Jun 13, 2023 via email

@marcusBamboo
Copy link

Hey, thanks. I got it to work, I found out that I had an old script loaded where I was setting the scope to the old params.

@matt-t-tinkers
Copy link

Is there a route to achieving something similar but without the deprecated SMTP Auth?

@br4nnigan
Copy link

@greew

can you help me out maybe? I did everything as described in the tutorial, except that I don't know how to get "the user that gave consent" userName parameter ($provider->getResourceOwner($token) yields a "Access token is empty" error). But even if I hard code that to the e-mail I gave consent with, I still get the same error:

AADSTS9002313: Invalid request. Request is malformed or invalid. Trace ID: 3f2d3ced-7966-4d9d-b33a-28196f6b3302 Correlation ID: 9c436b3c-a277-4197-96c9-9aced7d81d88 Timestamp: 2024-04-08 16:10:13Z2024-04-08 16:10:13 SERVER -> CLIENT: 220 FR5P281CA0031.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 8 Apr 2024 16:10:12 +0000
\n2024-04-08 16:10:13 CLIENT -> SERVER: EHLO mydomain.org
\n2024-04-08 16:10:13 SERVER -> CLIENT: 250-FR5P281CA0031.outlook.office365.com Hello [85.13.166.xxx]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-STARTTLS250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8
\n2024-04-08 16:10:13 CLIENT -> SERVER: STARTTLS
\n2024-04-08 16:10:13 SERVER -> CLIENT: 220 2.0.0 SMTP server ready
\n2024-04-08 16:10:13 CLIENT -> SERVER: EHLO mydomain.org
\n2024-04-08 16:10:13 SERVER -> CLIENT: 250-FR5P281CA0031.outlook.office365.com Hello [85.13.166.xxx]250-SIZE 157286400250-PIPELINING250-DSN250-ENHANCEDSTATUSCODES250-AUTH LOGIN XOAUTH2250-8BITMIME250-BINARYMIME250-CHUNKING250 SMTPUTF8

If I use SSL instead of TLS the smtp connect times out.

@decomplexity
Copy link
Contributor

Probably something wrong with your refresh token.
Have a look at the AAD sign-in logs.

  • sign in to AAD
  • select Enterprise Applications
  • select your app
  • select Sign-in Logs (near the bottom of the vertical list)
  • select User-sign-ins (non-interactive)
  • select Refresh
  • select the Request ID ‘Aggregate’ item for today’s date and for Resource ‘Office 365 Exchange Online’, or 'Graph'
  • select the event for the time of your last failed event
    This may have some useful information.

Then also:

  • select the ‘…’ on the RHS of the title
  • see the OAuth Scope Info field in the Additional Details tab.

Let us have the Resource shown in the basic tab and the Scope shown in the Additional Details tab

@br4nnigan
Copy link

Thank you, pointed me in the right direction! the error was on my side, the token was truncated in the DB because of the size of the field...

next I only had to enable SMTP auth as described here, which might be useful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet