Adding a new header to a Request, while preserving the body
Asked Answered
S

1

7

I am setting up a PWA for my company in house usage. What method should I use to attach a bearer token to all of the requests from dom or web-worker.

This method that I am using works as expected when posting a form or json but I would like a cleaner or friendlier method as I don't trust that the text fallback will suffice.

I was looking for a function in Google's workbox.js service worker module to see if I could set up a intercept to always append the Bearer token when a request gets made to my server as that would solve the problem why I end up here in first place. This code is based on the Firebase Service Worker setup. And there was nothing to get and re-add the post data to the new request thus effectively dropping the entire POST body.

This is the code I ended up with.

self.addEventListener( 'fetch', ( event ) => {
    const requestProcessor = async ( idToken ) => {

        let req = event.request;

        // For same origin https requests, append idToken to header.
        if ( self.location.origin == getOriginFromUrl( event.request.url ) &&
            ( self.location.protocol == 'https:' ||
                self.location.hostname == 'localhost' ) &&
            idToken ) {


            let contentType = req.headers.get( "Content-Type" );

            // Clone headers as request headers are immutable.
            const headers = new Headers();
            for ( let entry of req.headers.entries() ) {
                headers.append( entry[ 0 ], entry[ 1 ] );
            }
            // Add ID token to header.
            headers.append( 'Authorization', 'Bearer ' + idToken );
            try {

                let tmpReq = req.clone();
                let body = "";

                if ( req.body ) {
                    body = req.body;

                } else if ( req.method === "POST" ) {
                    // get the post data if its json
                    if ( contentType === "application/json" ) {
                        // get JSON data
                        let json = await tmpReq.json();
                        body = JSON.stringify( json );

                    // Get the post data if its a submitted form
                    } else if ( contentType === "application/x-www-form-urlencoded" ) {
                        // get Form-Data
                        body = await tmpReq.formData();

                    // Get the post data as plain text as a fallback
                    } else {
                        body = await tmpReq.text();
                    }

                    console.log( "Content", content );
                }

                // create a new request with the Bearer Token and post body
                req = new Request( req.url, {
                    method: req.method,
                    headers: headers,
                    mode: 'same-origin',
                    credentials: req.credentials,
                    cache: req.cache,
                    redirect: req.redirect,
                    referrer: req.referrer,
                    body: body,
                    bodyUsed: req.bodyUsed,
                    context: req.context
                } );

            } catch ( e ) {
                // This will fail for CORS requests. We just continue with the
                // fetch caching logic below and do not pass the ID token.
            }

        }
        return fetch( req );
    };
    // Fetch the resource after checking for the ID token.
    // This can also be integrated with existing logic to serve cached files
    // in offline mode.
    event.respondWith( getIdToken().then( requestProcessor, requestProcessor ) );
} );

So in summary my question is... Is the text() fallback that I add when a POST's contentType is neither JSON or FormData going to cover all angles or should I consider a new method of transferring the POST body.

Sasser answered 11/2, 2019 at 16:11 Comment(0)
B
9

If you want to modify a Request, preserving the body but with new or updated headers, the easiest approach is to pass in the original request as the first parameter to the Request constructor, which is of type RequestInfo; it can be either a string URL, or an existing Request object. Any fields that you specify in the second parameter, which is of type RequestInit, will override the fields in the original response.

It gets a little trickier if you want to add in an additional header value while keeping all the headers from the original request, since by default, if you only provide the new values in headers, that will overwrite all of the original headers. So you need to make sure that you set headers to a combination of the original headers plus your new header.

Here's some code that illustrates this:

// This request might be created implicitly by the web app,
// but let's just create it manually as an example:
const originalRequest = new Request('https://example.com', {
  body: 'shared body',
  method: 'POST',
  headers: {
    'x-header': 'my header value'
  },
});

// Start with the original headers as a baseline:
const modifiedHeaders = new Headers(originalRequest.headers);
// Make any changes you want:
modifiedHeaders.set('Authorization', 'Bearer 12345');

// Create a new request based on the original,
// with the modified headers:
const modifiedRequest = new Request(originalRequest, {
  headers: modifiedHeaders,
});

// Everything else in modifiedRequest should be as-is,
// but the headers will be updated.
// Do whatever you want with modifiedRequest at this point.

One thing to note is that with this approach, the body of the original request will end up being used when you construct the modified request. This shouldn't matter in your use case, since only the body of the modified request will end up being read (when you pass it to fetch()). If, for some reason, you do need to read both bodys, then call clone() on the original request first, like

const modifiedRequest = new Request(originalRequest.clone(), {...});
// The two requests now have independent bodies.
Brader answered 20/2, 2019 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.