Chaining Express.js 4's res.status(401) to a redirect
Asked Answered
M

3

18

I'd like to send a response code of 401 if the requesting user is not authenticated, but I'd also like to redirect when the request was an HTML request. I've been finding that Express 4 doesn't allow this:

res.status(401).redirect('/login')

Does anyone know of a way to handle this? This might not be a limitation of Express, since I'm asking to essentially pass two headers, but I don't see why that should be the case. I should be able to pass a "not authenticated" response and redirect the user all in one go.

Meiny answered 7/4, 2015 at 0:19 Comment(3)
I've also noticed express redirects POST to GET, which surprised meFissure
Just a quick note: if the asset can accessed with authentication the status code should be 401. If it is completely forbidden with or without authentication the status code should be 403.Conjure
@brockangelo Ixe's answer is the only way I know how to do this (manually .set(), then .send()). But I think the larger question is whether you truly mean to send a 401 if you're planning on a redirect. Take a look at the SO link in my comment to Jason's answerNasser
C
25

There are some subtle diferences with the methods for sending back a new location header.

With redirect:

app.get('/foobar', function (req, res) {
  res.redirect(401, '/foo');
});
// Responds with
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Location: /foo
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 33
Date: Tue, 07 Apr 2015 01:25:17 GMT
Connection: keep-alive

Unauthorized. Redirecting to /foo

With status and location:

app.get('/foobar', function (req, res) {
  res.status(401).location('/foo').end();
});
// Responds with
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Location: /foo
Date: Tue, 07 Apr 2015 01:30:45 GMT
Connection: keep-alive
Transfer-Encoding: chunked

With the original (incorrect) approach using redirect:

app.get('/foobar', function (req, res) {
  res.status(401).redirect('/foo')();
});
// Responds with 
HTTP/1.1 302 Moved Temporarily
X-Powered-By: Express
Location: /foo
Vary: Accept
Content-Type: text/plain; charset=utf-8
Content-Length: 38
Date: Tue, 07 Apr 2015 01:26:38 GMT
Connection: keep-alive

Moved Temporarily. Redirecting to /foo

So it looks like redirect will abandon any previous status codes and send the default value (unless specified inside the method call). This makes sense due to the use of middleware within Express. If you had some global middleware doing pre-checks on all requests (like checking for the correct accepts headers, etc.) they wouldn't know to redirect a request. However the authentication middleware would and thus it would know to override any previous settings to set them correctly.

UPDATE: As stated in the comments below that even though Express can send a 4XX status code with a Location header does not mean it is an acceptable response for a request client to understand according to the specs. In fact most will ignore the Location header unless the status code is a 3XX value.

Conjure answered 7/4, 2015 at 1:45 Comment(2)
this answers it (partially). redirect will abandon any previous status codes and send the default value - note the docs here "If you don’t specify status, the status code defaults to “302 “Found”". Note though, I think that the redirect function only works on the 300 range. there's a previous SO on this here - #21519594Nasser
@JustinMaat Agreed that the overall use of this is incorrect. 401 should be sent back to the requester to let them know they should request again with the correct authentication method specified via the WWW-Authenticate header. This is typically a token or basic auth. My answer was to show that Express can do it but not that it is correct to do so. Updating my answer to reflect this.Conjure
P
9

You can certainly send a Location: /login header alongside with your 401 page, however, this is ill-advised, and most browsers will not follow it, as per rfc2616.

One way to do overcome this, is to serve <meta http-equiv="refresh" content="0; url=/login"> alongside with your 401 page:

res.set('Content-Type', 'text/html');
res.status(401).send('<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=/login"></head></html>');
Phosphaturia answered 7/4, 2015 at 1:10 Comment(1)
Sorry for resuming this old topic, but you seem to have a closing backtick instead of a single quote in your .send() method. Used your great idea but I couldn't figure out why it would error out :) Thanks!Inquisitor
P
0

I fell on the same issue and decided to use the session to handle this kind of job.

I didn't want to have an intermediate view...

With the below code, I can redirect to the homepage, which will be rendered with 401 unauthorized code.

app.get('patternForbiddenRoute', (req, res, next) => {
       // previousCode
       if (notForbidden === true) {
           return res.render("a_view");
       }

       req.session.httpCode = 401;

       res.redirect('patternHomeRoute');
});

app.get('patternHomeRoute', (req, res, next) => {
       res.render("my_home_view", {}, (error, html) => {
            // ... handle error code ...

            const httpCode = req.session.httpCode;
            if (httpCode !== undefined) {
                delete req.session.httpCode;
                res.status(httpCode);
            }

            res.send(html);
       }));
});
Pash answered 9/7, 2019 at 6:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.