php-imap
php-imap copied to clipboard
Can't connect to Outlook OAuth IMAP
I can't connect to Outlook OAuth IMAP
I am using Laravel and the the latest version:
"webklex/laravel-imap": "2.4"
- Here I redirect to microsoft to get the Auth token:
public function getToken(Request $request, Workspace $workspace, EmailAccount $emailAccount)
{
$provider = MicrosoftOathController::getProvider();
$authUrl = $provider->getAuthorizationUrl();
$request->session()->flash('oauth2state', $provider->getState());
$request->session()->flash('microsoftOathEmailAccountID', $emailAccount->id);
$request->session()->save();//need to call this before a redirect
header('Location: '.$authUrl);
exit;
}
- Here is how I get the provider:
public static function getProvider()
{
return new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => env('MICROSOFT_CLIENT_ID'),
'clientSecret' => env('MICROSOFT_CLIENT_SECRET'),
'redirectUri' => env('MICROSOFT_REDIRECT_URI'),
'urlAuthorize' => env('MICROSOFT_AUTHORITY_URL') . env('MICROSOFT_AUTHORIZE_ENDPOINT'),
'urlAccessToken' => env('MICROSOFT_AUTHORITY_URL') . env('MICROSOFT_TOKEN_ENDPOINT'),
'urlResourceOwnerDetails' => '',
'scopes' => env('MICROSOFT_SCOPES')
]);
}
- These are my config settings
MICROSOFT_CLIENT_ID=[REDACTED]
MICROSOFT_CLIENT_SECRET=[REDACTED]
MICROSOFT_REDIRECT_URI=http://localhost:8000/microsoft-oath-callback
MICROSOFT_AUTHORITY_URL=https://login.microsoftonline.com/common
MICROSOFT_AUTHORIZE_ENDPOINT=/oauth2/v2.0/authorize
MICROSOFT_TOKEN_ENDPOINT=/oauth2/v2.0/token
MICROSOFT_RESOURCE_ID=https://graph.microsoft.com
MICROSOFT_SENDMAIL_ENDPOINT=/v1.0/me/sendmail
MICROSOFT_SCOPES='profile openid email offline_access User.Read Mail.Send https://outlook.office.com/IMAP.AccessAsUser.All'
- Here I get the token:
public function callback(Request $request)
{
$provider = MicrosoftOathController::getProvider();
// Check given state against previously stored one to mitigate CSRF attack
if (empty($request->input('state')) || ($request->input('state') !== session('oauth2state'))) {
exit('Invalid state');
} elseif ($request->has('error')) {
dd($request->input('error_description'));
} else {
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken('authorization_code', [
'code' => $request->input('code')
]);
$emailAccount = EmailAccount::findOrFail(session('microsoftOathEmailAccountID'));
$emailAccount->microsoft_access_token = $token->getToken();
$emailAccount->microsoft_access_token_updated_at = now();
$emailAccount->save();
//redirect back to the email account config page
return redirect('/workspaces/' . $emailAccount->workspace->rid . '/email-accounts/' . $emailAccount->rid);
}
}
I seem to be getting the token no problem.
I have granted these permissions in Azure AD:

- Here I try and Authenticate with the token:
$client = Webklex\IMAP\Facades\Client::make([
'host' => 'outlook.office365.com',
'port' => 993,
'encryption' => 'tls',
'validate_cert' => true,
'username' => 'nicholas@[REDACTED].com.au',
'password' => $acces_token,
'protocol' => 'imap',
'authentication' => 'oauth',
]);
However, I get the following error message
"message": "connection failed",
"exception": "Webklex\\PHPIMAP\\Exceptions\\ConnectionFailedException",
"file": "C:\\...vendor\\webklex\\php-imap\\src\\Connection\\Protocols\\ImapProtocol.php",
"line": 81,
The same username and password allows me connect to Outlook365 SMTP, no problem.
Any help would be much appreciated.
Hi @nickster-pixel , many thanks for your detailed report!
Please note that you also have to add a client secret under "certificates and secrets".
It looks like you are missing the scope. Please go ahead and enter this scope: offline_access%20https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All%20https%3A%2F%2Foutlook.office365.com%2FSMTP.Send (You can probably ignorehttps%3A%2F%2Foutlook.office365.com%2FSMTP.Send, however I can confirm that this scope works).
Best regards & happy coding,
Hi @Webklex,
Thanks for your reply but still getting the following error
exception: "Webklex\\PHPIMAP\\Exceptions\\ConnectionFailedException"
file: "vendor\\webklex\\php-imap\\src\\Connection\\Protocols\\ImapProtocol.php"
line: 81
message: "connection failed"
I am setting a client secret. Here is my .env file:
MICROSOFT_CLIENT_ID=[REDACTED]
MICROSOFT_CLIENT_SECRET=[REDACTED]
MICROSOFT_REDIRECT_URI=http://localhost/microsoft-oath-callback
which is used when configuring the GenericProvider:
return new League\OAuth2\Client\Provider\GenericProvider([
'clientId' => env('MICROSOFT_CLIENT_ID'),
'clientSecret' => env('MICROSOFT_CLIENT_SECRET'),
'redirectUri' => env('MICROSOFT_REDIRECT_URI'),
'urlAuthorize' => env('MICROSOFT_AUTHORITY_URL') . env('MICROSOFT_AUTHORIZE_ENDPOINT'),
'urlAccessToken' => env('MICROSOFT_AUTHORITY_URL') . env('MICROSOFT_TOKEN_ENDPOINT'),
'urlResourceOwnerDetails' => '',
'scopes' => env('MICROSOFT_SCOPES')
]);
When you say I should set the scope, I presume you mean when requesting the oAuth token? Here I have the scopes set in my .env file:
MICROSOFT_SCOPES='offline_access%20https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All%20https%3A%2F%2Foutlook.office365.com%2FSMTP.Send'
This requested in the above GenericProvider configuration.
This particular string gives me the following error, which I presume is because of the URL encoding:
AADSTS650053: The application asked for scope 'offline_access%20https%3A%2F%2Foutlook.office365.com%2FIMAP.AccessAsUser.All%20https%3A%2F%2Foutlook.office365.com%2FSMTP.Send' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor. ◀
Trace ID: 548e8832-4057-4cb9-8339-233e92136600
However, when I change it to non-url encoded like this I can get the token no problem:
MICROSOFT_SCOPES='offline_access https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/SMTP.Send'
However, I'm still getting the above 'connection failed' error.
Any help would be much appreciated
Hi @nickster-pixel , thanks for the update. Can you try to get a hold of the entire call stack? The error seems to originate here and should contain more information: https://github.com/Webklex/php-imap/blob/6e76b3552491e437f37721ae113129682489c141/src/Connection/Protocols/ImapProtocol.php#L73-L83
You can also try to enable the debug mode: https://github.com/Webklex/php-imap/blob/master/src/config/imap.php#L152
This might provide some additional insight.
Best regards & happy coding,
Thanks @Webklex.
Here is the entire call stack:
{
"message": "connection failed",
"exception": "Webklex\\PHPIMAP\\Exceptions\\ConnectionFailedException",
"file": "vendor\\webklex\\php-imap\\src\\Connection\\Protocols\\ImapProtocol.php",
"line": 81,
"trace": [
{
"file": "vendor\\webklex\\php-imap\\src\\Client.php",
"line": 350,
"function": "connect",
"class": "Webklex\\PHPIMAP\\Connection\\Protocols\\ImapProtocol",
"type": "->"
},
{
"file": "app\\Http\\Controllers\\EmailAccountController.php",
"line": 221,
"function": "connect",
"class": "Webklex\\PHPIMAP\\Client",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Controller.php",
"line": 54,
"function": "testIncomingConnection",
"class": "App\\Http\\Controllers\\EmailAccountController",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
"line": 45,
"function": "callAction",
"class": "Illuminate\\Routing\\Controller",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 254,
"function": "dispatch",
"class": "Illuminate\\Routing\\ControllerDispatcher",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
"line": 197,
"function": "runController",
"class": "Illuminate\\Routing\\Route",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 695,
"function": "run",
"class": "Illuminate\\Routing\\Route",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 128,
"function": "Illuminate\\Routing\\{closure}",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "app\\Http\\Middleware\\DenyDemoUser.php",
"line": 24,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "App\\Http\\Middleware\\DenyDemoUser",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Middleware\\SubstituteBindings.php",
"line": 50,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Routing\\Middleware\\SubstituteBindings",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Auth\\Middleware\\Authenticate.php",
"line": 44,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Auth\\Middleware\\Authenticate",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken.php",
"line": 78,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\VerifyCsrfToken",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\View\\Middleware\\ShareErrorsFromSession.php",
"line": 49,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\View\\Middleware\\ShareErrorsFromSession",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Session\\Middleware\\StartSession.php",
"line": 121,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Session\\Middleware\\StartSession.php",
"line": 64,
"function": "handleStatefulRequest",
"class": "Illuminate\\Session\\Middleware\\StartSession",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Session\\Middleware\\StartSession",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse.php",
"line": 37,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Cookie\\Middleware\\EncryptCookies.php",
"line": 67,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Cookie\\Middleware\\EncryptCookies",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 103,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 697,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 672,
"function": "runRouteWithinStack",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 636,
"function": "runRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
"line": 625,
"function": "dispatchToRoute",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 166,
"function": "dispatch",
"class": "Illuminate\\Routing\\Router",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 128,
"function": "Illuminate\\Foundation\\Http\\{closure}",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "vendor\\laravel\\vapor-core\\src\\Http\\Middleware\\ServeStaticAssets.php",
"line": 21,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Laravel\\Vapor\\Http\\Middleware\\ServeStaticAssets",
"type": "->"
},
{
"file": "vendor\\fideloper\\proxy\\src\\TrustProxies.php",
"line": 57,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Fideloper\\Proxy\\TrustProxies",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
"line": 21,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull.php",
"line": 31,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
"line": 21,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TrimStrings.php",
"line": 40,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\TrimStrings",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize.php",
"line": 27,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance.php",
"line": 86,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 167,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Middleware\\PreventRequestsDuringMaintenance",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
"line": 103,
"function": "Illuminate\\Pipeline\\{closure}",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 141,
"function": "then",
"class": "Illuminate\\Pipeline\\Pipeline",
"type": "->"
},
{
"file": "vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
"line": 110,
"function": "sendRequestThroughRouter",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
},
{
"file": "public\\index.php",
"line": 55,
"function": "handle",
"class": "Illuminate\\Foundation\\Http\\Kernel",
"type": "->"
}
]
}
Here is my debug file:
'options' => [
'delimiter' => '/',
'fetch' => \Webklex\PHPIMAP\IMAP::FT_PEEK,
'sequence' => \Webklex\PHPIMAP\IMAP::ST_UID,
'fetch_body' => true,
'fetch_flags' => true,
'soft_fail' => false,
'rfc822' => true,
'debug' => true,
'uid_cache' => true,
// 'fallback_date' => "01.01.1970 00:00:00",
'boundary' => '/boundary=(.*?(?=;)|(.*))/i',
'message_key' => 'list',
'fetch_order' => 'asc',
'dispositions' => ['attachment', 'inline'],
'common_folders' => [
"root" => "INBOX",
"junk" => "INBOX/Junk",
"draft" => "INBOX/Drafts",
"sent" => "INBOX/Sent",
"trash" => "INBOX/Trash",
],
'decoder' => [
'message' => 'utf-8', // mimeheader
'attachment' => 'utf-8' // mimeheader
],
'open' => [
// 'DISABLE_AUTHENTICATOR' => 'GSSAPI'
]
],
Here is my connect call:
$client = Client::make([
'host' => 'outlook.office365.com',
'port' => '993',
'encryption' => 'tls',
'validate_cert' => true,
'username' => '[redacted]',
'password' => $accessToken,
'protocol' => 'imap',
'authentication' => 'oauth',
]);
$client->connect();
Best regards
Hi @nickster-pixel , thanks for the follow-up.
A few thoughts:
- Your provided snipped and config look fine to me
- Make sure to include
https://outlook.office365.com/IMAP.AccessAsUser.All"inside your scope - I suspect your azure setup doesn't allow imap access
Additional information:
- https://www.php-imap.com/examples/oauth
- https://github.com/MicrosoftDocs/office-developer-exchange-docs/issues/87
- https://github.com/Webklex/php-imap/issues/81
- https://github.com/MicrosoftDocs/office-developer-exchange-docs/issues/100
Best regards,
Thanks @Webklex, still not working.
In Azure, I've enabled these permissions:

I'm requesting these scopes when getting the access token:
openid profile email offline_access https://outlook.office365.com/IMAP.AccessAsUser.All
I'm connecting like this:
$client = Webklex\IMAP\Facades\Client::make([
'host' => 'outlook.office365.com',
'port' => 993,
'encryption' => 'tls',
'validate_cert' => true,
'username' => '[redacted]',
'password' => $accessToken,
'protocol' => 'imap',
'authentication' => 'oauth',
]);
$client->connect();
But still getting the following error:
exception: "Webklex\\PHPIMAP\\Exceptions\\ConnectionFailedException"
file: "webklex\\php-imap\\src\\Connection\\Protocols\\ImapProtocol.php"
line: 81
message: "connection failed"
Any help would be much appreciated
Ok, I've got it working now.
The problem was I was using an old version. I've now upgraded from
"webklex/laravel-imap": "2.4"
to
"webklex/laravel-imap": "4.0"
Thanks for all your help @Webklex
I'm using version 5.5 and I'm having this exact same issue. Should I open a new case?