Positioning​

If a Smtp or Pop3 endpoint for e-mail exchange requests authorization via the OAuth 2.0 protocol, then this flow-based protocol must be used when communicating with the server to authenticating the client. This may also require displaying a user interface for obtaining the user's consent to the e-mail exchange.

The client-side of the OAuth 2.0 protocol, including the handling of the UI for obtaining user consent, is implemented in the OAuth2 asset available for Xbase++. This asset provides the application with an access token as the result of the authentication process. This token can then be used with the SmtpClient() and Pop3Client() classes for handling the e-mail exchange.

This article is divided into two parts. First, an application is registered in Office 365. This is the precondition to be able to communicate with the OAuth 2.0 peers at all. The second part shows the client-side implementation using the Xbase++ OAuth2 asset. Because Office 365 uses the code grant flow for authenticating a client, the code in this part is based on the code grant example included with the OAuth2 asset.

Reading the articles Xbase++ Workbench Asset Management and OAuth2 asset explained will certainly be helpful for understanding asset usage and the OAuth2 protocol in general.

Overview application registration in Office 365​

To allow a client to exchange e-mails with the Office 365 pop3/smtp server, a so-called application configuration must be set up in the Office 365 infrastructure. For this purpose, Microsoft has published a manual: https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app. Once application configuration is complete, the following information is available for usage in the client implementation:
  • Application (client) ID
  • Redirect URI
  • Authorization Endpoint
  • Token Endpoint
Static information for Office 365:

Overview client implementation​

For the Xbase++ client, we will implement the Office365CodeGrant() class using the Office 365 application information shown above. The Xbase++ application can then use the asset's OAuth2Client() class to authenticate the client and obtain an access token using this specialized code grant. We will also see how to implement the UI binding and how to persist the Access Token for reuse.

In the end, the Xbase++ application only needs to implement the following code to get an access token:

Xbase++:
...
   oCodeGrant := Office365CodeGrant():new()
   oCodeGrant:setLoginFormAdapter( LoginForm():new() )
   oCodeGrant:setWallet( AccessTokenWallet():new() )

   oAuth2Client := OAuth2Client():new()
   oAuth2Client:setOAuth2Grant( oCodeGrant )

   cAccessToken := oAuth2Client:getAccessToken()
...

The Access Token can then be used with the classes SmtpClient() and Pop3Client() by supplying the token as the user's password. For this purpose, the "xoauth2" authentication protocol must be set first.

Xbase++:
   ...
   oSmtpClient := SMTPClient():new( "smtp.office365.com", 587, ,oLogger, 2 )
   oSmtpClient:setAuthentication( "xoauth2" )
   oSmtpClient:connect( "mail@account-detail.microsoft.com", cAccessToken )
   ...
   oPop3Client := Pop3Client():new( "outlook.office365.com", 995, cUser, cAccessToken, oLogger, 3 )
   oPop3Client:setAuthentication( "xoauth2" )
   IF .NOT. oPop3Client:connect()
   ...

Registration of an application​

After the introduction, we now come to the practical part and will register an application with the Microsoft infrastructure. The procedure is described in the Microsoft manual: https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app. The screenshots may be outdated after the publication of this article. However, the basic procedure should not change.

Log into the Azure Portal: https://portal.azure.com/

Select Manage Azure Active Directory​


Select Manage Azure Active Directory.png

Click on "App registrations" and then "New registration"​


Click App Registration.png


New Registration.png


Enter a name and select "Who can use the application"​

The Redirect URI is not required yet. Click on Register at the bottom of the page.

Name and account.png


Click on "Endpoints" and copy the URLs of "OAuth 2.0 authorization" and "OAuth 2.0 token" endpoints​


click Endpoints copy url.png

Note the "Application (client) ID" and click on "Redirect URI"​


Click on Redirect URI.png

Click on "Add a Platform" and "Mobile and desktop applications"​


Add Plattform Mobile and Desktop applications.png


Enter http://localhost/RedirectionSink/redirected as the "Custom redirect URI"​

Note that the URI is case-sensitive. Another URI can be assigned, too. However, this requires modification of the OAuth2 asset.

Click on "Configure"


Enter redirection URI.png

The application is now registered and you have noted the "Application (client) ID" and the URLs for the Authorization and Token endpoints.

Client-side implementation with the Xbase++ OAuth2 asset​

The client-side implementation in this article is based on the "Authorization Code Grant" example of the Xbase++ OAuth2 asset. Parts of the example can be used unchanged. Other parts must be adapted for the Microsoft Office 365 connection. The necessary steps are explained in the following.

The class Office365CodeGrant()​

In your application, you must implement a class that is derived from the asset's OAuth2CodeGrant class. The actual configuration for using the Office 365 Authorization and Token endpoints takes place in the implementation of this class.

In order to use the OAuth2 asset in your application, the asset must be added to your project using the Asset Manager in the Xbase++ Workbench. We also recommend adding the code grant example in order to be able to use it as a reference when coding.

The class declaration remains unchanged compared to the asset example except for the name of the class.

Xbase++:
CLASS Office365CodeGrant FROM OAuth2CodeGrant
   EXPORTED:
      METHOD getRedirectPort

      METHOD getHtmlRedirectFailureResponse
      METHOD getHtmlRedirectSuccessResponse

      METHOD configureRequestCommon
      METHOD configureAuthorizationRequest
      METHOD configureTokenRequest
      METHOD configureRefreshRequest
ENDCLASS

The implementations of the :getRedirectPort(), :getHtmlRedirectFailureResponse() and :getHtmlRedirectSuccessResponse() methods can also be used without changes. You only need to adjust the redirect port if you want to use a different port for redirection, for example, if port 85 is already in use.

Xbase++:
METHOD Office365CodeGrant:getRedirectPort()
RETURN 85

METHOD Office365CodeGrant:getHtmlRedirectFailureResponse()
   LOCAL cResult

   TEXT INTO cResult

   <!DOCTYPE html>
   <html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
   </head>
   <body>
   <h1>Authorization failed</h1>
   <p>
      The browser can be closed.
   </p>
   </body>

   </html>

   ENDTEXT

RETURN cResult

METHOD Office365CodeGrant:getHtmlRedirectSuccessResponse()
   LOCAL cResult

   TEXT INTO cResult

   <!DOCTYPE html>
   <html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
   </head>
   <body>
   <h1>Authorization success</h1>
   <p>
      The browser can be closed.
   </p>
   </body>
   </html>

   ENDTEXT

RETURN cResult

The code grant class must now be customized to use the information obtained when registering the Office 365 application application. In the :configureRequestCommon() method, the Redirect URI remains unchanged to match the URL entered earlier in this article. The Application (Client) ID must be exactly the same as the one you created and noted when registering the Office 365 application.

Xbase++:
METHOD Office365CodeGrant:configureRequestCommon( oRequest )
   // Todo: Assign your Client Id here
   oRequest:client_id      := ""
   oRequest:redirect_uri   := "http://localhost:"+Var2Char(::getRedirectPort())+"/RedirectionSink/redirected"
RETURN SELF

In the :configureAuthorizationRequest() method, you must enter the URL of the Authorization endpoint and the scope mentioned earlier in this article. Response Type and state can be left the same as in the original example. The Response Type is always "code" and the State must always be unique.

Xbase++:
METHOD Office365CodeGrant:configureAuthorizationRequest( oAuthorizationRequest )
   // Todo: Assign the URL of the Authorization server here
   oAuthorizationRequest:ServerUrl     := ""
   oAuthorizationRequest:response_type := "code"
   oAuthorizationRequest:state         := UUIDToChar( UUIDCreate() )
   oAuthorizationRequest:scope          := "https://outlook.office.com/POP.AccessAsUser.All " + ;
                                           "https://outlook.office.com/SMTP.Send"
RETURN SELF

Similarly, the the URL of the token server must be entered in method :configureTokenRequest(). The grant type must always be "authorization_code", the code must be taken from the parameter oAuthorizationResult.

Xbase++:
METHOD Office365CodeGrant:configureTokenRequest( oTokenRequest, oAuthorizationResult )
   // Todo: Assign the URL of the Token server here
   oTokenRequest:ServerUrl       := ""
   oTokenRequest:grant_type      := "authorization_code"
   oTokenRequest:code            := oAuthorizationResult:Code
RETURN .T.

Finally, the method :configureRefreshRequest() must be implemented. The method's body can remain empty because the Office 365 token endpoint does not implement a refresh.

Xbase++:
METHOD Office365CodeGrant:configureRefreshRequest( oRefreshRequest, cRefreshToken )
RETURN self

The class LoginForm()​

For the user interface to collect the user consent, the open() and close() methods of the LoginFormAdaptor() class included in the OAuth2 asset must be implemented. In our case, the user interface is a WebBrowser process that is started with the ShellExecute() operating system function.

Xbase++:
#include "dll.ch"

STATIC EXTERN UINTEGER ShellExecute( hwnd AS UINTEGER,;
                                     lpOperation AS UINTEGER, ;
                                     lpFile AS STRING,        ;
                                     lpParameter AS UINTEGER, ;
                                     lpDirectory AS UINTEGER, ;
                                     nShowCmd AS INTEGER ) IN shell32.dll

PROCEDURE StartWebBrowser( cUrl )
   ShellExecute( 0, 0, cUrl, 0, 0, 0 )
RETURN

CLASS LoginForm FROM LoginFormAdapter
   PROTECTED:
      VAR ReadyForClose
      VAR Dialog
   EXPORTED:
      METHOD open
      METHOD close
ENDCLASS

METHOD LoginForm:open( cUrlLogin )

   ::ReadyForClose := .F.

   StartWebBrowser( cUrlLogin )

   DO WHILE .NOT. ::ReadyForClose
      Sleep(100)
   ENDDO

   ::ReadyForClose := .F.

RETURN SELF

METHOD LoginForm:close()
   ::ReadyForClose := .T.
RETURN self

The class AccessTokenWallet()​

Finally, we will implement the wallet, which stores the access token on disk, from where it can later be reused in subsequent requests. See the header section of the original asset example for details.

Xbase++:
CLASS AccessTokenWallet FROM TokenWallet
   EXPORTED:
      INLINE METHOD readToken()
         LOCAL oToken
         LOCAL cToken := MemoRead( "tokenwallet.json" )
         IF Empty( cToken )
            RETURN NIL
         ENDIF
         oToken := Json2Var( cToken )
      RETURN oToken
      INLINE METHOD writeToken( oToken )
         MemoWrit( "tokenwallet.json", Var2Json( oToken ) )
      RETURN oToken
ENDCLASS

Summary​

The OAuth2 Code Grant protocol must be used in order to be able authorize a client for accessing e-mails on an Office 365 backend. This protocol is implemented in the Xbase++ OAuth2 asset. For this to work, an application must be set up in the Office 365 infrastructure, and the application's details must be used to configure the code grant used in the client. Once client access has been authorized, the resulting access token can be used for sending and retrieving e-mails using the SmtpClient and Pop3Client classes.

References​