Introduction
Microsoft has retired Basic Authentication for Microsoft Office 365. A service or batch job written in Xbase++ can no longer connect with a plain username and password. Instead, every request must carry an OAuth2 access token issued by Microsoft Entra ID (formerly Azure Active Directory).Several flows (so-called grants) exist for obtaining such an access token. For unattended applications, such as schedulers, back-end services or agents, the token is obtained with the Client Credentials Grant, which is a non-interactive authorization flow intended for service-to-service communication; the application authenticates itself using a client id and secret with no user interaction.
This article shows how an access token can be obtained from MS Office 365 with the Client Credentials Grant using the OAuth2 asset.
Once an access token has been granted, it can be used by the application for the intended purpose (scope), such as for email exchance via POP3 or SMTP or for accessing the Microsoft Graph API. An example for an exemplary use-case can be found here: Automated email exchange with Microsoft Office 365 (SMTP, POP3)
Prerequisite
The application must be registered in Microsoft Entra ID, see here: Register and configure Azure app for Office 365 SMTP and POP3.What you need to know
The Xbase++ application interacts with just a single class, responsible for authenticating the application in exchange for an access token. You do not need to know how this class works internally, only how to configure and use it.Class: OAuth2ClientCredentialsGrant
| Member | Purpose |
|---|---|
| :configureTokenRequest() | Configures the token acquisition request with data from the app registration in Microsoft Entra ID, the token endpoint URL and the intended purpose (scope) |
| :getAccessToken() | Authenticates the application using the Client Credentials flow and returns an access token if successful. The token is cached and handed back on subsequent calls, only going back to the network when it is missing or about to expire. You don't have to track expiry yourself. |
The following information must be provided for authenticating with MS Office 365:
| Token endpoint URL | https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token *) |
| Scope | The required scope. Default: https://outlook.office365.com/.default |
| Client id | Application's client id *) |
| Client secret | Application's client secret *) |
Note: Keep the secret out of your source code. Read it from an environment variable or a secrets store instead!
Step 1 — Obtain an access token
Xbase++:
// Create a customized OAuth2 grant class configured for application-specific token acquisition
CLASS MyClientCredentialsGrant FROM OAuth2ClientCredentialsGrant
EXPORTED:
METHOD configureTokenRequest
ENDCLASS
METHOD MyClientCredentialsGrant:configureTokenRequest( oConfig )
oConfig:ServerUrl := <token-end-point-url>
oConfig:client_id := <your-apps-client-id>
oConfig:scope := <your-scope>
oConfig:client_secret := <your-apps-client-secret>
oConfig:grant_type := "client_credentials"
RETURN self
PROCEDURE Main()
LOCAL cAccessToken
LOCAL oGrant
// Initialize application logging.
// Optional: enable ClientCredentialsGrant logging by setting the environment variable:
// xpp.logger=oauth2.client-credentials
XppRtFileLogger():startup()
// Create a grant object for requesting an access token
oGrant := MyClientCredentialsGrant():new()
cAccessToken := oClientCredentialsGrant:getAccessToken()
...
Step 2 — Optional: Persist the access token for usage in subsequent application runs
By default, access tokens are held only in memory and are lost when the application exits. In order to reuse tokens in subsequent runs of the application, a persistent token storage can be configured.The following example shows using a persistent storage which stores tokens in a JSON file.
Xbase++:
// Create a customized class for token serialization in a JSON file
CLASS MyTokenWallet FROM TokenWallet
EXPORTED:
METHOD readToken()
METHOD writeToken()
ENDCLASS
METHOD MyTokenWallet:readToken()
LOCAL oToken
LOCAL cToken := MemoRead( "tokenwallet.json" )
IF Empty( cToken )
RETURN NIL
ENDIF
oToken := Json2Var( cToken )
RETURN oToken
METHOD MyTokenWallet:writeToken( oToken )
MemoWrit( "tokenwallet.json", Var2Json( oToken ) )
RETURN oToken
PROCEDURE Main()
LOCAL cAccessToken
LOCAL oGrant
// Initialize application logging.
// Optional: enable ClientCredentialsGrant logging by setting the environment variable:
// xpp.logger=oauth2.client-credentials
XppRtFileLogger():startup()
// Create a grant object for requesting an access token and configure it to use the persistent JSON storage
oGrant := MyClientCredentialsGrant():new()
oGrant:setWallet( MyTokenWallet():new() )
cAccessToken := oGrant:getAccessToken()
...
Summary
The Client Credentials Grant enables non-interactive authentication for service applications. The Xbase++ application uses a customized grant object configured for the respective endpoint and intent to obtain an access token. Once an access token has been acquired, can be used for directly acccessing the resource, for example with the SMTPClient() and POP3Client() class for email exchange.References
- Ilx article Register and configure Azure app for Office 365 SMTP and POP3
- Ilx article Automated email exchange with Microsoft Office 365 (SMTP, POP3)
- Ilx article Xbase++ Workbench Asset Management
- Ilx article XppRtFileLogger
- Xbase++ Documentation Class SMTPClient()
- Xbase++ Documentation Class POP3Client()
- RFC 9794 The OAuth 2.0 Authorization Framework