Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error SoapFaultDetails message: '401 Unauthorized', #415

Open
fgisslen opened this issue Oct 21, 2022 · 8 comments
Open

Error SoapFaultDetails message: '401 Unauthorized', #415

fgisslen opened this issue Oct 21, 2022 · 8 comments

Comments

@fgisslen
Copy link

Hi,
I have used this package for a while and everything has been working perfectly.
Until now.

With exactly the same setup I nowadays get the response:
Error SoapFaultDetails {
message: '401 Unauthorized',
InnerException: null,
faultCode: null,
faultString: null,
faultActor: null,
responseCode: 127,
errorCode: 0,
exceptionType: null,
lineNumber: 0,
positionWithinLine: 0,
errorDetails: DictionaryWithStringKey {
keys: [],
keysToObjs: {},
objects: {},
keyPicker: [Function (anonymous)]
},
HttpStatusCode: 401,
Exception: ServiceRequestUnauthorizedException {
message: '401 Unauthorized',
InnerException: null
}
}

Here is my setup:

const EXCHANGE_URL = 'https://outlook.office365.com/EWS/Exchange.asmx';
const ewsLogin = config.exchange.login;
const ewsPassword = config.exchange.password;
const service = new ews.ExchangeService(ews.ExchangeVersion.Exchange2013);
service.Credentials = new ews.WebCredentials(ewsLogin, ewsPassword);
service.TraceEnabled = true;
service.TraceFlags = ews.TraceFlags.All;

const email = new ews.EmailMessage(service);
email.ToRecipients.Add('xx@xx.xx');
email.Subject = 'xxx';
email.Body = new ews.MessageBody('xxxx');
email.Body.BodyType = ews.BodyType.Text;
email.Send()
.then((val) => {
console.log("Sent");
})
.catch((e) => {
console.log("Error ", e);
});

Please help me :P

@gautamsi
Copy link
Owner

@fgisslen
Copy link
Author

fgisslen commented Oct 21, 2022

Thanks @gautamsi ,
what would you recommend that I should do instead of basic auth?

@gautamsi
Copy link
Owner

gautamsi commented Nov 3, 2022

you have to use microsoft guide to get the oauth refresh and access token then pass access token to this library.

@zhukovsv
Copy link

Hi Everyone,

Please find the solution we used to fix the same problem.

The solution consist of 3 parts:

  1. Setup application in AZURE.
  2. Add code to get accessToken.
  3. Made changes in old code to use previously received accessToken.

@zhukovsv
Copy link

zhukovsv commented Nov 16, 2022

Part 1:

  1. Login to MS AZURE portal.
  2. Open "App registration" tool:
    image
  3. Click "New Registration":
    image
  4. Setup new App:
    image
  5. After you click registrate button you will receive smtg like this:
    image
  6. Open API permissions tab for previously created App + click Add permission and select MS Graph:
    image
  7. Select Delegated permissions:
    image
  8. Find User section and select User.Read + Add permission click:
    image
  9. Add a permission again + APIs my organizaton uses tab(or find it) and find Office 365 Exchange Online:
    image
  10. Find EWS section and select EWS.AccessAsUser.All and click Add permissons:
    image
  11. Go to Authentication tab and click Add platform:
    image
  12. Select Mobile and Desctop apps and click Save button:
    image
  13. Select to options and click Configure:
    image

image
14. Also on Authentication tab set "Supported accounts types" and "Allow public client flows" and click Save:
image
15. Go to Overview tab you should see smthg like this:
clientID
tenantId
image
16. THIS STEP should be made BY EACH USER that WILL USE this API - use USER credentials to open this link (or YOUR ADMIN should make bulk apply). Check made changes by opening next link in browser in incognito mode(FOR each user). :
https://login.microsoftonline.com/ADD YOUR TENANTID/oauth2/v2.0/authorize?
client_id=ADD YOUR CLIENTID
&response_type=code
&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
&response_mode=query
&scope=EWS.AccessAsUser.All
&state=12345
17. After the opening previously generated link you should login and then receive another link in browser which shoud contains generated accessToken(secret):
image
18. Now we can start add code allows us to get accessToken

@zhukovsv
Copy link

zhukovsv commented Nov 16, 2022

Part 2 - get accessToken by using userName + userPassword to email box:

import * as path from 'path';
import { ExchangeService, EmailMessage, MessageBody, OAuthCredentials, AutodiscoverService, Folder, Item, ExchangeVersion } from 'ews-javascript-api';

public async getEmailAccessToken(
clientId: string,
tenantId: string,
emailUserName: string,
emailUserPassword: string,
cacheFilePath: string = .${path.sep}tokenCache.json) {

    const msal = require('@azure/msal-node');
    const { promises: fs } = require('fs');

    //Cache Plugin configuration         
    const beforeCacheAccess = async (cacheContext) => {
        try {
            const cacheFile = await fs.readFile(cacheFilePath, 'utf-8');
            cacheContext.tokenCache.deserialize(cacheFile);
        } catch (error) {
            // if cache file doesn't exists, create it
            cacheContext.tokenCache.deserialize(await fs.writeFile(cacheFilePath, ''));
        }
    };

    const afterCacheAccess = async (cacheContext) => {
        if (cacheContext.cacheHasChanged) {
            try {
                await fs.writeFile(cacheFilePath, cacheContext.tokenCache.serialize());
            } catch (error) {
                console.log(error);
            }
        }
    };

    const cachePlugin = {
        beforeCacheAccess,
        afterCacheAccess
    };

    const msalConfig = {
        auth: {
            clientId: clientId, // YOUR clientId
            authority: `https://login.microsoftonline.com/${tenantId}` // YOUR tenantId
        },
        cache: {
            cachePlugin
        },
        system: {
            loggerOptions: {
                loggerCallback(loglevel, message, containsPii) {
                    console.log(message);
                },
                piiLoggingEnabled: false,
                logLevel: msal.LogLevel.Verbose
            }
        }
    };

    const pca = new msal.PublicClientApplication(msalConfig);

    const msalTokenCache = pca.getTokenCache();

    const accounts = await msalTokenCache.getAllAccounts();

    // Acquire Token Silently if an account is present
    let accessToken = null;

    if (accounts.length > 0) {
        const silentRequest = {
            account: accounts[0], // Index must match the account that is trying to acquire token silently
            scopes: ['https://outlook.office365.com/EWS.AccessAsUser.All'],
        };

        const response = await pca.acquireTokenSilent(silentRequest);

        accessToken = response.accessToken;
    } else {
        // fall back to username password if there is no account
        const usernamePasswordRequest = {
            scopes: ['https://outlook.office365.com/EWS.AccessAsUser.All'],
            username: emailUserName, // Add your username here      
            password: emailUserPassword, // Add your password here
        };

        const response = await pca.acquireTokenByUsernamePassword(usernamePasswordRequest);

        accessToken = response.accessToken;
    }

    return accessToken;
}

@zhukovsv
Copy link

zhukovsv commented Nov 16, 2022

Part 3 - connect to emailbox:
import { ExchangeService, EmailMessage, MessageBody, OAuthCredentials, AutodiscoverService, Folder, Item, ExchangeVersion } from 'ews-javascript-api';

public async connectAndChangeAllEmailsFromBlaBla(
clientId: string,
tenantId: string,
exchangeServiceUrl: string = 'https://outlook.office365.com/Ews/Exchange.asmx',
emailUserName: string,
emailUserPassword: string,
searchMask: string = 'hasattachments:yes and from:NoReply@blabla.com and received:today') {

    // get acces token by method written above in part 2
    const emailAccessToken = await this.getEmailAccessToken(clientId, tenantId, emailUserName, emailUserPassword);

    const ews = require('ews-javascript-api');
    const service = new ExchangeService(ews.ExchangeVersion.Exchange2013);

    // use emailAccesToken
    service.Credentials = new OAuthCredentials(emailAccessToken);

    service.Url = new ews.Uri(exchangeServiceUrl);

    const mailInbox = await ews.Folder.Bind(service, ews.WellKnownFolderName.Inbox);
    const loadPageSize = 1000; // 1 means load last email according to filter

    const view = new ews.ItemView(loadPageSize);
    view.PropertySet = new ews.PropertySet(ews.BasePropertySet.FirstClassProperties);
    let mailItems;
    // hasattachment:yes
    // isread:false
    // received:today or received:[date]
    mailItems = await mailInbox.FindItems(searchMask, view);
    console.log(`Emails were found before processing: ${mailItems.Items.length}`);

    for (const item of mailItems.Items) {
        // mark mail.item as read
        item.IsRead = true;
        await item.Update(1);
        // Do what you want
    }

    return mailItems.Items.length;
}

@zhukovsv
Copy link

Have a good day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants