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
- Scope: https://outlook.office.com/POP.AccessAsUser.Allhttps://outlook.office.com/SMTP.Send
- Smtp: smtp.office365.com Port 587
- Pop3: outlook.office365.com Port 995
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
Click on "App registrations" and then "New registration"
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.Click on "Endpoints" and copy the URLs of "OAuth 2.0 authorization" and "OAuth 2.0 token" endpoints
Note the "Application (client) ID" and click on "Redirect URI"
Click on "Add a Platform" and "Mobile and desktop applications"
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"
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
- Ilx article Xbase++ Workbench Asset Management.
- Ilx article OAuth2 asset explained
- Xbase++ documentation Class SMTPClient()
- Xbase++ documentation Class Pop3Client()
- RFC 6794 "The OAuth 2.0 Authorization Framework"