3

I seem to be having an issue with getting the expected response from a fetch call within a firebase cloud function. I'm sure it's due to my lack of knowledge on how the responses, promises, etc. work.

I'm trying to use atlassian crowd's rest api for SSO. If I use postman, I can get the desired results from the request. So I know that part of it is working.

What led me to using a cloud function is that making the same request using fetch was resulting in CORS issues from localhost. I figured if I can take the browser out of the equation, then the CORS issues would disappear. Which they have, but I'm not getting the desired response.

My cloud function looks like this:

const functions = require('firebase-functions');
const fetch = require('node-fetch');
const btoa = require('btoa');
const cors = require('cors')({origin:true});

const app_name = "app_name";
const app_pass = "app_password";

exports.crowdAuthentication = functions.https.onRequest((request, response)=>
{
    cors(request, response, () =>{

        let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
        let _headers = {
            'Content-Type':'application/json',
            'Authorization':`Basic ${btoa(`${app_name}:${app_pass}`)}`
        }


        let _body = {
            username: request.body.username,
            password: request.body.password
        }

        const result = fetch(_uri, {
            method: 'POST',
            headers: _headers,
            body: JSON.stringify(_body),
            credentials: 'include'
        })

        response.send(result);
    })
})

I'm then making the call in my application using fetch to the firebase endpoint and passing the username/password:

fetch('https://my.firebase.endpoint/functionName',{
            method: 'POST',
            body: JSON.stringify({username:"myusername",password:"mypassword"}),
            headers: {
                'Content-Type':'application/json'
            }
        })
        // get the json from the readable stream
        .then((res)=>{return res.json();})
        // log the response - {size:0, timeout:0}
        .then((res)=>
        {
            console.log('response: ',res)
        })
        .catch(err=>
        {
            console.log('error: ',err)
        })

Thanks for looking.

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
gin93r
  • 1,551
  • 4
  • 21
  • 39
  • 1
    Which error do you receive? – Renaud Tarnec Nov 06 '19 at 16:37
  • @RenaudTarnec I don't get an error. I get a response of type cors, and the body is a ReadableStream. In other attempts to get this working I'd receive an object `{size:0, timeout:0}` but I haven't received that in a while. – gin93r Nov 06 '19 at 16:46

1 Answers1

6

Edit of May 2020

Note that request-promise is deprecated and I recommend to use axios.


Update following our discussion in the comments below

It appears that it doesn't work with the node-fetch library and that you should use another library like request-promise.

Therefore you should adapt your code as follows:

//......
var rp = require('request-promise');


exports.crowdAuthentication = functions.https.onRequest((request, response) => {

    cors(request, response, () => {

        let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
        let _headers = {
            'Content-Type': 'application/json',
            'Authorization': `Basic ${btoa(`${app_name}:${app_pass}`)}`
        }

        let _body = {
            username: request.body.username,
            password: request.body.password
        }

        var options = {
            method: 'POST',
            uri: _uri,
            body: _body,
            headers: _headers,
            json: true
        };

        rp(options)
            .then(parsedBody => {
                response.send(parsedBody);
            })
            .catch(err => {
                response.status(500).send(err)
                //.... Please refer to the following official video: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
            });

    });

});

Initial answer with node-fetch

The fetch() method is asynchronous and returns a Promise. You therefore need to wait this Promise resolves before sending back the response, as follows:

exports.crowdAuthentication = functions.https.onRequest((request, response)=>
{
    cors(request, response, () =>{

        let _uri = "https://my.server.uri/crowd/rest/usermanagement/1/session";
        let _headers = {
            'Content-Type':'application/json',
            'Authorization':`Basic ${btoa(`${app_name}:${app_pass}`)}`
        }


        let _body = {
            username: request.body.username,
            password: request.body.password
        }

        fetch(_uri, {
            method: 'POST',
            headers: _headers,
            body: JSON.stringify(_body),
            credentials: 'include'
        })
        .then(res => {
          res.json()
        })
        .then(json => {
          response.send(json);
        })
        .catch(error => {
            //.... Please refer to the following official video: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
        });
        
    })
})

In addition, note that you need to be on the "Flame" or "Blaze" pricing plan.

As a matter of fact, the free "Spark" plan "allows outbound network requests only to Google-owned services". See https://firebase.google.com/pricing/ (hover your mouse on the question mark situated after the "Cloud Functions" title)

Genix
  • 153
  • 1
  • 9
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • I've tried the method you suggested before and it didn't work. I just tried it again, same result. I'll get a response, but it's not the response from the fetch that I can tell. Also - I am using the Blaze plan. – gin93r Nov 06 '19 at 16:49
  • I'm pretty sure it's just a node version of the fetch api. I did what you said and I'm still getting the same response. – gin93r Nov 06 '19 at 17:06
  • Apparently the `fetch()` method returns an HTTP(S) response which implements the `Body` interface. See https://www.npmjs.com/package/node-fetch#class-response. I've adapted my answer with it, even if you don't succeed for the moment, it seems this is the right way. – Renaud Tarnec Nov 06 '19 at 17:07
  • 1
    You could give a try with https://www.npmjs.com/package/request-promise. It works correctly with Cloud Functions. – Renaud Tarnec Nov 06 '19 at 17:14
  • Ok after updating using the body interface, I now get the response I mentioned above which is `{size:0, timeout:0}`. So at least it's getting something other than the Readable stream - which is what I was getting before. I could look into request-promise. – gin93r Nov 06 '19 at 17:25
  • Looks like using request-promise ended up doing the trick. Once I changed it to using that, everything was fine. So either something is up with node-fetch or I don't know how to use it properly. Probably the latter. – gin93r Nov 06 '19 at 18:19