Markus Moltke
Markus Moltke

Reputation: 1151

java - IMAP connection to outlook using OAUTH2 serverside

I have currently been trying to access a office 365 account via the IMAP protocol. I am using Jakarta Mail (Javax.mail) to try the connection. When I try to authenticate I get the following error:

DEBUG IMAP: trying to connect to host "imap-mail.outlook.com", port 993, isSSL true
* OK The Microsoft Exchange IMAP4 service is ready. [...]
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS ID UNSELECT CHILDREN IDLE NAMESPACE LITERAL+
A0 OK CAPABILITY completed.
DEBUG IMAP: AUTH: PLAIN
DEBUG IMAP: AUTH: XOAUTH2
DEBUG IMAP: protocolConnect login, host=imap-mail.outlook.com, user=[...], password=<non-null>
DEBUG IMAP: SASL Mechanisms:
DEBUG IMAP:  XOAUTH2
DEBUG IMAP: 
DEBUG IMAP: SASL client XOAUTH2
DEBUG IMAP: SASL callback length: 2
DEBUG IMAP: SASL callback 0: javax.security.auth.callback.NameCallback@11d86b9d
DEBUG IMAP: SASL callback 1: javax.security.auth.callback.PasswordCallback@6dce59e
A1 AUTHENTICATE XOAUTH2 [...]
A1 NO AUTHENTICATE failed.

With the system that I am making it is a requirement that I don't have to manually get the access token every time from a login link. To this extent, I thought the smartest move would be to use grant_type=client_credentials. However, I have not been getting this to work.

The code I use for the connection is as follows:

    Properties props = new Properties();

    // SMTP
    props.put("mail.smtp.host", "smtp-mail.outlook.com");
    props.put("mail.smtp.port", "587");
    props.put("mail.smtp.starttls.enable", "true");

    // IMAP
    props.put("mail.imap.ssl.enable", "true"); // required for Gmail
    props.put("mail.imap.sasl.enable", "true");
    props.put("mail.imap.sasl.mechanisms", "XOAUTH2");
    props.put("mail.imap.auth.login.disable", "true");
    props.put("mail.imap.auth.plain.disable", "true");

    // Debug
    props.put("mail.debug", "true");
    props.put("mail.debug.auth", "true");

    Session session = Session.getInstance(props);
    session.setDebug(true);

    String accessToken = renewToken();
    try {
      Store mailStore = session.getStore("imap");
      mailStore.connect("imap-mail.outlook.com", System.getenv("MAIL_USERNAME"), accessToken);
    } catch (MessagingException e) {
      throw new RuntimeException(e);
    }

and the renewToken() looks like the following:

  @Nullable
  private String renewToken() {
    if(System.currentTimeMillis() > tokenExpires) {
      try {
        String request = "client_id=" + URLEncoder.encode(System.getenv("CLIENT_ID"), StandardCharsets.UTF_8)
                + "&client_secret=" + URLEncoder.encode(System.getenv("CLIENT_SECRET"), StandardCharsets.UTF_8)
                + "&scope=" + URLEncoder.encode("https://outlook.office365.com/.default", StandardCharsets.UTF_8)
                + "&grant_type=client_credentials";
        HttpURLConnection conn = (HttpURLConnection) new URL(TOKEN_URL).openConnection();
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        PrintWriter out = new PrintWriter(conn.getOutputStream());
        out.print(request);
        out.flush();
        out.close();
        conn.connect();
        HashMap<String,Object> result = new ObjectMapper().readValue(conn.getInputStream(), new TypeReference<>() {});
        tokenExpires = System.currentTimeMillis()+(((Number)result.get("expires_in")).intValue()* 1000L);
        return (String)result.get("access_token");
      } catch (IOException e) {
        LOGGER.error(e.getMessage());
      }
    }
    return null;
  }

Finally the CLIENT_AUTHORITY is defined as https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token Where the {tenant} is replaced with the tenant id from https://portal.azure.com

As it is now, I am not really sure why I am getting the error, and don't really understand the meaning of the "NO AUTHENTICATE failed" error. The only thing that comes to mind is that the access token is either wrongly generated or I am not requesting the correct permissions.

It should also be mentioned that I have tried with the host "outlook.office365.com" instead of "imap-mail.outlook.com" without any luck.

Upvotes: 1

Views: 8067

Answers (1)

Markus Moltke
Markus Moltke

Reputation: 1151

I got it working, by following this guide:

Authentication Failure for IMAP using Client Credential flow for OAuth2.0 | Java | Exchange Online

The error for me was that the guide provided on the https://learn.microsoft.com website did not explain how to use the commands needed to enable OAuth2.0 properly.

Upvotes: 1

Related Questions