Async/await/promises problem with MSAL.JS - javascript

I'm trying to write a function to acquire the Bearer token and return the headers before calling an API on behalf of the user, using MSAL.JS.
I think I'm struggling with the await/async/promises. Stuff I'm not really familiat with, at least in JS.
This is the function (I omitted the ssoSilent/login's part):
const prepareAPI = async (type) => {
const ssoRequest = {
loginHint: _userUpn,
scopes: ["openid", "profile", "user.read", _APIscope]
};
// if the user is already logged in you can acquire a token
if (msalInstance.getAccount()) {
var tokenRequest = {
scopes: [_APIscope]
};
try {
const resp = await msalInstance.acquireTokenSilent(tokenRequest);
console.log(resp.accessToken);
var headers = new Headers();
var bearer = "Bearer " + resp.accessToken;
headers.append("Authorization", bearer);
resolve({
method: type,
headers: headers
});
} catch (err) {
// could also check if err instance of InteractionRequiredAuthError if you can import the class.
if (err.name === "InteractionRequiredAuthError") {
return msalInstance.acquireTokenPopup(tokenRequest)
.then(response => {
return getOpts(response.accessToken, type);
})
.catch(err => {
console.log("Something went wrong:" + err);
});
}
}
} else {
// ... RequestSSO, loginPopup, etc,
}
};
I call the function like this:
prepareAPI("GET")
.then(opts => console.log("Headers: ", opts));
I get the token in the console.log(resp.accessToken) just before the resolve.
But "opts" is undefined, and looks like is executed before the token retrieval.

I don't see any reason in your code to use anything other than async await. Simplify the code by replacing .then with await and removing any .catch calls(It will throw instead). Make sure any async calls(resolve?) have an await statement. Keep in mind await is just syntax sugar for resolving a Promise where it returns the value on resolve or throws on reject.
Ex
prepareAPI("GET")
.then(opts => console.log("Headers: ", opts))
.catch(e => console.error(e));
becomes
try {
const opts = await prepareAPI("GET");
console.log("Headers: ", opts)
} catch (e) {console.error(e);}

Related

NodeJS Fetch Not Waiting

I'm trying to force Node to wait for either a success or a failure. I understood fetch to return a promise and I thought I told it how to handle both.
The following code does not honor the await I asked it to do:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " + JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " + JSON.stringify(other));
process.exit(3);
});
console.log('## after fetch fetchResult=' + fetchResult);
...
}
You might think that the await would cause it to, wait for the Promise to complete, but instead it leaves the whole function, and goes back to the caller. It does not print the '## after fetch fetchResult=' line. Neither the failure, nor success handler is executed.
I should point out that it also does not appear to make the requested POST call either. Instead, it sees that request and does something completely different without raising any exception.
Why is it not honoring the 'await' keyword whatsoever?
--- If I try the try/catch approach as follows:
async function getAccessToken() {
console.log('##getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('##getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('##getAccessToken BP3');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('##getAccessToken BP4');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('##getAccessToken BP5');
console.log('POST ' + argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
console.log('##getAccessToken BP6');
try {
console.log('##getAccessToken BP7');
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log('##getAccessToken BP8');
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log('##getAccessToken BP9');
console.log(`Other reached.`, json);
return json;
} catch (error) {
console.log('##getAccessToken BP10');
console.log(`!! something went wrong`, error.message);
console.error(error);
return error;
} finally {
console.log('##getAccessToken BP11');
console.log(`fetch finished`);
}
console.log('##getAccessToken BP12');
}
console.log('##getAccessToken BP13');
return "Should not have reached this point";
}
I get
##getAccessToken BP1
##getAccessToken BP2
##getAccessToken BP3
##getAccessToken BP4
##getAccessToken BP5
POST https://some-url
{}
##getAccessToken BP6
##getAccessToken BP7
As you can see, it goes just inside of the try block, then goes back to the caller without triggering the finally, error handlers or the logging after the fetch.
Using the .then approach as follows:
async function getAccessToken() {
console.log('##getAccessToken BP1');
if (argumentParserResult.authenticationScheme == 'OAUTH2') {
console.log('##getAccessToken BP2');
const fetch = require('node-fetch');
const url = argumentParserResult.resourceUrl;
console.log('##BP1.9');
let formData = new URLSearchParams({
'grant_type': 'client_credentials',
'client_id': argumentParserResult.clientId,
'scope': argumentParserResult.clientScope,
'client_secret': argumentParserResult.clientSecret
})
console.log('##getAccessToken BP3');
let headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
console.log('##getAccessToken BP4');
console.log('POST ' + argumentParserResult.authorizationUrl);
console.log(JSON.stringify(formData));
let response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log('##getAccessToken BP5');
console.log("Success reached. " + JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log('##getAccessToken BP6');
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('##getAccessToken BP7');
console.log('## after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
console.log('##getAccessToken BP8');
}
I get these logs:
##getAccessToken BP1
##getAccessToken BP2
##BP1.9
##getAccessToken BP3
##getAccessToken BP4
POST https://login.microsoftonline.com/5a9bb941-ba53-48d3-b086-2927fea7bf01/oauth2/v2.0/token
{}
As you can see above, it goes just to the point of the fetch, then returns to the calling function.
In neither case, can I see any evidence that the fetch was ever called.
Try this:
async function getAccessToken() {
try {
const response = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers,
});
console.log(`Success reached.`, JSON.stringify(response));
const json = await response.json();
console.log(`Other reached.`, json);
} catch (error) {
console.log(`!! something went wrong`, error.message);
console.error(error);
} finally {
console.log(`fetch finished`);
}
}
You don't need to use thenable object when writing with async/await, instead, catch the error with a try catch bloc, and just get the async value using return of awaited function.
You are mixing await and then. It is not forbidden, but in most simple case you don't need it.
Solution without then:
async function getAccessToken() {
try {
console.log('fetching data') // this log will always appear as first log, before fetching data
let fetchResult = await fetch(argumentParserResult.authorizationUrl,
{
method: 'POST',
body: formData,
headers: headers
})
let jsonR = await fetchResult.json()
console.log('fetch done') // this log will appear only if fetch is done with no errors
} catch (e) {
console.error('something went wrong', e) // this log will appear only if there was an error
}
console.log('after all') // this log will appear always, after fetch (even if fetch fails or not)
}
Solution with then:
async function getAccessToken() {
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
}).then(success => {
console.log("Success reached. " + JSON.stringify(success));
return success // !--> LOOK HERE, you should return the success variable
}).catch(e => {
console.log(e) // !--> LOOK HERE, if you catch the error, no error will be thrown to the caller
return e
});
console.log('## after fetch fetchResult=', fetchResult); // !--> LOOK HERE, this log will always log something now, it could be the responso or the error
}
As you can see, error handling is not quite convenient in the second solution. That's why you should not mix await with then, unless you know what you are doing
The point of async/await is to get rid of the callbacks and make the code more procedural. Your code:
async function getAccessToken() {
...
let fetchResult = await fetch(argumentParserResult.authorizationUrl, {
method: 'POST',
body: formData,
headers: headers
})
.then( success => {
console.log("Success reached. " + JSON.stringify(success));
process.exit(2);
}, other => {
console.log("Other reached. " + JSON.stringify(other));
process.exit(3);
});
console.log('## after fetch fetchResult=' + fetchResult);
...
}
fails, because you are
Waiting for fetch() to resolve and return a result, and
In your then() chain, you are
Invoking process.exit() in the case of either success or failure.
Than means you kill the entire process as soon as the call to fetch() resolves with either a success or a failure.
If you do something like this:
async function getAccessToken() {
...
const opts = {
method: 'POST',
body: formData,
headers: headers
};
const {json, err} = await execFetch( argumentParserResult.authorizationUrl, opts );
if ( err ) {
console.log("that didn't work!", err);
process.exit(1);
}
...
}
async function execFetch( url, opts ) {
const response = { json: undefined, err: undefined };
const { res, err } = await fetch( argumentParserResult.authorizationUrl, opts )
.then( res => ({ res , err: undefined }) )
.catch( err => ({ res: undefined , err }) );
if ( err ) {
response.err = err;
}
else if ( !res.ok ) {
// non-2xx HTTP status
response.err = new Error(`${res.status}: ${res.statusText}`);
}
else {
// the 2xx happy path: deserialize the JSON response body into a JS object
response.json = res.json();
}
return response;
}
Your call to fetch() will always succeed and hand you back a tuple with a json and an err property.
A successful call will return something like this:
{
json: { a: 1, b: 2, c: 3, },
err: undefined,
}
Whilst a call to fetch() that fails will return something like this:
{
json: undefined ,
err: /* some error object with details about what went south */,
}

How I can custom axios in Vue

I wrote a response with an axios interceptors and send the return value of this response to a js file named handleResponse. This js file takes the return value and returns a result to me. If I get an error, I have it drop to reject.
const instance = axios.create({
baseURL:
apiName === ""
? process.env.VUE_APP_API_URL
: process.env.VUE_APP_API_URL + apiName,
withCredentials: false,
headers: headers
});
instance.interceptors.response.use(
(response) => handleResponse(response),
(error) => console.log(error)
);
My handleResponse js file inside interceptors is as follows
export const handleResponse = (response) => {
return new Promise((resolve) => {
if (response.data["Success"]) resolve(response.data["Payload"]);
else {
let msg = "";
if (response.data["Information"]) msg = response.data["Information"];
showError(msg);
reject(response);
}
});
};
Here I make it fall into catch where I call the api when it drops to the reject operation.
const cancelStorageTransfer = () => {
return StorageTransferRequestService.cancelStorageTransfer(selectedStorageTransfer.value.Id)
.then(() => {
showSuccess("Transfer İptal İşlemi Başarıyla Gerçekleşti")
storageTransferRequestSummary()
}).catch(response => {
showError(response.data.Information)
})
}
I call the api here, but I don't want to use the catch. But when I don't use it, I get "Uncauth(in promise)" error on the log screen.
Here how can I do whether to use the catch at my own will?

Facebook Graph request with async await

I want to make a call to my backend (registerSanctumFacebook method) after a facebook Graph request to get user profile info (email), however I'm getting the following error:
await is only allowed within async functions
Pretty self explanatory, the problem is, I don't know how to make graph start method to work with async-await...
const getInfoFromToken = async (token) => {
const PROFILE_REQUEST_PARAMS = {
fields: {
string: 'email',
},
};
const profileRequest = new GraphRequest(
'/me',
{token, parameters: PROFILE_REQUEST_PARAMS},
(error, user) => {
if (error) {
console.log('login info has error: ' + error);
} else {
//this.setState({userInfo: user});
console.log('user:', user);
}
},
);
new GraphRequestManager().addRequest(profileRequest).start();
let response = await registerSanctumFacebook(user.email,user.id);
};
How I call getTokenInfo method:
const loginWithFacebook = async () => {
LoginManager.logInWithPermissions(['email']).then(
login => {
if (login.isCancelled) {
console.log('Login cancelled');
} else {
AccessToken.getCurrentAccessToken().then(data => {
const accessToken = data.accessToken.toString();
console.log('accessToken',accessToken);
getInfoFromToken(accessToken);
});
}
},
error => {
console.log('Login fail with error: ' + error);
},
);
};
As per your problem statement, I think you should add await on this line
await new GraphRequestManager().addRequest(profileRequest).start();
await will only work if the function after await keyword is also async.
or declare registerSanctumFacebook as async

How to resolve or reject a promise from the result of another promise?

Really sorry if this has been answered, I've searched everywhere and can't find the exact problem I'm facing.
Take this as the example:
const fetchData = (email, password) => new Promise(async (resolve, reject) => {
await axios.post('https://api.something.com', {
email: email,
password: password,
},
{
headers: {
'Content-Type': 'application/json',
}
})
.then(res => {
cookie = res.headers['set-cookie'];
})
.catch(err => {
return reject('Login failed');
});
await axios.get('https://api.something.com', {
headers: {
'cookie': cookie
}
})
.then(res => {
data = res;
})
.catch(err => {
return reject('Failed to retrieve something');
});
return resolve(data);
});
If the login credentials are incorrect, the 'Login failed' reject is sent but the script will keep on running and there will be an additional error message saying that cookie isn't set. I want to completely stop the script in the first catch.
I could use throw new Error('Login failed') and that would stop the script completely but I don't feel like that's the right answer and also because it makes me wonder what else could I use to resolve the promise (for other purposes) and still don't let the script continue running.
I'm also not interested in nesting functions, to avoid promise-callback christmas tree-like hell.
Am I making sense?
Because you use async/await you don't need create a new Promise and instead of then/catch use try/catch;
const fetchData = async (email, password) => {
let cookie;
try {
const res = await axios.post(
"https://api.something.com",
{
email,
password
},
{
headers: {
"Content-Type": "application/json",
},
}
);
cookie = res.headers["set-cookie"];
} catch (err) {
throw new Error("Login failed");
}
let data;
try {
const res = await axios.get("https://api.something.com", {
headers: {
cookie
},
});
data = res;
} catch (err) {
throw new Error("Failed to retrieve something");
}
return data;
};
If you want to use the Promise API instead of async/await you can chain the promises: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#chaining

Returning nested promises to another function

I have a NodeJS application and I think I have an issue with returning from inside a nested Promise.
As below, the getToken function is working. It calls another function to retrieve a password. After this, it uses the password value when making a GET call.
We then successfully get a token and we print the body to the console. This works.
However, I now have the challenge of passing the value of body which is my token, to another method for later consumption. printBodyValue currently fails and fails with an 'undefined' error.
How can I pass the value from deep inside getToken to printBodyValue
getToken: function() {
module.exports.readCredentialPassword()
.then(result => {
var request = require('request-promise');
var passwd = result;
var basicAuthData = "Basic " + (new Buffer("fooUser" + ":" + passwd).toString("base64"));
var options = {
method: "GET",
uri: ("http://localhost:8001/service/verify"),
followRedirects: true,
headers: {
"Authorization": basicAuthData
}
};
return request(options)
.then(function (body) {
console.log("Token value is: ", body);
return body;
})
.catch(function (err) {
console.log("Oops! ", err);
});
});
}
printBodyValue: function() {
module.exports.getToken().then(function(body) {
console.log("Token value from printBodyValue is: \n", body);
});
}
In getToken, instead of using the nested promise anti-pattern, chain your promises instead, and return the final promise, so that you can then consume the promise and use its resolved value:
(also, since you're using ES6, prefer const over var)
getToken: function() {
return module.exports.readCredentialPassword()
.then(result => {
const request = require('request-promise');
const passwd = result;
const basicAuthData = "Basic " + (new Buffer("fooUser" + ":" + passwd).toString("base64"));
module.exports.log("Sending Request: ", jenkinsCrumbURL);
const options = {
method: "GET",
uri: ("http://localhost:8001/service/verify"),
followRedirects: true,
headers: {
"Authorization": basicAuthData
}
};
return request(options);
})
.then(function(body) {
console.log("Token value is: ", body);
// the return value below
// will be the final result of the resolution of
// `module.exports.readCredentialPassword`, barring errors:
return body;
})
.catch(function(err) {
console.log("Oops! ", err);
});
}
printBodyValue: function() {
module.exports.getToken().then(function(body) {
console.log("Token value from printBodyValue is: \n", body);
});
}

Categories

Resources