3

I currently use the PHP-EWS project to help automatically log emails from multiple mailboxes. The system was working great until recently I started to get a "The request failed with HTTP status 401: Unauthorized." error.

After contacting Microsoft, they said it could be that the authentication needs to use a token instead of username/password. I came across this question by user3434790 which appears to be exactly what I'm looking for, except I'm not sure how to GET the token in the first place! I have seen some bits about a Javascript method, but my script is completely server-side(PHP) and I'm specifically trying to connect to office 365 accounts.

Community
  • 1
  • 1
jtcrow
  • 53
  • 9
  • If you are only targeting Office 365, have you looked at Outlook REST APIs? It would be much easier to use them with PHP - See https://dev.outlook.com/RestGettingStarted/Tutorial/php – Venkat Ayyadevara - MSFT Nov 19 '15 at 17:18
  • Thanks Venkat. I have seen the Outlook REST APIs, but I was hoping to steer clear of it (unless it is the only way) - as I already had the system working before using SOAP-CURL. – jtcrow Nov 19 '15 at 17:33
  • 1
    @jtcrow Sadly the php-ews library you linked to doesn't support tokens, it only supports usernames and passwords. It's also unmaintained, so it won't be added soon. I've got my own fork of it called garethp/php-ews, and I'll be looking to add Token support some time in the next few weeks, but for the moment you won't be able to use tokens with that library without forking it yourself – Gareth Parker Nov 20 '15 at 08:23

2 Answers2

2

Okay, so far doing some research this is basically a story in three parts.

Part one is regarding getting an "Authorization Code". Basically what this means is that you register your application with Azure or with Outlook 365 Dev and then have a page or a check to see if you have a valid "Token" for your user. If you don't, then you need to get that Token.

Basically, it means sending yours user to a login page on the Microsoft Domain with your ClientID and Callback URI (That you registered before) in the URL, along with what permissions you want. The user will then log in and be directed back to your site with the "Authorization Code" that you need in the URL. This is a generalized guide on how to do it. It doesn't deal in a specific language, but in the general flow of requests.

A very simple, and not to be used in production, example of this would be

index.php

if (!$_SESSION['code']) {
    $redirect = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' .
        '?response_type=code' .
        '&client_id=' . urlencode('clientId') .
        '&redirect_uri=' . urlencode('https://localhost/callback.php') .
        '&scope=' . urlencode('https://outlook.office365.com/Mail.Read')
        ;
    header("Location: {$redirect}");
    exit();
}

callback.php

if (isset($_GET['code'])) {
    $_SESSION['code'] = $_GET['code'];
    header("Location: index.php");
}

Part Two: Getting the Authorization Token

Unfortunately the Code is not all you need. It's a quick one-use code that you use to get the actual token that you need to authorize your requests. You can do this by posting to "https://login.microsoftonline.com/common/oauth2/v2.0/token" with the following data

  • Client ID
  • Client Secret
  • Authorization Code
  • Redirect URI
  • Grant Type

With Guzzle, that can be done as such

$postOptions = array(
    'http_errors' => false,
    'form_params' => array(
        'client_id' => $clientId,
        'client_secret' => $clientSecret,
        'code' => $authorizationCode,
        'redirect_uri' => $redirectUri,
        'grant_type' => 'authorization_code'
    )
);

$url = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';

$client = new Client();
$response = $client->request('POST', $url, $postOptions);
$response = $response->getBody()->__toString();

$response = json_decode($response);
return $response->access_token;

Once you have your token, you want to keep this. While the code is a one-use thing, the token will last a bit longer. From what I saw it seems to expire after an hour. So you want to get your token once and then store it in yours session or however you want to store it. Once you have your token you can make your calls. Well, almost.

Part Three: The Soap Calls

Unfortunately the library you are using doesn't support this at all. It's got hard coded authorization as either basic HTTP auth or NTLM. You can see that there's no option for support via tokens here. If you forked it, you could remove Line 78 and 79 and change ExchangeWebServices to pass down a token instead, and you'd just need to add

'Authorization: Bearer ' . $token

to the headers, and that should work. Or, if you have the time you can try to use a much more updated fork of that library which I write and maintain, garethp/php-ews. While it can't take care of Step 1 for you, once you have your Authorization Code, it will make Step 2 and 3 much much easier. Here's a small example. However, it's a new feature I just put in today, and it's still experimental. I ran in to issues with permissions because it looks like you need to register your app with Azure AD for Office 365 to get the right permissions for EWS, instead of registering with Outlook to get permissions for the Outlook REST API, and I can in to some trouble creating a dev account for Azure. So I managed to get as far as creating a token, authorizing with it, making sure it's being passed through only to be told that the token I have doesn't have permissions to do what needs done.

Gareth Parker
  • 5,012
  • 2
  • 18
  • 42
  • That's fantastic thanks Gareth - you have explained it in a lot of detail, which I appreciate too! I will work on my update and let you know how I get on! – jtcrow Nov 20 '15 at 15:58
  • @jtcrow I just found this article, it's much more helpful for understanding the general flow of getting a token than anything I've read so far. Might be worth bookmarking for reference. https://msdn.microsoft.com/en-us/office/office365/howto/authentication-v2-protocols – Gareth Parker Nov 20 '15 at 16:48
1

in october 2022 you cannot use mail + pass in ews for office 365 account anymore. Use Gareth answer to get an accesstoken. We made a fork on this repo to use oauth: https://github.com/facilioo/php-ews

$ews = new Client("outlook.office365.com", Client::VERSION_2016); 
$ews->authWithOauth2("your-access-token"); 

or for on premises:

$ews->authWithUserAndPass($user, $password);
Tobias Bambullis
  • 736
  • 5
  • 17
  • 45