Authenticate to Graph API from Azure Functions with JavaScript - azure

I would like to create an azure function using NodeJS and authenticate to Graph APIs. Through my reading I know that I have to use client credentials flow. I am using this code as posted by:
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void>
const APP_ID = [appId]';
const APP_SECERET = '[secret]';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/[tenantid]/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
context.res = {
body: response.data //JSON.stringify(w, null, 4)
};
})
.catch(error => {
console.log(error);
});
};
As mentioned in this post: How to get users from azure active directory to azure function
However this is not working as it's not even making a request to Azure. Is there something missing? Can't I use MSAL.JS to make server to server calls when am using Node or is it just for web based apps and won't work with azure functions?
Most of the examples am seeing are related to .Net and they're using bunch of nuget packages, etc.. Isn't what I need supported in JavaScript azure functions?
Thanks.

I don't know why did you say it's not even making a request to azure, I test it with almost same code with yours and it work fine. I provide details of my steps as below for your reference.
1. I create a type script function in VS code (do not forget declare require in the second line of my code), my function code show as:
import { AzureFunction, Context, HttpRequest } from "#azure/functions"
declare var require: any
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const APP_ID = 'xxxxxx';
const APP_SECERET = 'xxxxxx';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/xxxxxx/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
console.log('=====below is response====');
console.log(response.data);
console.log('=====above is response====');
context.res = {
body: response.data
};
})
.catch(error => {
console.log(error);
});
};
export default httpTrigger;
2. I install axios and qs modules by the command:
npm install axios
npm install qs
3. To start the function, I ran the command:
npm install
npm start
4. After request the function, I get the result as:

Related

How to get Azure access token with Node js API using Axios

I have a backend in Nodejs using Axios for my API calls. I need to implement Azure Authentication to get a token so I followed the sample below:
https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-nodejs-webapp-msal?WT.mc_id=Portal-Microsoft_AAD_RegisteredApps
The sample uses express and has redirects to first get and authorization and then a token, I have been trying to find a sample with Axios however I couldn't find one.
This is what I have so far, the idea is using the result to get a token,any guidance is much appreciate it.
const msal = require('#azure/msal-node');
const REDIRECT_URI = "http://localhost:3000/";
const LOGIN = "https://login.microsoftonline.com/";
const config = {
auth: {
clientId: "12345678910",
authority: "https://login.microsoftonline.com/12345678910",
clientSecret: "Secret",
knownAuthorities: ["https://login.microsoftonline.com/12345678910"
]
}
};
const pca = new msal.ConfidentialClientApplication(config);
module.exports = {
async getAzureAdToken(){
try {
let instance = axios.create({baseURL: LOGIN});
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: REDIRECT_URI
};
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) =>{
let url = response.substring(LOGIN.length);
instance.get(url).then((result) =>{
});
}).catch((error) => console.log(JSON.stringify(error)));
} catch (error) {
throw error
}
},
You could use client credentials flow to get access token with axios. Client credentials flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. In the client credentials flow, permissions are granted directly to the application itself by an administrator. We need to add application permissions in API Permission.
Test in Postman:
POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id=<client_id>
&scope=https://graph.microsoft.com/.default
&client_secret=<client_secret>
&grant_type=client_credentials
Code using Nodejs:
// Replace these values from the values of you app
const APP_ID = '[APP_ID/CLIENT_ID]';
const APP_SECERET = '[CLIENT_SECRET]';
const TOKEN_ENDPOINT ='https://login.microsoftonline.com/[TENANT_ID]/oauth2/v2.0/token';
const MS_GRAPH_SCOPE = 'https://graph.microsoft.com/.default';
const axios = require('axios');
const qs = require('qs');
const postData = {
client_id: APP_ID,
scope: MS_GRAPH_SCOPE,
client_secret: APP_SECERET,
grant_type: 'client_credentials'
};
axios.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded';
let token = '';
axios
.post(TOKEN_ENDPOINT, qs.stringify(postData))
.then(response => {
console.log(response.data);
})
.catch(error => {
console.log(error);
});

Nodejs fs.createReadStream can't read Firebase Storage download URL "ENOENT: no such file or directory"

The documentation for fs.createReadStream(path) says that it can read a URL:
path <string> | <Buffer> | <URL>
The documentation shows quotes around the path:
fs.createReadStream('/dev/input/event0');
But when I put in this Firebase Storage download URL
fs.createReadStream('https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e'),
I get the error message ENOENT: no such file or directory. You can click on the URL and hear that it works:
https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e
I also tried the Google Storage URI:
fs.createReadStream('gs://languagetwo-cd94d.appspot.com/Users/bcmrZDO0X5N6kB38MqhUJZ11OzA3/audio-file.flac'),
That didn't work. Do I have to use https.request to make an HTTP GET request?
Here's my full code, copied from the IBM Cloud Speech-to-Text API docs:
const fs = require('fs');
const SpeechToTextV1 = require('ibm-watson/speech-to-text/v1');
const { IamAuthenticator } = require('ibm-watson/auth');
const speechToText = new SpeechToTextV1({
authenticator: new IamAuthenticator({
apikey: 'my-api-key',
}),
url: 'https://api.us-south.speech-to-text.watson.cloud.ibm.com/instances/01010101',
});
var params = {
audio: fs.createReadStream('https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e'),
contentType: 'audio/flac',
wordAlternativesThreshold: 0.9,
keywords: ['colorado', 'tornado', 'tornadoes'],
keywordsThreshold: 0.5,
};
speechToText.recognize(params)
.then(results => {
console.log(JSON.stringify(results, null, 2));
})
.catch(err => {
console.log('error:', err);
});
To get the file, you have to open up a write stream, then pipe the output of the http library (axios in this example) to that stream.
This would serve you better:
const Fs = require('fs')
const Path = require('path')
const Axios = require('axios')
async function downloadImage () {
const url = 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true'
const path = Path.resolve(__dirname, 'images', 'code.jpg')
const writer = Fs.createWriteStream(path)
const response = await Axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}
source
If you see here, it clearly mentions that it only supports file:// protocol. There is no support for http/https.
I wouldn't suggest using request or request-promise as they have been deprecated. I would recommend using some modern libraries like got, axios etc.
Another question I asked answers this question as well. Axios not only gets the file but also streams it, eliminating the fs.createReadStream.

Can't Find Response Headers in an NodeJS - ExpressJS App

I have set up a simple api request using node/express.js to access the Feedly Dev Api: https://developer.feedly.com/. The Feedly Api comes with two response headers -- X-Ratelimit-Count and X-Ratelimit-Reset. The problem is that I can't figure out how to access.
Here is the relevant parts of my express.js file:
app.get('/feedly', async (request, response) => {
const apiUrl =
'https://cloud.feedly.com/v3/streams/contents?streamId=user/[USER_ID]/category/global.all'
const fetchResponse = await fetch(apiUrl, {
headers: {
Authorization: `Bearer ${process.env.FEEDLY_ACCESS_TOKEN}`
}
})
const json = await fetchResponse.json()
response.json(json)
response.end()
})
what do I have to do in order to see both X-Ratelimit-Count and X-Ratelimit-Reset response headers?
Thanks.
Fetch API: Headers
(async() => {
const fetchResponse = await fetch("https://httpbin.org/anything");
console.log(fetchResponse.headers.get('X-Ratelimit-Count'))
console.log(fetchResponse.headers.get('X-Ratelimit-Reset'))
fetchResponse.headers.forEach((v, k) => console.log(`${k}: ${v}`));
})();

Consume a nopCommerce API behind OAuth2.0

My goal is to consume an API which has already been deployed with nopCommerce (I do not have dev access to the server - I am just a user). There is a sample client application here, but the code is in C#. I have a webapp deployed on an Azure server using node.js. The API uses the OAuth 2.0 Authorization Code grant type.
I did a bit of Googling and it appears that client_credentials is typically used for this type of server to server flow:
How does 2-legged oauth work in OAuth 2.0?
Using OAuth for server-to-server authentication?
There is also an answer here which suggests that I can manually retrieve a token and then store it on the server. This is what I'm currently doing while testing my code.
I also found this answer which appears to be asking the same question. Like the author of that post, I am able to get a token via Postman, but my node.js code fails.
OAuth2.0 for nopCommerce
I wrote a minimal example in node.js here.
import { config } from 'dotenv';
import * as path from 'path';
import fetch from 'cross-fetch';
const ENV_FILE = path.join(__dirname, '.env');
const loadFromEnv = config({ path: ENV_FILE });
export async function getCodeUrl() {
const params = {
client_id: <CLIENT_ID>,
redirect_uri: 'http://example.com',
response_type: 'code',
};
console.log(params);
const url = new URL(`http://example.com/OAuth/Authorize`);
Object.keys(params).forEach(( key ) => url.searchParams.append(key, params[key]));
const res = await fetch(url.href, { method: 'GET' });
return res;
}
export async function getToken(code: string) {
const url = new URL(`http://example.com/api/token`);
const options = {
form: {
client_id: <CLIENT_ID>,
client_secret: <CLIENT_SECRET>,
code,
grant_type: 'authorization_code',
redirect_ui: 'http://example.com',
},
headers: { 'content-type': 'application/x-www-form-urlencoded' },
method: 'POST',
};
console.log(options);
const res = await fetch(url.href, options);
console.log('res', res);
return res;
}
const test = async () => {
const codeUrlString = (await getCodeUrl()).url;
const code = (new URL(codeUrlString).searchParams.get('code'));
if (code) {
console.log('code', code);
const tokenResponse = await getToken(code);
console.log('token res', tokenResponse);
}
};
test();
I am successfully able to retrieve the authorization code, but when I use that in a POST request to get a token, I get this error:
{ error: 'invalid_client' }

How can I use node-mocks-http when mocking the AWS SDK?

I've got an existing working test that is testing a route in an Express app (trimmed code):
const AWS = require('aws-sdk-mock');
const AWS_SDK = require('aws-sdk');
AWS.setSDKInstance(AWS_SDK);
...
before(() => {
sendEmailMock = sinon.stub().callsArgWith(1, null, 'All is well');
AWS.mock('SES', 'sendEmail', sendEmailMock);
server = rewire('../../../..');
...
describe('POST:/feedback', () => {
it('Returns 200 with a fully formed request', (done) => {
request(app)
.post('/gethelp/api/v1/feedback')
.send({
thumbsUp: 'true',
title: 'Abcdef ghi',
url: 'http://google.com',
comments: 'lkajsdj lkajsdkjf aslkjdfa asjdflasjd lkfj',
})
.expect(200, () => {
const args = sendEmailMock.args[0][0];
... etc
This is a working test. But I need to refactor it to not use the full server (because it's doing some integration stuff on startup). So I'm bringing in node-mocks-http:
const httpMocks = require('node-mocks-http');
const feedbackRouteHandler = require('./feedback');
...
before(() => {
sendEmailMock = sinon.stub().callsArgWith(1, null, 'All is well');
AWS.mock('SES', 'sendEmail', sendEmailMock);
});
...
const mockRequest = httpMocks.createRequest({
method: 'POST',
url: '/gethelp/api/v1/feedback',
body: {
thumbsUp: 'true',
title: 'Abcdef ghi',
url: 'http://google.com',
comments: 'lkajsdj lkajsdkjf aslkjdfa asjdflasjd lkfj',
},
});
const mockResponse = httpMocks.createResponse();
feedbackRouteHandler(mockRequest, mockResponse);
expect(mockResponse.statusCode).to.equal(200);
expect(sendEmailMock.args).to.exist;
The problem is that adding in node-mocks-http appears to have broken the mocking of the AWS SDK. When sendEmail is hit it's hitting the actual AWS SDK, not the mocked version. It was hitting the mocked version in the previous version of the test.
How can I use node-mocks-http with aws-sdk-mock?

Resources