Sitecore / jss

Software development kit for JavaScript developers building web applications with Sitecore Experience Platform
https://jss.sitecore.com
Apache License 2.0
261 stars 275 forks source link

sitecore-jss-proxy authentication handling/ onError headers #479

Closed jpspringall closed 2 years ago

jpspringall commented 4 years ago

Is your feature request related to a problem? Please describe.

Currently there does not appear to be a way to cleanly handle 401 responses from the layout service in the jss proxy I'm happy to do the work, but I would like to get agreement with regards to my proposed solution (See additional information)

Describe the solution you'd like

Update the replyWithFunction so that it is possible to create a redirection from the config.onError

Describe alternatives you've considered

It is possible to achieve this by using the content property of the onError function to use a meta tag e.g: <meta http-equiv="refresh" content="0;URL='http://sitecore.re/login'" /> However this does not seem like a very solution to meta

I have also tried to add the functionality to the express index.js, using middleware, however I could not get this to work. e.g

server.use('*', 
scProxy(configDev.serverBundle.renderView, configDev, configDev.serverBundle.parseRouteUrl), 
function (req, res, next) {res.redirect('/login')}
);

I think this is because scProxy completes the request, so you cannot pass through the response

Additional information

Current code:

async function replyWithError(error: Error) {
    console.error(error);

    let errorResponse = {
        statusCode: proxyResponse.statusCode || HttpStatus.INTERNAL_SERVER_ERROR,
        content: proxyResponse.statusMessage || 'Internal Server Error',
    };

    if (config.onError) {
        const onError = await config.onError(error, proxyResponse);
        errorResponse = {
            ...errorResponse,
            ...onError
        };
    }

    completeProxyResponse(Buffer.from(errorResponse.content), errorResponse.statusCode, {});
}

Proposed solution:

async function replyWithError(error: Error) {

    let errorResponse = {
        statusCode: proxyResponse.statusCode || HttpStatus.INTERNAL_SERVER_ERROR,
        content: proxyResponse.statusMessage || 'Internal Server Error',
       headers: {}
    };

    if (config.onError) {
        const onError = await config.onError(error, proxyResponse);
        errorResponse = {
            ...errorResponse,
            ...onError
        };
    } else {
        // If you have a config.onError, it is assumed you will handle logging as you may not always not want to log
        console.error(error);
    }

    // errorResponse.headers is the new property
    completeProxyResponse(Buffer.from(errorResponse.content), errorResponse.statusCode, errorResponse.headers);
}

function completeProxyResponse(content: Buffer | null, statusCode: number, headers ?  : any) {
    if (!headers) {
        headers = proxyResponse.headers;
    }
    ...
}
sc-illiakovalenko commented 4 years ago

@jpspringall Can you provide example, how do you want to achieve redirect using onError handler?

nickwesselman commented 4 years ago

@sc-illiakovalenko So @jpspringall and I talked about this a bit on Slack. The use case is related to authentication and the desire to do a redirect when the Layout Service returns a 401. Not sure if this would require setting the Location header directly, or if there is a redirect method on the response, but we should enable this somehow.

jpspringall commented 4 years ago

Hi @sc-illiakovalenko As @nickwesselman says, the use case I'm trying to cater is whereby you trying to access a protected route when you are unauthenticated.

Currently it just returns a 401 and does not load the SPA.

So I'm happy to come up with another solution other than a redirect but after talking to nick, it appeared to the solution.

As you only have access to the proxyresponse in the onError method, I don't think there is a redirect method available

My current pseudo code would be something like: `onError: (err, response) => { // http 200 = an error in rendering; http 500 = an error on layout service if (response.statusCode !== 500 && response.statusCode !== 200 && response.statusCode !== 401) return null; if (response.statusCode === 401) { var requestUrl = url.parse(response.req.path, true); console.log('onError query', requestUrl.query.item); //You could use the requestUrl.query.item to ensure you aren't redirecting back to the login page, if something has gone really wrong return { statusCode: 302, headers : {location : '/identity/login'}, content: '' };

}
else
{
return {
  statusCode: 500,
  content: fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8')
};

}`