markitosgv/JWTRefreshTokenBundle

HttpCookie not sent to API

geoffroyp opened this issue · 4 comments

Hi guys,

I'm trying to implement this bundle in my symfony project in order to get a way to refresh my JWT token. It seems to work well (minus some other minor problems) if I'm using the token and not the HttpOnly cookie.

However, I want to use the HttpOnly option since it's way more secure this way (cannot be read by JS + I'm not storing JWT and refresh token at the same place)

Here's my config for the bundle:

gesdinet_jwt_refresh_token:
    # ...
    ttl: 62000
    return_expiration: true
    cookie:
        enabled: true
        same_site: lax
        path: /
        domain: mysite.com
        http_only: true
        secure: true
        remove_token_from_body: true

When I log in from my frontend (angular 13), I get my JWT token (from lexik/LexikJWTAuthenticationBundle) , as well as set-cookie header like this:

Request URL: https://api.mysite.com/api/login_check
Request Method: POST
Status Code: 200 OK
Remote Address: 192.168.1.43:443
Referrer Policy: strict-origin-when-cross-origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://front.mysite.com:4200
Access-Control-Expose-Headers: link
Cache-Control: no-cache, private
Connection: Keep-Alive
Content-Type: application/json
Date: Mon, 25 Apr 2022 15:54:30 GMT
Keep-Alive: timeout=5, max=99
Server: Apache/2.4.41 (Ubuntu)
Set-Cookie: refresh_token=a2615d6dd2986680fd79807c61d050424b4e290090a4beaf1f1063c0c8f1807681d86c1c07216630a629667e711897dbae7a6083884d3f19b028aa72f9bbca84; expires=Tue, 26-Apr-2022 09:07:50 GMT; Max-Age=62000; path=/; domain=mysite.com; secure; httponly; samesite=lax
Transfer-Encoding: chunked
X-Debug-Token: e31102
X-Debug-Token-Link: https://api.mysite.com/_profiler/e31102
X-Robots-Tag: noindex

Of course, I cannot check if the cookie has been created or not, since it's HttpOnly

Then in my angular, I have a test button with the following simple code:

  onCallRefreshToken() {
    this.http.post('https://api.mysite.com/api/token/refresh', {site:2}, {withCredentials: true}).subscribe((datas) =>{
      console.log('datas', datas);
    });

  }

and when I run it, I get the following error: [401] Missing JWT Refresh Token

If dump the request inside sympfony, I see that $request->cookies->parameters is empty. And when I look at the sent request from Chrome inspector, I see the following:

Request URL: https://api.mysite.com/api/token/refresh
Request Method: POST
Status Code: 401 Unauthorized
Remote Address: 192.168.1.43:443
Referrer Policy: strict-origin-when-cross-origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://front.mysite.com:4200
Access-Control-Expose-Headers: link
Cache-Control: no-cache, private
Connection: Keep-Alive
Content-Type: application/json
Date: Mon, 25 Apr 2022 16:00:44 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.41 (Ubuntu)
Transfer-Encoding: chunked
Vary: Authorization
X-Debug-Token: 54c0bb
X-Debug-Token-Link: https://api.mysite.com/_profiler/54c0bb
X-Robots-Tag: noindex
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Authorization: Bearer AVeryLongAndValidTokenHere
Connection: keep-alive
Content-Length: 10
Content-Type: application/json
Host: api.mysite.com
Origin: https://front.mysite.com:4200
Referer: https://front.mysite.com:4200/
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36

(JWT token removed on purprose)
So as you can see, th cookie doesn't seem to be passed in the request, which would obviously explain why I don't get it in my symfony API.

So from what I read, the way to "configure" the angular http call in order to pass the HttpOnly cookie was by setting options to "withCredentials" to true, which I did, but as you can see nothing seems to be sent.
Note that I don't seem to have any CORS issue (same domain, secure, allow-credential to true) and HTTPS is working fine

Is there something I am doing wrong? Do you have any idea? Am I not calling the API the right way? Could it be a "wrong error", and the real error is somewhere else?

Thanks!

https://front.mysite.com:4200 is a different origin than https://api.mysite.com/.

You're setting the cookie's domain to mysite.com but your JavaScript runs in the context of front.mysite.com:4200. You could try messing with document.domain = "mysite.com", but it's probably easier to configure your dev environment to use same_site: none. You shouldn't have this issue in production, as you likely won't run the frontend on a non-standard port.

For reference, my configuration is as follows:

  • (React) front-end on http://localhost:3000
  • API on https://localhost/api/
# config/packages/gesdinet_jwt_refresh_token.yaml 
gesdinet_jwt_refresh_token:
    refresh_token_class: App\Entity\RefreshToken
    ttl: 7200
    single_use: true
    cookie:
      enabled: true
      path: /api/token/
# config/packages/dev/gesdinet_jwt_refresh_token.yaml 
gesdinet_jwt_refresh_token:
    cookie:
      same_site: none

Yes I thought about that and tried to put none as well. Did not change anything, the error still remain the same,, [401] Missing JWT Refresh Token

I have tried many configuration otpion, and still end up with the same message. I always left path to /, though, So I tried to change it for /api/token (like you), and /api/token/refresh (like my endpoint for refresh), and still the same problem...

At this point, I'm not even sure if it's a bundle problem or angular problem. But from what I read there was not a million ways to pass the cookie with angular...

You could rule it out by hitting the API directly. For example using postman to authenticate, receive the cookie, and then hit the refresh endpoint.

Also keep in mind that the intial authentication request must also set withCredentials: true in order to store the cookie in the first place.

Also keep in mind that the intial authentication request must also set withCredentials: true in order to store the cookie in the first place.

Wow! thank you so, SO much! I didn't know that and this was indeed the problem here. Now it works perfectly! :)
I know that somehow it's not related to the bundle itself, but maybe it would be a nice addition to the documentation (as a reminder, or something like that)

Anyway: thanks again! You made my day!