1

I read in some security blog that storing a token in localstorage is unsafe so what i want to do is to store the token in the vuex storage, and all the api call will include that token in all following request.

But I am not able to access the token in the first place during successful login, what I want to do is to store the token in the vuex storage for the first time, I thought of sending the token to the body of the response but it will be a vulnerable method so I am sending it in the header["authorization"].

below are my user.js and login.vue file respectively.

router.post('/login', function (req, res, next) {
    const {
        UserName,
        Password
    } = req.body;

    if (UserName.length == 0 || Password.length == 0) {
        res.status(400).json({
            message: 'Email or Password is empty',
        });
    } else {
        login_pool.query(
            'SELECT * FROM authentication WHERE user_name = ($1) and password = crypt(($2), password)',
            [UserName, Password],
            (err, results) => {
                if (err) {
                    throw err;
                } else if (results.rows.length == 1) {
                    // On Successful Login
                    const token = jwt.sign(
                        {
                            user_name: results.rows[0].user_name,
                            full_name: results.rows[0].full_name,
                            phone_number: results.rows[0].phone_number,
                        },
                        btoa(process.env.TOKEN_SECRET), // converting token_secret to base 64
                        { expiresIn: '1800s' },
                        { algorithm: 'HS256' },
                        (err) => {
                            if (err) {
                                res.status(400).json({
                                    message: 'Not able to create a token',
                                });
                                console.log(err);
                            }
                        }
                    );
                    res.header('Authorization', `Bearer ${token}`);
                    res.status(201).json({
                        message: results.rows[0].full_name + 'logged in.',
                    });
                    console.log(results.rows[0].full_name + 'Just Logged In. ');
                } else {
                    login_pool.query(
                        'SELECT * FROM authentication WHERE user_name = ($1)',
                        [UserName],
                        (errUser, resultUser) => {
                            if (resultUser.rows.length != 1) {
                                res.status(400).json({
                                    message: 'User with this email does not exist',
                                });
                            } else {
                                res.status(400).json({
                                    message: 'Password is not correct',
                                });
                            }
                        }
                    );
                }
            }
        );
    }
});
LoginSubmit() {
    this.axios
        .post(
            "http://127.0.0.1:3000/users/login",
            {
                UserName: this.UserName,
                Password: this.Password,
            },
            {
                headers: {
                    "Content-Type": "application/json;charset=UTF-8",
                    "Access-Control-Allow-Origin": "*",
                    Accept: "application/vnd.api+json",
                },
            }
        )
        .then(
            (res) => {
                // successful login
                console.log(res.headers); // authentication header not present here
                this.Error = "";
                console.log(this.axios.defaults.headers.common); // authentication header not present here
            },
            (err) => {
                console.log(err.response.data.message);
                this.Error = err.response.data.message.replace(/"/g, "");
            }
        );
},
Flame
  • 5,185
  • 3
  • 29
  • 45
degod
  • 23
  • 1
  • 6
  • 1
    *”sending the token to the body of the response but it will be a vulnerable method”* — Uhm, how so? – deceze May 16 '21 at 08:05
  • I think it can be Intercepted. – degod May 16 '21 at 09:12
  • 1
    Everything can be intercepted. I'd suggest to estimate possible risks and study the reasoning behind statements regarding the unsafety of local storage first. Otherwise this approach to security is pretty much a tinfoil hat. – Estus Flask May 16 '21 at 09:15
  • Possible duplicate of https://stackoverflow.com/questions/44133536/is-it-safe-to-store-a-jwt-in-localstorage-with-reactjs – Estus Flask May 16 '21 at 09:15
  • Do you know any way to access the Authorization Header in the Font ENd – degod May 16 '21 at 09:23

3 Answers3

2

I've never seen it done like this. A JWT can just be sent as part of the body of the response to some POST /auth endpoint. You then save that token using some sort of storage.

Whether that is localStorage or a cookie is debatable; I personally just use localStorage so that the API can be as stateless as possible. However when using a cookie, you can set some sort of expiry to your cookie which makes sure its deleted after the expiry date.

Vuex store is in essence a global state object that loses all its contents once you refresh your browser window. That is unless you couple it with some sort of localStorage/sessionStorage that loads/saves to your Vuex store.

So I suggest you remove the Access-Control-Expose-Headers: Authorization header and just send the JWT token as a POST body response during authentication. The safety of such auth request depends on whether you use http or https. You obviously always want to use https because everything that is not can easily be read plain-text by some malicious owner of a (wifi-)network.

Flame
  • 5,185
  • 3
  • 29
  • 45
0
res.header({
   Authorization: "Bearer" + token,
   "Access-Control-Expose-Headers": "Authorization",
});

Using Access-Control-Expose-Headers solved my problem now I can access the Authorization Header at the frontend part by using res.headers["authorization"]

degod
  • 23
  • 1
  • 6
  • `Authorization` is a request header, it shouldn't be used for what you're trying to achieve. If you really want to return the token in a header, use your own header (you will still have to expose it). That said, it looks like in your case you could just use sessions with cookies. – Michal Trojanowski May 17 '21 at 08:39
0

Our vue app is storing the token in the sessionStorage and running into an strange issue because of it.

The normal behavior we see and expect is each time a user goes to our app on a new tab, they have to authenticate. This works fine in all browser, all the time. There is one path only on Windows Chrome where the user does not have to authenticate.

  1. Make sure there is at least one other tab or instance of Chrome open, whether or not there is a website loaded is not relevant.
  2. Log into our site
  3. Close the tab.
  4. Open either a new tab or new instance of Chrome.
  5. Go to the browser History and restore the tab that was just closed.

In this scenario the sessionStorage is restored and the user is logged in. I have been able to duplicate this in a simple HTML Page.

Is there some other way to save the JWT token other then the sessionStorage?

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Session Persists Test</title>
    <style>
        div { margin: .75em; }
        input { margin-left: 2em;}
    </style>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script>
        $(document).ready(function () {
            const formatDateTime = function (date_ob) {
                let date = ("0" + date_ob.getDate()).slice(-2);
                let month = ("0" + (date_ob.getMonth() + 1)).slice(-2);
                let year = date_ob.getFullYear();
                let hours = ("0" + date_ob.getHours()).slice(-2);
                let minutes = ("0" + date_ob.getMinutes()).slice(-2);
                let seconds = ("0" + date_ob.getSeconds()).slice(-2);
                return `${month}-${date}-${year} ${hours}:${minutes}:${seconds}`;
            };

            const updateTimes = () => {
                let now_token = formatDateTime(new Date());
                let session_now_token = sessionStorage.getItem('now_token');

                if (session_now_token == null) {
                    session_now_token = now_token;
                    sessionStorage.setItem('now_token', session_now_token);
                }
                $("#sessionTime").text(`Session Time: ${session_now_token}`);
                $("#lastCall").text(`Current Time: ${now_token}`);
            }

            $("#updateSession").click(function () {
                sessionStorage.clear();
                location.reload();
            });

            $('#updateTime').click(updateTimes);

            updateTimes();
        });
    </script>
</head>
<body>
<h1>Session Persists Test</h1>
<div><span id="sessionTime">Session Time: (please enable Javascript not running>)</span><input id="updateSession" type="button" value="Update Session Time"></div>
<div><span id="lastCall">Current Time: (please enable Javascript not running>)</span><input id="updateTime" type="button" value="Update Current Time (or press F5)"></div>
<H3>Testing of how long a browser session persists.</H3>
<div>
    <p>Google Chrome Version 97.0.4692.99 on Windows is not clearing the session when restoring a tab from the history. This site
        has been created to test and demo that issue.</p>
    <ul>
        <li>When this page is first loaded (or the Update Session time button is pressed) the session variable with the
            time is updated from the server.
        </li>
        <li>Each time the browser is refreshed (F5) the current time is updated.</li>
        <li>When a new tab opened to this site, the there will be a new session storage and a new session time.</li>
    </ul>
    <h3>The Problem:</h3>
    <p>A new session is NOT create when restoring a tab from history.  To reproduce, using Google Chrome on Windows do the following:</p>
    <ol>
        <li>Make sure there is at least one other tab or instance of Chrome open, whether or not there is a website
            loaded is not relevant.
        </li>
        <li>Load this site and record the session time.</li>
        <li>Close the tab that is displaying this site.</li>
        <li>Open either a new tab or new instance of Chrome.</li>
        <li>Go to the browser History and restore the tab that was just closed.</li>
    </ol>
    <p>The session time is the same time recorded in step 2. As long as there is at least one instance of Chrome
        running, when restoring from history, the session will also be restored.</p>
    <p>This is not the way eitehr Edge or Firefox is currently working. Both created a new session. Which browser is
        working correctly?</p>
</div>
</body>
</html>
Sam Carleton
  • 1,239
  • 6
  • 22
  • 37