#JWT validation question

51 messages · Page 1 of 1 (latest)

astral kettle
#

I have a frontend connected to a NestJs backend with JWT authentication implemented. Users can sign up and sign in. Now I am wondering how I can prevent refreshing the browser from requiring another log in.

I researched a bit and wanted to ask here to see if my thinking is correct. I am thinking to store the access token in the browser cookies. If a refresh happens, I will send a request with the cookie access token to a new route. Since I have AuthGaurds setup, if the request is successful I will return the userid from the access token and the user will not be logged out. Then I can use this userid and access token for further requests. (I use the userid to filter on the DB and hence need a way to get the userid when the user refreshes the screen)

Is this an appropriate method?

patent crown
#

Yo can save it either in cookies (then it will be automatically sent in the cookies header) or LocalStorage (from which you have to retrieve it and add it to the request headers)

astral kettle
#

Is there documentation in NestJs I can reference to get the cookies in Nest?

gentle oriole
#

What do you know about cookies with servers in general? Hsve you used them with node before?

astral kettle
#

No, I haven't used them before

gentle oriole
#

Start with MDN's and OWASP's docs on Cookies then

astral kettle
#

Ok thanks I'll research these now

gentle oriole
#

Nest has docs on using cookies but you real;y should have an understanding of them

astral kettle
#

Yes I agree, I've been building this project piece by piece learning as I go and felt I was missing a lot on this part especially and want to ensure I follow best practices as much as I can

#

Am I heading in the right direction for my problem? In the scenario that users refresh I don't want them to login again until the token is invalidated by an expiration set on the token

#

Would cookies be one way to resolve this? I think I'll stumble on the answer as I research MDN/OWASP docs, but curious

#

Please disregard, it's almost the first sentence on MDN's docs

astral kettle
#

Ok I have successfully set the cookie and tested through postman. Now when I test through my frontend I do not see the cookie set (I inspected the browser and viewed the cookies and nothing is there). Any reason this is setting correctly on postman but not the frontend?

gentle oriole
#

Lots of possible reasons. CORS, the domain of the front end and back end, cross-site settings, if you're in Chroe you can view the response headers and it should have a ⚠️ icon that you can hover over and see what the issue is

astral kettle
#

I do have cors enabled as frontend is on 3000 and backend is on 4000 and I'm setting the cookie like this:

response.cookie('access_token', token.access_token, {domain: 'localhost:3000', path: '/'});

I saw this could be because 'SameSite' is not specified although it should default to lax I thought

#

I'll check chrome now

gentle oriole
#

If I recall correctly, you don't need the port for the domain

astral kettle
#

Yeah I meant to remove that, and in chrome I see in the response cookies the cookie is there, but it's not stored on the Cookies > localhost section

gentle oriole
#

Looks at the response headers, find the Set-Cookie header, that'll have the ⚠️ icon I was talking about

astral kettle
#

The response only shows the {} object I'm returning.. is that the issue? This is my signIn function

   async signIn(@Body() user: Users, @Res({ passthrough: true }) response: Response) {
        try {
            const token = await this.authService.signIn(user.username, user.password)
            response.cookie('access_token', token.access_token, {domain: 'localhost', path: '/'});
            return { message: 'Sign in successful', user: user, token: token.access_token };
        } catch (err) {
            return { message: 'Sign in failed', error: err.message };
        }
    }
gentle oriole
#

In the network tab, you should be able to look at "Response Headers". That's where you'll find the info I'm asking about

astral kettle
#

Ohh I see:

#

I don't see any warning

gentle oriole
#

Nothing on the Set-Cookie header specifically?

astral kettle
#

No nothing there

gentle oriole
#

Interesting, then the cookie should be set. Can you show the full hedaer?

astral kettle
#

the full response header:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Set-Cookie: access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsInVzZXJuYW1lIjoic2xvdWdoeSIsImlhdCI6MTY5OTQxMjQzOCwiZXhwIjoxNjk5NDEyNTU4fQ.R4lFMXDSkM1xYnsEDTQZY6gd0wEZ_re0JLbGiG_xh78; Domain=localhost; Path=/
Content-Type: application/json; charset=utf-8
Content-Length: 264
ETag: W/"108-fWKa9YKQ3YyZ94oSgOSKrs8Xgzg"
Date: Wed, 08 Nov 2023 03:00:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5

gentle oriole
#

What's the HTTP client on the front end?

astral kettle
#

Sorry what do you mean, it's a react front end

gentle oriole
#

Do you use fetch, axios, gaxios, got, XHMTLHttpRequest, phin, something else?

astral kettle
#

fetch

gentle oriole
#

Do you have credentials: "include" set in the request options?

astral kettle
#
try {
        console.log("POST /api/v1/auth/login");

        const request = {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        }
        const res = await fetch('http://localhost:4000/api/v1/auth/login', request);
        if (!res.ok) {
            throw new Error(`Network response error. Response: ${res}`);
        }
        return res.json();
    } catch (err) {
        console.error("Error creating expense.", err);
        throw err;
    }

This is my request, no I don't have that

gentle oriole
#

Okay, add that

astral kettle
#

okay

gentle oriole
#

That tells fetch (and the browser) to handle cookies

astral kettle
#

okay got it, now when I try to login I see a new error stating: Access to fetch at 'http://localhost:4000/api/v1/auth/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'

gentle oriole
#

Yep, you have to specify in your CORS config what frontends are valid and you have to set credentials: true

astral kettle
#

awesome okay

#

ok you're the best, it's coming together now, but for some reason I'm seeing unauthorized errors leading me to believe my AuthGuard is throwing this although the cookie should be valid (it's 2 min for now which is low, but I'm making these requests quickly)

#

I have logging enabled and even see the database is queried when I make a request, but not sure why I'm seeing unauthorized

#

oh wait

#

🥳 it's working!! Thanks for your help, now I'm guessing when I refresh the page I need my frontend to try a request with the cookie and if successful don't display the login

astral kettle
#

A general question before closing this thread, I make API calls where userid = loggedinuser to retrieve their data. The userid is captured when they log in, is this another candidate for a cookie value, or should this be something else?

patent crown
#

Can a logged in user send a request using a different userid and get that user's data? If yes, that's a problem.

astral kettle
#

Right now the app will authenticate with a username and password, populate the access token in the cookies, then pull data using the username as a query id and validating the token through an auth guard

#

So yes if someone updates the GET request with a different username and a valid token it will pull that users data which I agree is bad. What is the standard to do this securely?

patent crown
astral kettle
#

Yes ok I updated my access token to store the username, then in my controllers I'm accessing the request Req() request: Request and using the username for queries

#

Let me know if this is a fine approach security wise, otherwise, thank you both for the help

patent crown
#

I think this is good enough as far as basic security goes. You want to make sure users can't ever access other user's stuff even if they tamper with the request data.