Moving Basic Authentication project to Oauth

304 views Asked by At

Since Microsoft disabled Basic authentication, I need to change this project for using OAuth and I cannot get it to work. Any help would be greatly appreciated.

old code:

    // expose our config directly to our application using module.exports
module.exports = {
  // this user MUST have full access to all the room accounts
  'exchange' : {
    'username'  : process.env.USERNAME || '[email protected]',
    'password'  : process.env.PASSWORD || 'PASSWORD',
    'uri'       : 'https://outlook.office365.com/EWS/Exchange.asmx'
  },
  // Ex: CONTOSO.COM, Contoso.com, Contoso.co.uk, etc.
  'domain' : process.env.DOMAIN || 'DOMAIN.COM'
};


    module.exports = function (callback) {

  // modules -------------------------------------------------------------------
  var ews = require("ews-javascript-api");
  var auth = require("../../config/auth.js");

  // ews -----------------------------------------------------------------------

  var exch = new ews.ExchangeService(ews.ExchangeVersion.Exchange2016);
  exch.Credentials = new ews.ExchangeCredentials(auth.exchange.username, auth.exchange.password);
  exch.Url = new ews.Uri(auth.exchange.uri);

  // get roomlists from EWS and return sorted array of room list names
  exch.GetRoomLists().then((lists) => {
    var roomLists = [];

    lists.items.forEach(function (item, i, array) {
      roomLists.push(item.Name);
    });
    callback(null, roomLists.sort());
  }, (err) => {
    callback(err, null);
  });

};
1

There are 1 answers

0
feng63600 On

I recently went into the exactly situation and finally got it working after spending countless hours. Hope this post will help some lost souls like I was.

So, what happened? Basic auth to MS Exchange Online will be disabled by end of 2022. All relevant applications' authentication will require updates.

reference: https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437

How to do it? My use case is a simple one. A mail daemon application 1) logins and 2) download some email attachments. What happens in the background and what do you need to do are written in below article.

reference: https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth

In summary, you need to follow below steps:

  1. Register your application with Azure Active Directory
  2. Get an access token
  3. Add required permissions to your app. If you are using plain password for your old project, you may refer to Use client credentials grant flow to authenticate IMAP and POP connections section in the above article. Below are a list of permissions required for my simple app. I added permission regarding email sending for future use:
  • Microsoft Graph:

    • IMAP.AccessAsUser.All
    • offline_access
    • openid
    • POP.AccessAsUser.All
    • profile
    • SMTP.Send
  • Office 365 Exchange Online:

    • full_access_as_app
    • IMAP.AccessAsApp
    • Mail.Read
    • Mail.ReadWrite
    • Mail.Send
  1. Get tenant admin consent to your app (done by your Azure admin).

  2. Register service principals in Exchange (done by your Azure admin).

This blog will talk you through above procedures: https://blog.hametbenoit.info/2022/07/01/exchange-online-use-oauth-to-authenticate-when-using-imap-pop-or-smtp-protocol/#.Y6RdVXZBxm7

Authentication failed? you may be able to retrieve a token from Exchange server, but got an error message:"A1 NO AUTHENTICATE failed" when trying to connect to Exchange server. If you took above steps one by one, it was most likely to be a permission related issue, please refer to the list in step 3. Unfortunately, this is where took me longest to test and Exchange server does not provide more information than "you are screwed", which is a pity.

Last but not the least... Here is my sample Java code. The simple app authenticate with Exchange server using IMAP. Apache HttpCore and Jackson libraries are required.

1 - Class for access token generation:

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;


public class OAuthDL {

    public String getAuthToken() {
        
        String token = "";
        HttpClient httpClient = HttpClients.createDefault();
        
        String tenant_id = "from Azure portal";
        String client_id = "from Azure portal";
        String client_pw = "created after app was registered";
        String scope = "https://outlook.office365.com/.default";
        
        HttpPost httpPost = new HttpPost("https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token");
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("grant_type", "client_credentials"));
        params.add(new BasicNameValuePair("client_id", client_id));
        params.add(new BasicNameValuePair("client_secret", client_pw));
        params.add(new BasicNameValuePair("scope", scope));;
        
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        try {
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity respEntity = response.getEntity();

            if (respEntity != null) {
                String content =  EntityUtils.toString(respEntity);
                
                ObjectNode node = new ObjectMapper().readValue(content, ObjectNode.class);
                if (node.has("access_token")) {
                    token = node.get("access_token").asText();
                }
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return(token);
    }

    public static void main(String[] args) {
        OAuthDL oa = new OAuthDL();
        String token = oa.getAuthToken();
        System.out.println("Token: " + token);
    }

}
  1. Class that configures protocols and authenticate with Exchange server. JavaMail is required:

    import java.util.Properties; import javax.mail.Folder; import javax.mail.Session; import javax.mail.Store;

    public class ImapMailBoxReader {

     private String host;
     private String username;
     private String password;
    
     public ImapMailBoxReader(String host, String username, String password) {
         this.host = host;
         this.username = username;
         this.password = password;
     }
    
     public void testConnection(String folder) {
         try {
    
             String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
             Properties props= new Properties();
    
             props.put("mail.imaps.ssl.enable", "true");
             props.put("mail.imaps.sasl.enable", "true");
             props.put("mail.imaps.port", "993");
    
             props.put("mail.imaps.auth.mechanisms", "XOAUTH2");
             props.put("mail.imaps.sasl.mechanisms", "XOAUTH2");
    
             props.put("mail.imaps.auth.login.disable", "true");
             props.put("mail.imaps.auth.plain.disable", "true");
    
             props.setProperty("mail.imaps.socketFactory.class", SSL_FACTORY);
             props.setProperty("mail.imaps.socketFactory.fallback", "false");
             props.setProperty("mail.imaps.socketFactory.port", "993");
             props.setProperty("mail.imaps.starttls.enable", "true");
    
             props.put("mail.debug", "true");
             props.put("mail.debug.auth", "true");
    
             Session session = Session.getDefaultInstance(props, null);
             session.setDebug(true);
             Store store = session.getStore("imaps");
             store.connect(host, username, password);
    
             Folder inbox = store.getFolder(folder);
             inbox.open(Folder.READ_ONLY);
    
             inbox.close(false);
             store.close();
    
         } catch (Exception e) {
             e.printStackTrace();
         }
     }
    
     public static void main(String[] args) {
         String host = "outlook.office365.com";
         String username = "your email address";
         OAuthDL oa = new OAuthDL();
         String password = oa.getAuthToken();
    
         ImapMailBoxReader reader = new ImapMailBoxReader(host, username, password);
         reader.testConnection("inbox");
     }
    

    }

Hope this helps.