ews-javascript-api
ews-javascript-api copied to clipboard
Error SoapFaultDetails message: '401 Unauthorized',
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('[email protected]'); 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
Microsoft no longer allows to use basic auth.
https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437
some way to extend it for some time. https://practical365.com/exchange-online-basic-authentication/
Thanks @gautamsi , what would you recommend that I should do instead of basic auth?
you have to use microsoft guide to get the oauth refresh and access token then pass access token to this library.
Hi Everyone,
Please find the solution we used to fix the same problem.
The solution consist of 3 parts:
- Setup application in AZURE.
- Add code to get accessToken.
- Made changes in old code to use previously received accessToken.
Part 1:
- Login to MS AZURE portal.
- Open "App registration" tool:

- Click "New Registration":

- Setup new App:

- After you click registrate button you will receive smtg like this:

- Open API permissions tab for previously created App + click Add permission and select MS Graph:

- Select Delegated permissions:

- Find User section and select User.Read + Add permission click:

- Add a permission again + APIs my organizaton uses tab(or find it) and find Office 365 Exchange Online:

- Find EWS section and select EWS.AccessAsUser.All and click Add permissons:

- Go to Authentication tab and click Add platform:

- Select Mobile and Desctop apps and click Save button:

- Select to options and click Configure:

14. Also on Authentication tab set "Supported accounts types" and "Allow public client flows" and click Save:
15. Go to Overview tab you should see smthg like this:
clientID
tenantId
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):
18. Now we can start add code allows us to get accessToken
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;
}
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:[email protected] 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;
}
Have a good day.