todo:ext:imap:xoauth2

Google and Microsoft intend to stop support for password-based login to IMAP and POP, beginning on 2020-06-15 and 2020-10-13, respectively. The alternative, OAuth2-based login, will continue to work as the only means to authenticate users to these providers' mail servers.

Applications using the PHP IMAP extension for IMAP and POP access do not currently have a means to present OAuth tokens for authentication. This means that these applications will no longer be able to access Google and Microsoft email over IMAP and POP after these dates. We, the maintainers of the IMAP extension, do not wish to see our users lose access, so we are expediting the addition of token-based authentication.

What's This All About?

Currently, to access a Google-hosted inbox using the PHP IMAP extension, you do something like this:

$mbh = imap_open('{imap.gmail.com:993/imap/ssl/}INBOX', $username, $password);
if (! $mbh) {
    error_log(imap_last_error());
    throw new \RuntimeException('Unable to open Google INBOX');
}

This is classic password-based authentication, which Google (rightly) considers less secure than a time-limited, token-based authentication. In fact, to even get the above code to work, you need to instruct Google to allow “Less Secure Apps”. A similar situation applies for Microsoft 365, just the terminology is a bit different.

When these providers stop support for passwords, the above code will no longer work, period. Instead, the application will first need to register with Google as an application, then update its code to acquire an access token, then pass the access token to the IMAP server instead of the password.

The new code is substantially more complicated. I'll give an outline here (refer to the Google OAuth 2 documentation for more detail including a diagram):

  1. Create a login link that includes your application's registered ID and a nonce, scoped to the email service for Google.
  2. Redirect your user to this link, which will prompt them to confirm the share.
  3. Google redirects back to you with the same nonce and an authorization code.
  4. Verify the nonce matches, then use the authorization code to generate an access token an d a refresh token.
  5. Store the refresh token.
  6. Pass the access token to the imap_open function call. See below, under Our Plan.

For more on OAuth, refer to this OAuth primer by Aaron Parecki.

What You Need to Do

If you are the author of a PHP application using imap_open to access Google or Microsoft IMAP or POP servers, then you need to:

  1. Update your application to support OAuth flows. A general sketch is given above, but you'll need to support:
    1. session storage of nonce and access tokens per user
    2. persistent, secure storage of refresh tokens per user
    3. redirection to, and from, Google or Microsoft
  2. Update your imap_open call to pass the access token

In addition:

  1. Contact Google and Contact Microsoft and ask them to extend the deadline. The more time we have to migrate, the better.

Don't forget:

  1. Support the development of this feature by:
    1. Voting or commenting on Issue #64039

Our Plan

As you can see, this is not just a drop-in replacement. Once your application can speak an OAuth flow, then you need to pass the token to imap_open. We have some work to do here:

  1. Improve imap_open to accept OAuth tokens (see below for sample)
  2. Open RFC to backport this feature into all supported PHP versions, including security fix branches
    • Normally these branches don't receive features
    • However, if they don't, you'll have to patch your install manually -- and that's not ideal
  3. Provide a pure PHP example using curl and imap extensions to provide OAuth authentication
  4. Support the community by FAQ, Q&A, etc.

Proposed API Change

The plan is to add token support to imap_open, as follows:

$mbh = imap_open($server, $username, $token, OP_XOAUTH2);
if (! $mbh) {
    error_log(imap_last_error());
    throw new \RuntimeException('Unable to open Google INBOX');
}

Here, the parameter described as “password” takes the token received from the OAuth flow, and the function's told it's a token by the newly added option OP_XOAUTH2. Internally, this will cause the function to use the XOAUTH2 mechanism during the AUTHENTICATE sequence, which is transparent to consumers.

Future Direction

OAuth 2 supports an extra challenge phase, whereby one has to supply multi-factor authentication beyond just the token. In this first implementation, because of the looming deadline, imap_open will not support this. However, a future implementation might, using an API like follows:

$options = [];
if ($_GET['mfa']) {
    $options['OP_XOAUTH2_CHALLENGE'] = $_GET['mfa'];
}
$mbh = imap_open($server, $username, $token, OP_XOAUTH2, $max_retries, $options);
if (! $mbh) {
    if (IMAP_XOAUTH2_CHALLENGE === imap_last_error_code()) {
        // show form collecting MFA into 'mfa' variable
    } else {
        error_log(imap_last_error());
        throw new \RuntimeException('Unable to open Google INBOX');
    }
}

The basic idea is to first try an OAuth authentication. If that fails, prompt the user to enter their MFA, then on the next submission, pass the challenge value to the open.

Work in Progress

This is a work in progress. Plan changes will be posted here. Status reports will be blogged. Feel free to ask a question.

todo/ext/imap/xoauth2.txt · Last modified: 2020/01/08 05:25 by bishop