Question
· Aug 9, 2022

OAuth 2.0 for Mail (POP3/SMTP)?

Hello,

i receiving several mails via "EmailInboundAdapter” and sending via "EmailOutboundAdapter"

Now Microsoft will force OAuth 2.0 for Outlook365-Mails and want to drop POP3 basic authentication permanetly at Oct/1 2022. All have to use OAuth 2.0 then.

IRIS documentation is very tiny for OAuth 2.0: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

A new paramter for a token is defined for "%Net.POP3.Connect()". But the token got only on authenication calls using ClientID and ClientSecret (last one valid for maximum 24 months). There is not support for OAuth 2.0 on EmailInboundAdapter, i think.

Is it be possible to implement an own Adapter using “%Net.POP3.Connect()” with tokens? Does anyone have experiance using OAuth 2.0 for Microsoft Outlook 365?

Many thanks for any hint or help!

Product version: IRIS 2022.1
Discussion (16)0
Log in or sign up to continue

I've made such an adapters based on Jose's work.

My articles, I'm afraid which is written in Japanese only,  is using IMAP/SMTP against gmail as target provider but I hope it works against Office365 as well.

The idea is you prepare a JSON file like this and production will pick it up when it starts (via OnStart() callback).
After that, adapter itself get new AccessToken periodically by using a given RefreshToken (well, at least that what I've intended).

i have modify a python scripts to get an access token from Outlook/Azure. I put all them together and defining a service, that periodically refresh the access token and save it in a global.

This works fine.

Next i have made my own "Class RT.EnsLib.EMail.InboundAdapter Extends Ens.InboundAdapter { ..." cloning and modify EnsLib.EMail.InboundAdapter

Adding:

            Set tAccessToken=""
            Set:..AccessTokenName'="" tAccessToken=$GET(^OAuth2.AccessToken(..AccessTokenName))
            $$$TRACE("tAccessToken="_tAccessToken)
            Set tSC = ..MailServer.ConnectPort(..POP3Server,..POP3Port,..%CredentialsObj.Username,..%CredentialsObj.Password,tAccessToken)

Using the access token on Intersystems ConnectPort() method

Method ConnectPort(PopServer As %String, PopPort As %Integer, UserName As %String, Password As %String, AccessToken As %String = "") As %Status

Calling

                ConnectPort(“outlook.office365.com”,995,”i...t@h...a.com”,”1…3”,”eyJ…vIQ”)

I got back:

                Fehler auf POP3-Server: -ERR Authentication failure: unknown user name or bad password.

Exact. The lack on intersystems is to aquire an access token. %Net.POP3 implements is as descripted on M$-docs.

I used a modified python-script "mutt_oauth2.py". First we had problems that python are an unauthorized client on the authorization part. Switch to multi-tanant-account AND add python on app-authentication-web-quickstart AND add redirect-uri 'http://localhost' (also on mutt_oauth2.py) was the solution to use python for aquire a access token.

Class My.EnsLib.EMail.OAuth2InboundAdapter Extends Ens.InboundAdapter [ ClassType = "", ProcedureBlock, System = 4 ]
{
...
Method OnTask() As %Status
{
...
            If '$IsObject(..%CredentialsObj) Do ..CredentialsSet(..Credentials) If '$IsObject(..%CredentialsObj) { Set tSC=$$$EnsError($$$EnsErrNoCredentials,..Credentials) Quit }
            Set tAccessToken=""
            Set:..AccessTokenName'="" tAccessToken=..getAccessToken(..AccessTokenName)
            If $FIND(tAccessToken,"ERROR:") = 7 {
                $$$LOGERROR(tAccessToken)
                Set tAccessToken=""
            }
            Set:tAccessToken="" tAccessToken=..AccessToken
            $$$TRACE("tAccessToken="_tAccessToken)
            Set tSC = ..MailServer.ConnectPort(..POP3Server,..POP3Port,..%CredentialsObj.Username,..%CredentialsObj.Password,tAccessToken)
...
}

ClassMethod getAccessToken(pTokenName As %VarString) As %VarString
{
    Quit ..OAuth2Method(pTokenName)
}

ClassMethod askAuthorization(pTokenName As %VarString, pReg As %VarString = "", pMail As %VarString = "", pClientId As %VarString = "", pClientSecret As %VarString = "", pTenant As %VarString = "") As %VarString
{
    Quit ..OAuth2Method(pTokenName, 1, pReg, pMail, pClientId, pClientSecret, pTenant)
}

ClassMethod doAuthorization(pTokenName As %VarString, pAuthCode As %VarString = "") As %VarString
{
    Quit ..OAuth2Method(pTokenName, 1, , , , , , pAuthCode)
}

ClassMethod OAuth2Method(pTokenName As %VarString, pDoAuth As %VarString = "", pReg As %VarString = "", pMail As %VarString = "", pClientId As %VarString = "", pClientSecret As %VarString = "", pTenant As %VarString = "", pAuthCode As %VarString = "") As %VarString [ Internal, Language = python, Private ]
{
#   based on mutt_oauth2 https://github.com/muttmua/mutt/blob/master/contrib/mutt_oauth2.py
}

Got EnsLib.EMail.InboundAdapter and added some line using AccessToken[Name]. Embedded Python code based on mutt_oauth2.py I have stripped it a little (only 'authcode'-flow) Elliminate external file and using IRIS-globals. Put 'client_id', 'client_secret', 'tenant' from registrations to token (saved in global) to be independent from edit sourcecode like mutt_oauth2.py was.

To use OAuth2 you have to start with getting an URL for authentication process:

w ##class(My.EnsLib.EMail.OAuth2InboundAdapter).askAuthorization(AccessTokenName, registration, mail, clientid, clientsecret [,tenant])

registration meens the name of the python 'registrations'-Array-Element.  The output is an URL to use in browser for getting an authorizationCode. After login and some confirmations you get a code. On google you find it much clear in a well styled webpage. On microsoft you find the code as part of the destination url in browsers url textfield.

w ##class(My.EnsLib.EMail.OAuth2InboundAdapter).doAuthorization(AccessTokenName, AuthorizationCode)

returns the first AccessToken and saving all details on ^OAuth2.AccessToken(AccessTokenName) global.

now you are ready to retrieve mails by oauth2.