0

The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:

  1. How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
  2. How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?

Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?

I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.

By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.

By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.

exports.customer_profile = function (req, res) {
  var token = req.headers.token;
  var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
  var decoded = jwt.verify(token, public_key);
  var sql = 'SELECT * FROM  customer WHERE username = "' + decoded.sub + '"';
  util.conn.query(sql, function (err, rows) {
    if (!err) {
      for (var i = 0; i < rows.length; i++) {
        res.render('customer_profile', {customer_profile: rows[i]});
        break;
      }
    }
  });
};
Community
  • 1
  • 1
  • Got some kind of solution by combining [http://stackoverflow.com/questions/15201724/how-can-i-pass-variable-to-ejs-compile] and [http://stackoverflow.com/questions/483745/replace-html-page-with-contents-retrieved-via-ajax] – howToDeleteMyAccount Apr 17 '17 at 16:29
  • trying another way to mix token and cookie, since the cookie header field will be sent automatically by the browser after set-cookie ... – howToDeleteMyAccount Apr 21 '17 at 10:35

1 Answers1

0

I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.

So far what I was able to figure out is the following:

  1. Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
    // Get current user's ID Token
    firebase.auth().currentUser.getIdToken()
    .then(token => {
        // Make a fetch request to 'path'
        return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
            method: 'GET',
            headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
        });
    })
    .then(response => { 
        // As noted below, this part I haven't solved yet. 
        // TODO: Open response as new webpage instead of displaying as data in existing one
        return response.text();
    })
    .then(text => {
        console.log(text);
    })
    .catch(error => {
        console.log(error);
    });
}
  1. Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup). 
// getLocale - language negotiation. 
// getContext - auth token verification if it is available and appends it to Request object for convenience

app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);

// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
        const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
        if(!idToken) {
            return next(); // Passes to next middleware if no token, terminates further execution
        }
        admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
        .then(token => {
            req.decoded_token = token; // Append token to Request object for convenience in further middleware
            return next(); // Pass on further
        })
        .catch(error => {
            console.log('Request not authorized', 401, error)
            return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
        });
    }
  1. Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user

router.get('/console', middleware.getTranslation('console'), (req, res) => {
    if(req.decoded_token) { // if token was verified successfully and is appended to req
        res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
    } else {
        res.status(401).send('Not authorized'); // else send 401 to user
    }
});

As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access

What I have not solved yet:

As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.

Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet

  • Another way i just come up with is wrapping an element in a
    tag and resolving it as a normal form POST request. In that case, you still don’t add data to the header, but you can at least pass it in the body, which is already better than a query parameter, but still seems like a work-around rather than a legitimate solution. The response from your server is a new loaded web page as desired. Good compromise?
    – Maxim Yudayev Jul 19 '19 at 23:56