Read Outlook Mails via IMAP using nodejs and oauth2

saravanakumar saravanakumar 11 Reputation points
2022-06-03T06:14:42.097+00:00

My requirement is very simple.

I have a node server, which doesnt have login page, there is no interactivity login page(dont want to redirect using express.js). I want to connect my mailbox id and read all unread emails from nodejs using oauth2 access token, thats it enough. As i am new to Azure, i followed some steps from azure portal for app registrations set permissions to scopes ["Mail.ReadWrite", "User.Read", "Mail.ReadWrite.Shared", "offline_access"].

Anyone who knows, nodejs and Azure could help me out from this pathetic path. I was tried most of the way to websites, but no luck. I dont know, whether issue is in my code or in the azure app registration. I appreciate godfather of Azure and Nodejs developer, who can help in this issue. I am okay to have a call if possible through zoom:

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

To get oauth2 access token, followed the below code:

const msal = require('@azure/msal-node');  
  
/**  
 * Configuration object to be passed to MSAL instance on creation.   
 * For a full list of MSAL Node configuration parameters, visit:  
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md   
 */  
const msalConfig = {  
 auth: {  
 clientId: "*******************-589b9fbd51d3",  
        authority: "https://login.microsoftonline.com/*********************-d2c178decee1",  
        clientSecret: "***********************iqDQNl48FGc63"  
 }  
};  
  
const tokenRequest = {  
    scopes: ["https://graph.microsoft.com/.default"]  
 // scopes: ["https://outlook.office.com/IMAP.AccessAsUser.All"],  
      
};  
  
/**  
 * Initialize a confidential client application. For more info, visit:  
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/initialize-confidential-client-application.md  
 */  
  
  
  
/**  
 * Acquires token with client credentials.  
 * @param {object} tokenRequest   
 */  
async function getToken(tokenRequest) {  
    const cca = new msal.ConfidentialClientApplication(msalConfig);  
    const msalTokenCache = cca.getTokenCache();  
 return await cca.acquireTokenByClientCredential(tokenRequest);  
}  
  
async function main() {  
      
    try {  
        const authResponse = await getToken(tokenRequest);  
        console.log(authResponse.accessToken);  
    } catch (error) {  
        console.log(error);  
    }             
};  
  
main();  

My index.js for reading mails and using the above access token in the below code, for now i am manually copy and paste the token, later will merge these two code files for dynamic fetch.
Followed code was taken from here: https://www.npmjs.com/package/node-imap

var Imap = require('node-imap'),  
  inspect = require('util').inspect;  
const mailId = 'sstest**@******.com';  
const token =  
  'eyJ0eXAiOiJKV1QiLCJub25jZSI6ImRvd3R0S2draG1fVGN1T1g3S1p................';  
const auth2 = btoa('user=' + mailId + '^Aauth=Bearer ' + token + '^A^A');  
  
  
var imap = new Imap({  
  xoauth2: auth2,  
  host: 'outlook.office365.com',  
  port: 993,  
  tls: true,  
  debug: console.log,  
  authTimeout: 25000,  
  connTimeout: 30000,  
  tlsOptions: {  
    rejectUnauthorized: false,  
    servername: 'outlook.office365.com'  
  }  
});  
  
function openInbox(cb) {  
  imap.openBox('INBOX', true, cb);  
}  
  
imap.once('ready', function () {  
  openInbox(function (err, box) {  
    if (err) throw err;  
    var f = imap.seq.fetch('1:3', {  
      bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',  
      struct: true  
    });  
    f.on('message', function (msg, seqno) {  
      console.log('Message #%d', seqno);  
      var prefix = '(#' + seqno + ') ';  
      msg.on('body', function (stream, info) {  
        var buffer = '';  
        stream.on('data', function (chunk) {  
          buffer += chunk.toString('utf8');  
        });  
        stream.once('end', function () {  
          console.log(  
            prefix + 'Parsed header: %s',  
            inspect(Imap.parseHeader(buffer))  
          );  
        });  
      });  
      msg.once('attributes', function (attrs) {  
        console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));  
      });  
      msg.once('end', function () {  
        console.log(prefix + 'Finished');  
      });  
    });  
    f.once('error', function (err) {  
      console.log('Fetch error: ' + err);  
    });  
    f.once('end', function () {  
      console.log('Done fetching all messages!');  
      imap.end();  
    });  
  });  
});  
  
imap.once('error', function (err) {  
  console.log(err);  
});  
  
imap.once('end', function () {  
  console.log('Connection ended');  
});  
  
imap.connect();  






 
Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
22,095 questions
{count} vote

7 answers

Sort by: Most helpful
  1. Sambit Kumar Das 21 Reputation points
    2022-12-05T13:42:58.32+00:00

    Hi All,

    I have go through the above code .
    And I fixed the problem in the following process.

    you can encode username, access token together in the following format and It will work.

    let base64Encoded =  Buffer.from([`user=${mailId}`, `auth=Bearer ${token}`, '', ''].join('\x01'), 'utf-8').toString('base64');  
    

    var imap = new Imap({
    xoauth2: base64Encoded ,
    host: 'outlook.office365.com',
    port: 993,
    tls: true,
    debug: console.log,
    authTimeout: 25000,
    connTimeout: 30000,
    tlsOptions: {
    rejectUnauthorized: false,
    servername: 'outlook.office365.com'
    }
    });

    In this process I have fixed This above problem.
    and scope should be

    scopes:["https://outlook.office365.com/.default"]  
    
    4 people found this answer helpful.

  2. Shweta Mathur 29,781 Reputation points Microsoft Employee
    2022-06-06T07:08:10.217+00:00

    Hi @saravanakumar saravanakumar ,

    Thanks for reaching out.

    I understand you are looking to read all outlook mails from Nodejs server using oauth2 access token via IMAP.

    As you are looking to get access token without user interaction, in that case, client credential flow is recommended to get the access token which is commonly used for background process where impersonating a user is not required.

    As mentioned in the document "OAuth access to IMAP, POP, SMTP AUTH protocols via OAuth2 client credentials grant flow is not supported."

    We recommend using Microsoft Graph API which allow authorized access to read user's Outlook mail data without interactive user login.

    To call MS Graph API, your application must have appropriate application permissions when authenticating as an application (without user), to access the mail data. Delegated permissions won't work with client credential flow as there is no user for your app to act on behalf on.

    To read the mail data, Mail.Read application permission need to add in API permission blade while registering the application.

    208588-image.png

    Application permission always require admin consent ahead of time. You can grant the admin consent by clicking "Grant Admin Consent for <yourTenantName>"

    208644-image.png

    Once application permissions have been granted , you can call client credential flow to get the access token by passing below parameters.
    208634-image.png

    This access token need to pass as bearer token in authorization header to read outbox mails using Graph API endpoint https://graph.microsoft.com/v1.0/users/{userId}/messages

    208569-image.png

    Please find the nodejs sample to acquire token using client credential flow and call protected API. You can call Microsoft Graph API in similar way.

    Hope this will help.

    Thanks,
    Shweta

    -----------------------------------------

    Please remember to "Accept Answer" if answer helped you.

    2 people found this answer helpful.

  3. David 5 Reputation points
    2023-03-17T11:56:18.6+00:00

    Javascript code above works only if you follow https://www.limilabs.com/blog/oauth2-client-credential-flow-office365-exchange-imap-pop3-smtp

    Scopes

    Scopes in code above should be changed to https://outlook.office365.com/.default according to MS documentation:

    You must use https://outlook.office365.com/.default in the scope property in the body payload for the access token request.

    Working code fragment

    let base64Encoded = Buffer.from([`user=${mailId}`, `auth=Bearer ${token}`, '', ''].join('\x01'), 'utf-8').toString('base64');  
    
    var imap = new Imap({
        xoauth2: base64Encoded,
        host: 'outlook.office365.com',
        port: 993,
        tls: true,
        debug: console.log,
        authTimeout: 25000,
        connTimeout: 30000,
        tlsOptions: {
            rejectUnauthorized: false,
            servername: 'outlook.office365.com'
        }
    });
    ...
    

    When registering Service Principal, Object Id (Service Id) should be from Enterprise application - not from Registred applications!

    Same important note is from Microsoft documentation page:

    The OBJECT_ID is the Object ID from the Overview page of the Enterprise Application node (Azure Portal) for the application registration. It is not the Object ID from the Overview of the App Registrations node. Using the incorrect Object ID will cause an authentication failure.
    User's image

    Dictionary:

    Client id or Application id

    Client id or Application id is clientId in msalConfig:

    clientId: "..."

    Tenant Id or Directory ID

    Every organisation or company that is renting cloud from Azure is tenant in Azure AD (Azure Active Directory has many, many tenants). Tenenat Id is top most ID of your company in Azure.

    This id is used as authority in msalConfig:

    authority: "https://login.microsoftonline.com/<Directory (tenant) ID>",

    Client secret

    Secret is as clientSecret in msalConfig:

    clientSecret: "..."

    Check this link to create it correctly

    Logs

    <= 'A1 OK AUTHENTICATE completed.'
    => 'A2 CAPABILITY'
    <= '* CAPABILITY IMAP4 IMAP4rev1 AUTH=PLAIN AUTH=XOAUTH2 SASL-IR UIDPLUS MOVE ID UNSELECT CLIENTACCESSRULES CLIENTNETWORKPRESENCELOCATION BACKENDAUTHENTICATE CHILDREN IDLE NAMESPACE LITERAL+'
    <= 'A2 OK CAPABILITY completed.'
    => 'A3 NAMESPACE'
    <= '* NAMESPACE (("" "/")) NIL NIL'
    <= 'A3 OK NAMESPACE completed.'
    => 'A4 LIST "" ""'
    <= '* LIST (\\Noselect \\HasChildren) "/" ""'
    <= 'A4 OK LIST completed.'
    => 'A5 EXAMINE "INBOX"'
    <= '* 1390 EXISTS'
    <= '* 1390 RECENT'
    <= '* FLAGS (\\Seen \\Answered \\Flagged \\Deleted \\Draft $MDNSent)'
    <= '* OK [PERMANENTFLAGS ()] Permanent flags'
    <= '* OK [UNSEEN 1388] Is the first unseen message'
    <= '* OK [UIDVALIDITY 14] UIDVALIDITY value'
    <= '* OK [UIDNEXT 7984] The next unique identifier value'
    <= 'A5 OK [READ-ONLY] EXAMINE completed.'
    
    1 person found this answer helpful.

  4. saravanakumar saravanakumar 11 Reputation points
    2022-06-13T17:34:39.717+00:00

    Is it possible to read emails using imap but without basic auth method (username , password) ? like oauth2. Because i have tried below method also,

    var xoauth2 = require("xoauth2"),  
        xoauth2gen;  
      
    xoauth2gen = xoauth2.createXOAuth2Generator({  
        user: "sstestmbx@adobe.com",  
        scope: ["https://outlook.office.com/IMAP.AccessAsUser.All"],  
        accessUrl: 'https://login.microsoftonline.com/fa7b1b5a-7b34-*************/oauth2/v2.0/token',  
        clientId: "a4271100***************bd51d3",  
        clientSecret: "*********************--iqDQNl48FGc63",  
        refreshToken: "0.ASYAWht7-jR7h0OUrtLBeN7O4QARJ6SzQ1tAosZYm5-9UdMmAIw.AgABAAEAAAD************",  
    });  
      
      
    // SMTP/IMAP  
    xoauth2gen.getToken(function(err, token){  
        if(err){  
            return console.log(err);  
        }  
        console.log("AUTH XOAUTH2 " + token);  
    });  
    

    I could generate oauth2 token successfully, but getting <= 'A1 NO AUTHENTICATE failed.' all the time. I tried below changes in the above node js code, but all are vain:

     1. Change and Checked - Auth Failed.   
         Scopes update : ["https://outlook.office.com/IMAP.AccessAsUser.All"]   
      
     2. Change and Checked - Auth Failed.   
         Scopes update : ["https://graph.microsoft.com/IMAP.AccessAsUser.All"] // suggested in stackoverflow  
      
     3. Change and Checked - Auth Failed.   
         Scopes update : ["https://graph.microsoft.com/.default"] // suggested in stackoverflow  
      
     4. Changed config and Checked - Auth Failed.   
         user: mailId, *// test@contoso.onmicrosoft.com  and   test@contoso.com*  
         xoauth2: 'dXNlcj1zc3Rlc3RtYnhAYWRvYmUuY29tAWF.....',  
        host: 'outlook.office365.com',  
        port: 993,  
        tls: true,  
        secure: true,  
      
     5. Changed config and Checked - Auth Failed.   
         Buffer.from(`user=${mailId}^Aauth=Bearer ${access_token}^A^A`).toString('base64')  
    

    As per your suggestion, i requested to enable application level access scope for mail read and write instead of delegate, but they are saying, delegated permission is enough to access a single shared mailbox.

    Am i approaching wrongly, or i have think about other dimension apart from IMAP. I am blocked completely now. Could anyone help on with working code, would resolve all the issues ?


  5. Gruppo IT 16 Reputation points
    2022-11-24T09:24:21.54+00:00

    Hi, with node imap library I receive the error "A1 NO AUTHENTICATE".
    I'm using the scope "https://graph.microsoft.com/.default" with client_credentials flow and all these app permissions:

    263799-image.png

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.