'jwt - Where to store token in Vue.js?

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, "");
            }
        );
},


Solution 1:[1]

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"]

Solution 2:[2]

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>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 degod
Solution 2 Sam Carleton