jerer

Just tried getting Graph token using graph scopes and endpoints and same issue. I was able to get a token successfully but can't auth to IMAP using Graph tokens... so definitely an issue on Microsoft side that they need to address.

Cheers.

jerer

Confirmed that this works. Sucks to have to use a workaround, hopefully MS will fix something and it'll go back to working the right way at some point...

Dav1d

Yea but try to save the Remote Mailbox with IMAP configured and enabled and you'll see authentication fails.

Cheers.

@jerer

I just did a test to remove the call to the user endpoint and it gets a token but still same authentication failed message when attempting to authenticate to IMAP and SMTP. So this definitely appears to be an issue on their end as I can't even auth using a token I'm given.

Cheers.

    KevinTheJedi
    Yeah I can agree this is at least horrible design by Microsoft. The thing is, it has been like this for years and I don't see them fixing it :/

    https://stackoverflow.com/questions/61597263/office-365-xoauth2-for-imap-and-smtp-authentication-fails/61678485#61678485

    https://techcommunity.microsoft.com/t5/exchange/cannot-connect-to-imap-and-smtp-using-oauth2-0-to-exchange/m-p/1359651

    This is not the only thing we have been struggling with since the introduction of Graph and things being deprecated by it.

    @jerer

    To test this yourself you can unpack the plugin (cd to the plugin directory and run php -r '$phar = new Phar("auth-oauth2.phar"); $phar->extractTo("./auth-oauth2");'), edit the auth-oauth2/oauth2.php file, and update the callback() method for class OAuth2EmailAuthBackend to the following:

        public function callback($resp, $ref=null) {
            $errors = [];
            $err = sprintf('%s_auth_bk', $this->account->getType());
            try {
                // MOD
                //if ($this->getState() == $resp['state']
                //        && ($token=$this->getAccessToken($resp['code']))
                //        && ($owner=$this->client->getResourceOwner($token))
                //        && ($attrs=$this->mapAttributes($owner->toArray()))) {
                if ($this->getState() == $resp['state']
                        && ($token=$this->getAccessToken($resp['code']))) {
                    $this->resetState();
                    $info =  [
                        'access_token' => $token->getToken(),
                        'refresh_token' => $token->getRefreshToken(),
                        'expires' => $token->getExpires(),
                        'resource_owner_id' => $token->getResourceOwnerId(),
                        //'resource_owner_email' => $attrs['email'],
                        'resource_owner_email' => $this->getEmailAddress(),
                    ];
    /*
                    if (!isset($attrs['email']))
                        $errors[$err] = $this->error_msg(self::ERR_EMAIL_ATTR, $attrs);
                    elseif (!$info['refresh_token'])
                        $errors[$err] = $this->error_msg(self::ERR_REFRESH_TOKEN);
                    elseif (!$this->signIn($attrs)) {
                        // On strict mode email mismatch is an error, otherwise
                        // set email address being authorized as the resource
                        // owner - with the assumption that a global admin
                        // authorized the account.
                        if ($this->isStrict())
                            $errors[$err] = $this->error_msg(self::ERR_EMAIL_MISMATCH, $attrs);
                        else
                            $info['resource_owner_email'] = $this->getEmailAddress();
                    }
    */
                    if (!$info['refresh_token'])
                        $errors[$err] = $this->error_msg(self::ERR_REFRESH_TOKEN);
                    // END
    
                    // Update the credentials if no validation errors
                    if (!$errors
                            && !$this->updateCredentials($info, $errors)
                            && !isset($errors[$err]))
                         $errors[$err] = $this->error_msg(self::ERR_UNKNOWN);
                }
            } catch (Exception $ex) {
                $errors[$err] =  $ex->getMessage();
            }
    
            // stash the results before redirecting
            $email = $this->getEmail();
            // TODO: check if email implements StashableTrait
            if ($errors)
                $email->stash('errors', $errors);
            else
                $email->stash('notice', sprintf('%s: %s',
                            $this->account->getType(),
                            __('OAuth2 Authorization Successful')
                            ));
            // redirect back to email page
            $this->onSignIn();
        }

    Then login to the database, go to the _plugin table, modify your OAuth2 plugin record, set the install_path to plugins/auth-oauth2 (just remove the .phar), and set isphar to 0. Now the system will use the unpacked plugin with the custom changes.

    With the above changes applied the system will skip the user endpoint altogether during token retrieval. You will then get a token successfully (as previously) but then when you try to authenticate using that token you are given it doesn't work (go figure).

    Cheers.

      KevinTheJedi
      This is weird, things work fine for me at the moment (after removing the call to the user endpoing API). Did you forget some other scopes there? (like openid, User.Read, etc.).

      Can only use offline_access and https://outlook.office.com/XXXXXX scopes or IMAP/SMTP/POP authentication fails.

        jerer

        That's exactly what I used offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send.

        Cheers.

        KevinTheJedi
        Although I already bypassed it the other way, I still would like to confirm I tested this modification and it works. Thanks for looking into this!

          jerer

          So I’m assuming my account is just fucked up then. Because I always had a dev account but it expired so now I’m using my personal account and this is the one that’s not authenticating (yes I created a brand new registration using the personal account, etc.). I contacted MS to see what’s going on so unfortunately I’m at their mercy. Glad my changes are working for you though. Once I get more confirmations I’ll clean it up and make it legit.

          Cheers.

          I've modified the plugin... What other settings should we use for the Auth, Token and Scopes?

            KevinTheJedi Hi Kevin, My brain hurts after such a long day... I will however try tomorrow and come back to you. Thanks for your assistance in advance.

            KevinTheJedi,

            I get an AUTHENTICATE failed using the same settings but modifications to the plugin.

              jfields

              That’s the same thing I get but @jerer says otherwise. Maybe try offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send for the scopes?

              Cheers.

              array ( 'code' => 'InvalidAuthenticationToken', 'message' => 'Access token validation failure. Invalid audience.', 'innerError' => array ( 'date' => '2024-10-01T20:38:46', 'request-id' => '<guid>', 'client-request-id' => '<guid>', ), )

                jfields

                That sounds like something isn’t configured correctly. Do you have all of these scopes added and admin consented in the app registration in entra?

                Cheers.

                KevinTheJedi,

                This is the third account I'm setting up for this application. The previous two work fine because their token hasn't expired yet (and I'm tiptoeing around it). I can get it to authenticate just fine but then get AUTHENTICATE failed when trying to download IMAP mail.

                For as big as Microsoft is, you would think they would not change things so often or so quickly.