REFRESH TOKEN LASTS ~3 DAYS APROX


Mail Reader for Outlook, Office 365 accounts with OAuth IMAP protocol

This repo is just a summary of an answer on StackOverflow

Bear in mind that there is no need that your mail have the @outlook.com extension, it can be hosted by microsoft with any domain such as this example: docs@enterprise.com.

Before seeing any of the scripts below, let me tell you that you can run them in two ways (at least 2 ways is what I know):

  1. Run you script on terminal:

    Let's say you have script.php, to run this:

    php -f script.php
  2. Run your script on web browser:

    You have a script.php.

    Open a terminal and go to the folder where your script is.

    Then write the following (you can choose any port, in my case I choose 4050)

    php -S localhost:4050
    #                ^^^^ you can change this port

    Open your favorite browser and go to: localhost:4050/script.php

    Remember to change to the port you use and the name of the script you created.

    Let's begin.

1 - Configure your mail box in Azure

This link takes you to the steps to register an app.

You will need :

2 Grab a code to get a token

By creating a script using php: Eg: step2.php

<?php
require 'vendor/autoload.php';

$TENANT="...";
$CLIENT_ID="...";
$SCOPE="https://outlook.office365.com/IMAP.AccessAsUser.All";
$REDIRECT_URI="http://localhost/test_imap";

$authUri = 'https://login.microsoftonline.com/' . $TENANT
           . '/oauth2/v2.0/authorize?client_id=' . $CLIENT_ID
           . '&scope=' . $SCOPE
           . '&redirect_uri=' . urlencode($REDIRECT_URI)
           . '&response_type=code'
           . '&approval_prompt=auto';

echo($authUri);
?>

As FoxInDisguise says:

You'll be redirected to http://localhost/test_imap?code=LmpxSnTw...&session_state=b5d713....
Save the code (remove the '&' at the end !) and the session state inside the url. These codes expired after a few hours !

3 Get an access token

In this script CLIENT_SECRET is the one named as Secret Value in your microsoft

<?php
$CLIENT_ID="...";
$CLIENT_SECRET="...";
$TENANT="...";
$SCOPE="https://outlook.office365.com/IMAP.AccessAsUser.All offline_access";
$CODE="...";
$SESSION="...";
$REDIRECT_URI="http://localhost/test_imap";

echo "Trying to authenticate the session..";

$url= "https://login.microsoftonline.com/$TENANT/oauth2/v2.0/token";

$param_post_curl = [ 
 'client_id'=>$CLIENT_ID,
 'scope'=>$SCOPE,
 'code'=>$CODE,
 'session_state'=>$SESSION,
 'client_secret'=>$CLIENT_SECRET,
 'redirect_uri'=>$REDIRECT_URI,
 'grant_type'=>'authorization_code' ];

$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($param_post_curl));
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);

$oResult=curl_exec($ch);

echo "result : \n";

var_dump($oResult);
?>

In this part, a token and token_refresh will be retrieved, store both of them.

As FoxInDisguise says:

If you don't have the "refresh_token" you have forgot to put "offline_access" in the scope)

4 Connect to mail box

In this case the answer's author choose to use php-imap. I am doing the same due to its simplicity to connect to mail box.

<?php
include __DIR__.'/vendor/autoload.php'; 

use Webklex\PHPIMAP\ClientManager;

$access_token="...";

$cm = new ClientManager();

$client = $cm->make([
    'host' => 'outlook.office365.com',
    'port' => 993,
    'encryption' => 'ssl', // 'tls',
    'validate_cert' => false,
    'username' => 'docs@enterprise.com',
    'password' => $access_token,
    'protocol' => 'imap',
    'authentication' => "oauth",
]);


try {
    $client->connect();
    $folder = $client->getFolder('INBOX');
    $all_messages = $folder->query()->all()->get();
    
    echo "<h1>Asunto de mensajes:</h1>", "\n";

    foreach($all_messages as $message){
      // Just fetching the mail's subject for reading simplicity:
      echo $message->getSubject().'<br />';
      echo "\n";
    }
    
} catch (Exception $e) {
    echo 'Exception : ', $e->getMessage(), "\n";
}

?>

You have finally made a connection and can see messages. But what happens if you want to do the same tomorrow or in next days?

5 Connecting to mail box everyday:

With the 3 preious scripts you cann connect to your mailbox, but what happens when you want to connect tomorrow? So far I know that the access_token lasts just 1 hour or little more.

Due to that reason, the way to connect to mail box in the future is to use the refresh_token:

For this step we just have to run this script. I assume you have already saved your refresh_token.

include __DIR__.'/vendor/autoload.php'; 
    
use Webklex\PHPIMAP\ClientManager;

$APPLICATION_ID="c-9c-....";
$SECRET_VALUE="Y~tN...";
$DIRECTORY_ID="5-48...";
$REFRESH_TOKEN="EebH9H8S7...";

$url= "https://login.microsoftonline.com/$DIRECTORY_ID/oauth2/v2.0/token";

$param_post_curl = [ 
 'client_id'=>$APPLICATION_ID,
 'client_secret'=>$SECRET_VALUE,
 'refresh_token'=>$REFRESH_TOKEN,
 'grant_type'=>'refresh_token' ];

$ch=curl_init();

curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($param_post_curl));
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
//ONLY USE CURLOPT_SSL_VERIFYPEER AT FALSE IF YOU ARE IN LOCALHOST !!!
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER, false);// NOT IN LOCALHOST ? ERASE IT !

$oResult=curl_exec($ch);

echo("Trying to get the token.... \n");

if(!empty($oResult)){
    
    echo("Connecting to the mail box... \n");
    
    //The token is a JSON object
    $array_php_resul = json_decode($oResult,true);
    
    if( isset($array_php_resul["access_token"]) ){

        $access_token = $array_php_resul["access_token"];

        //$cm = new ClientManager($options = ["options" => ["debug" => true]]);                     
        $cm = new ClientManager();                      
        $client = $cm->make([
            'host'          => 'outlook.office365.com',                
            'port'          => 993,
            'encryption'    => 'ssl',
            'validate_cert' => false,
            'username'      => 'docs@enterprise.com',
            'password'      => $access_token,
            'protocol'      => 'imap',
            'authentication' => "oauth"
        ]);
        
        try {
            //Connect to the IMAP Server
            $client->connect();
        }catch (Exception $e) {
            echo 'Exception : ',  $e->getMessage(), "\n";
        }

    }else{
        echo('Error : '.$array_php_resul["error_description"]); 
    }
}   

Alternatively you can run in a safer way the script that is in the code file of this repo to use an .env file where only you can see your credentials

Just remember both files have to be in the same folder.

Also you have to install dotenv (in the same folder of your scripts):

$ composer require vlucas/phpdotenv