Using OAuth with the SMTP, IMAP, and POP Components


Requirements: IPWorks

Many mail providers, including Gmail and Microsoft 365, require OAuth 2.0 authentication when connecting with SMTP, IMAP, or POP, and no longer accept a user name and password. The SMTP, IMAP, and POP components (as well as the HTMLMailer and FileMailer components, which are based on SMTP) support OAuth through the XOAUTH2 authentication mechanism.

The components include built-in OAuth support, which handles the entire authorization process, including launching a browser so that the user can sign in. Alternatively, the authorization can be obtained separately (e.g., with the OAuth component) and passed to the mail component. This article covers both approaches, along with the settings for Gmail and Microsoft 365.

Contents

Registering an Application

Before an application can use OAuth, it must be registered with the provider, which issues a client ID (and usually a client secret) for the application. When registering the application, the following should be configured:

  • Google: In the Google Cloud console, enable the Gmail API, configure the OAuth consent screen, and create an OAuth client ID with the Desktop app application type.
  • Microsoft: In the Microsoft Entra admin center, register the application under App registrations, add a Mobile and desktop applications platform with the redirect URI http://localhost, and add the delegated permissions for the protocols the application uses (IMAP.AccessAsUser.All, POP.AccessAsUser.All, or SMTP.Send) from the Office 365 Exchange Online API.

The built-in OAuth support receives the response from the provider using an embedded web server on localhost, so the redirect URI registered for the application must allow this. Both Google desktop clients and Microsoft's http://localhost redirect URI accept any port on localhost.

Built-in OAuth Support

To use the built-in OAuth support, set the AuthMechanism property to amXOAUTH2, set the User property to the email address of the account, and set the fields of the OAuth property to the provider's values:

Field Description
ClientId The client ID issued when the application was registered.
ClientSecret The client secret issued when the application was registered (if any).
ServerAuthURL The URL of the provider's authorization endpoint.
ServerTokenURL The URL of the provider's token endpoint.
AuthorizationScope The scopes (permissions) requested by the application, separated by spaces.
RefreshToken A refresh token saved from a previous authorization (optional).

When the component connects to the server, it performs the OAuth authorization automatically. The component starts an embedded web server and launches a browser, where the user signs in to their account and grants the application access. The provider then redirects the browser back to the embedded web server, and the component exchanges the response for an access token, which it uses to log in to the mail server.

IMAP imap = new IMAP();
imap.MailServer = "imap.gmail.com";
imap.MailPort = 993;
imap.SSLStartMode = IMAPSSLStartModes.sslImplicit;
imap.User = "user@gmail.com";
imap.AuthMechanism = IMAPAuthMechanisms.amXOAUTH2;
imap.OAuth.ClientId = "CLIENT_ID";
imap.OAuth.ClientSecret = "CLIENT_SECRET";
imap.OAuth.ServerAuthURL = "https://accounts.google.com/o/oauth2/auth";
imap.OAuth.ServerTokenURL = "https://oauth2.googleapis.com/token";
imap.OAuth.AuthorizationScope = "https://mail.google.com/";
// Connect launches the browser so the user can sign in, and then logs in with the access token.
imap.Connect();

The component caches the access token, and automatically requests a new one using the refresh token when it expires. The LaunchBrowser event fires before the browser is launched, and can be used to change the URL or the command used to open it. The OAuthBrowserResponseTimeout configuration setting specifies how long the component waits for the user to finish signing in, and the OAuthWebServerPort configuration setting specifies the port of the embedded web server (by default, an available port is chosen automatically).

Saving and Reusing Refresh Tokens

So that the user does not need to sign in each time the application runs, the refresh token can be saved after a successful authorization, and then used the next time the application connects. After connecting, the refresh token is available in the RefreshToken field of the OAuth property. When this field is set before connecting, the component uses the refresh token to get a new access token without launching the browser.

// Use the refresh token saved from a previous run, if any.
imap.OAuth.RefreshToken = LoadRefreshToken();
imap.Connect();
// Save the refresh token, since some providers issue a new one each time it is used.
SaveRefreshToken(imap.OAuth.RefreshToken);

A refresh token grants access to the account, so it should be stored securely (e.g., encrypted, or in the operating system's credential store).

How long a refresh token remains valid depends on the provider:

  • Google refresh tokens remain valid until they are revoked, or until they have not been used for six months. While the application's consent screen is in the "Testing" publishing status, refresh tokens expire after seven days.
  • Microsoft refresh tokens expire after 90 days, but a new refresh token is returned each time one is used, so the saved value should be replaced after each connection. Microsoft only returns a refresh token when the offline_access scope is requested.

If the refresh token has expired or been revoked, the request for a new access token fails, and connecting throws an exception. In this case, discard the saved refresh token and connect again without it, so that the user can sign in through the browser.

Gmail

The settings for Gmail are listed below. All three protocols use the same scope, https://mail.google.com/.

Setting Value
ServerAuthURL https://accounts.google.com/o/oauth2/auth
ServerTokenURL https://oauth2.googleapis.com/token
AuthorizationScope https://mail.google.com/
IMAP server imap.gmail.com, port 993, sslImplicit
POP server pop.gmail.com, port 995, sslImplicit
SMTP server smtp.gmail.com, port 587, sslExplicit (or port 465, sslImplicit)

The following example demonstrates how to send a message through Gmail with the SMTP component.

SMTP smtp = new SMTP();
smtp.MailServer = "smtp.gmail.com";
smtp.MailPort = 587;
smtp.SSLStartMode = SMTPSSLStartModes.sslExplicit;
smtp.User = "user@gmail.com";
smtp.AuthMechanism = SMTPAuthMechanisms.amXOAUTH2;
smtp.OAuth.ClientId = "CLIENT_ID";
smtp.OAuth.ClientSecret = "CLIENT_SECRET";
smtp.OAuth.ServerAuthURL = "https://accounts.google.com/o/oauth2/auth";
smtp.OAuth.ServerTokenURL = "https://oauth2.googleapis.com/token";
smtp.OAuth.AuthorizationScope = "https://mail.google.com/";
smtp.OAuth.RefreshToken = LoadRefreshToken();
smtp.From = "user@gmail.com";
smtp.SendTo = "recipient@example.com";
smtp.Subject = "Test";
smtp.MessageText = "Hello, this is a test.";
smtp.Send();
SaveRefreshToken(smtp.OAuth.RefreshToken);

The POP component is configured in the same way, using the POP server settings above. Note that POP access must be enabled in the Gmail account's settings before connecting with POP.

Microsoft 365

The settings for Microsoft 365 (including Outlook.com accounts) are listed below. Each protocol requires its own scope, and the offline_access scope should be included so that a refresh token is returned.

Setting Value
ServerAuthURL https://login.microsoftonline.com/common/oauth2/v2.0/authorize
ServerTokenURL https://login.microsoftonline.com/common/oauth2/v2.0/token
IMAP scope https://outlook.office.com/IMAP.AccessAsUser.All offline_access
POP scope https://outlook.office.com/POP.AccessAsUser.All offline_access
SMTP scope https://outlook.office.com/SMTP.Send offline_access
IMAP server outlook.office365.com, port 993, sslImplicit
POP server outlook.office365.com, port 995, sslImplicit
SMTP server smtp.office365.com, port 587, sslExplicit

The common segment of the URLs allows both work or school accounts and personal Microsoft accounts to sign in. To allow only accounts from a single organization, replace it with the organization's tenant ID.

The following example demonstrates how to read messages from a Microsoft 365 mailbox with the POP component.

POP pop = new POP();
pop.MailServer = "outlook.office365.com";
pop.MailPort = 995;
pop.SSLStartMode = POPSSLStartModes.sslImplicit;
pop.User = "user@example.com";
pop.AuthMechanism = POPAuthMechanisms.amXOAUTH2;
pop.OAuth.ClientId = "CLIENT_ID";
pop.OAuth.ClientSecret = "CLIENT_SECRET";
pop.OAuth.ServerAuthURL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
pop.OAuth.ServerTokenURL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
pop.OAuth.AuthorizationScope = "https://outlook.office.com/POP.AccessAsUser.All offline_access";
pop.OAuth.RefreshToken = LoadRefreshToken();
pop.Connect();
SaveRefreshToken(pop.OAuth.RefreshToken);
Console.WriteLine("Messages: " + pop.MessageCount);
pop.Disconnect();

Applications registered as public clients (such as desktop applications) are not issued a client secret by Microsoft, so ClientSecret can be left empty in this case.

In Microsoft 365, IMAP, POP, and authenticated SMTP (SMTP AUTH) can be disabled for the organization or for individual mailboxes, and SMTP AUTH is disabled by default in newer tenants. If authentication fails even though an access token was obtained, check that the protocol is enabled for the mailbox in the Exchange admin center.

Using the OAuth Component

The built-in OAuth support uses the authorization code flow, in which a user signs in through a browser on the same machine. For other scenarios, such as web applications (where the user signs in through their own browser) or unattended services (which use the client credentials flow), the authorization can be obtained separately, and then passed to the mail component.

To do so, set the AuthorizationIdentity configuration setting to the authorization string (or access token) before connecting. When this setting is specified, the component uses it instead of the OAuth property.

The following example demonstrates how to use the OAuth component to get an authorization string for an unattended service that sends mail through Microsoft 365 using the client credentials flow.

OAuth oauth = new OAuth();
oauth.ClientProfile = OAuthClientProfiles.ocpApplication;
oauth.GrantType = OAuthGrantTypes.ogtClientCredentials;
oauth.ClientId = "CLIENT_ID";
oauth.ClientSecret = "CLIENT_SECRET";
oauth.ServerTokenURL = "https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/token";
oauth.AuthorizationScope = "https://outlook.office365.com/.default";
string authorization = oauth.GetAuthorization();
SMTP smtp = new SMTP();
smtp.MailServer = "smtp.office365.com";
smtp.MailPort = 587;
smtp.SSLStartMode = SMTPSSLStartModes.sslExplicit;
// The user is the mailbox that the application is sending mail from.
smtp.User = "mailbox@example.com";
smtp.AuthMechanism = SMTPAuthMechanisms.amXOAUTH2;
smtp.Config("AuthorizationIdentity=" + authorization);

The client credentials flow requires additional setup in Microsoft Entra ID and Exchange Online, including granting the application access to the mailboxes it uses. For more information, please see our Authenticating to Microsoft (Office) 365 with OAuth as an Unattended Application article.

Notes

  • The OAuth property is only used when AuthMechanism is set to amXOAUTH2. Without it, the component logs in with the User and Password properties, which most providers that require OAuth reject.
  • Errors returned by the provider during authorization (such as an invalid client ID or a redirect URI that does not match the registration) are reported as exceptions when connecting, and details are logged through the Log event.
  • Access tokens are issued for a specific account, so the User property must be set to the email address of the account that signed in (or, for the client credentials flow, the mailbox being accessed).
  • To use additional OAuth options, such as PKCE or extra parameters in the authorization request, see the OAuthUsePKCE and OAuthParamName configuration settings.

Note that in older versions of IPWorks, the mail components did not include built-in OAuth support, so the authorization string was always obtained with the OAuth component and passed through the AuthorizationIdentity configuration setting. This approach is still supported, as described in the Using the OAuth Component section.

We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.