0

I've uploaded my app to heroku: https://mystoryheroku.herokuapp.com

Please give it a try yourself to see the problem.

Everything seem to work, untill the website is refreshed.

The JWT token is saved in LocalStorage, but is lost from 'header' after page refresh,

which then 'Access Denied' error is seen.

By pasting : https://mystoryheroku.herokuapp.com to the browser, the token is sent to the header again, untill the next refresh..

Verify Middleware:

const jwt = require('jsonwebtoken')

module.exports = function auth (req, res, next){
    const token = req.header('auth-token');
    if(!token) return res.status(401).send('Access Denied')

    try{
        const verified = jwt.verify(token, process.env.JWT_KEY)
        req.user = verified;
        next();
    }catch(err){
        res.status(400).send('Invalid Token');
    }
}

Some routes examples:

//Get All Stories
storyRoute.route('/').get(verify,function(req, res){
    Story.find(function(err, story){
        if(err){
            console.log(err);
        }
        else{
            res.send(story);
        }
    });
});

//Get Stories by ID
storyRoute.route('/story/:id').get(verify,(req,res, next)=>{
    Story.findById(req.params.id, function(err,story){
        if(err){
            res.status(500).json({
                message: "Story not found!"
            })
            console.log( "Story not found!")
        }
        res.status(200).json({story});
    });
});

//User Profile
userRoute.get('/profile',verify, (req, res)=> {
    res.json({
        message: 'Auth successful',
        user:req.user
        });
    });
                
// User Profile (stories)
            userRoute.get('/profile/mystories',verify,(req,res) =>{
            Story.find({UserId: req.user._id})
            .then(stories => {
                res.status(200).json({
                    Stories:stories
                });
            });
        });

Service examples:

  getAllStories(){
    const token = localStorage.getItem('id_token');
    let headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'auth-token': token
    });
    
    const httpOptions = {
      headers: headers
    };
    return this.http.get(`${this.uri}/stories`,httpOptions)
  }

  getStoryById(id){
    const token = localStorage.getItem('id_token');
    let headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'auth-token': token
    });
    
    const httpOptions = {
      headers: headers
    };
    return this.http.get(`${this.uri}/stories/story/${id}`,httpOptions).pipe(map(res => res['story']))
  }
  
    getProfile(){

    const token = localStorage.getItem('id_token');
    let headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'auth-token': token
    });
    
    const httpOptions = {
      headers: headers
    };
    return this.http.get(`${this.uri}/profile`, httpOptions)
  
    };

And so on..

How do I resend the token to the header after each refresh?

Much appreciated!

moses
  • 181
  • 2
  • 9

5 Answers5

1

Your error is not related to a token.

I can see that you hosted your frontend and backend at the same domain(it's not a problem of course).

Here is the problem:

Your profile page path is /profile. Browser will send request to /profile to fetch the page script to be rendered.

But also your api endpoint for getting the user's profile is /profile.

So the request for getting the page script goes to the endpoint for fetching the user's profile(and this request doesn't contain a token), that's why it sends Access denied error.

Solution:

Set /api prefix at all of the endpoints of the backend api to avoid such conflicts.

Please check below post for checking how to set /api prefix in express:

How to add prefix to all node / express routes

critrange
  • 5,652
  • 2
  • 16
  • 47
  • Hi, could you please provide an example of how to set /'api prefix' in any of my routes? Im afraid I didnt quite understand how to do it. – moses Jul 29 '20 at 15:07
0

I created an account on your website. Seems that localStorage works as intented. The problem is when you refresh page there's no JS to inject the header into a request.

There are two possible solutions here:

  1. If this is SPA (and it seems it is) then don't check JWT on subpages serving HTML.
  2. Use cookies. They will be automatically attached to every request.
szatkus
  • 1,292
  • 6
  • 14
0

I have just created user on your website and flip through some links, it seems working as expected;

Check the image below, the 2nd 'profile' page passing the token.

The issue is only when you do hard refresh, then the request is directly going to your server, so in that case it should return the initial html (probably you can mark your api paths using '/api/') and then at front end your app should reload data based on url

enter image description here

Vivek Bani
  • 3,703
  • 1
  • 9
  • 18
0

For future readers: The solution was to add /api to all routes in server.js, and also in the services, but the most imoportant thing was : https://medium.com/wineofbits/angular-2-routing-404-page-not-found-on-refresh-a9a0f5786268

in app-routing.module.ts: RouterModule.forRoot(routes, { useHash: true }) which adds '/#' to the url.

The app just didnt work without it..

moses
  • 181
  • 2
  • 9
-1

Local storage does not persist between page reloads and refreshes. Instead you could use Session Storage.

From docs:

A page session lasts as long as the browser is open, and survives over page reloads and restores.

But also note

Opening a page in a new tab or window creates a new session with the value of the top-level browsing context, which differs from how session cookies work.

ruth
  • 29,535
  • 4
  • 30
  • 57
  • 3
    localStorage is persistent forever before I clear it, i think? – critrange Jul 29 '20 at 14:30
  • @yash: Accd. to spec, the user-agent shouldn't clear the local storage unless citing security reasons or when requested to do so by the user. – ruth Jul 29 '20 at 14:33