Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature verification failed with just generated tokens #525

Closed
flixman opened this issue Sep 23, 2023 · 1 comment
Closed

Signature verification failed with just generated tokens #525

flixman opened this issue Sep 23, 2023 · 1 comment

Comments

@flixman
Copy link

flixman commented Sep 23, 2023

I have flask-jwt-extended configured with the following settings, with an app running on a docker container behind nginx:

JWT_SECRET_KEY = secrets.token_urlsafe(24)
JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1)
JWT_TOKEN_LOCATION = ['cookies']
JWT_COOKIE_CSRF_PROTECT = False
JWT_CSRF_CHECK_FORM = False
JWT_COOKIE_SECURE = True
JWT_COOKIE_SAMESITE = "Strict"

In my locust test I have the following:

    def on_start(self):
        response = self.client.get('http://127.0.0.1/api/csrf')
        self.headers = {'X-CSRF-Token': response.json()['csrf']}
        self.cookies = dict(response.cookies.iteritems())

        response = self.client.post('http://127.0.0.1/api/auth/login', json=user_credentials.pop(), headers=self.headers, cookies=self.cookies)
        if response.status_code != HTTPStatus.OK:
            raise RuntimeError('Authentication did not succeed')
        self.cookies |= dict(response.cookies.iteritems())

        # get all the lists for the user
        lists = self.client.get('http://127.0.0.1/api/todolists', headers=self.headers, cookies=self.cookies)
        self.cookies |= dict(lists.cookies.iteritems())

        # request their deletion
        for l in lists.json():
            response = self.client.get('http://127.0.0.1/api/csrf', cookies=self.cookies)
            self.headers |= {'X-CSRF-Token': response.json()['csrf']}
            self.cookies |= dict(response.cookies.iteritems())

            response = self.client.delete(f'http://127.0.0.1/api/todolists/{l["id"]}', headers=self.headers, cookies=self.cookies)
            self.cookies |= dict(response.cookies.iteritems())

when running this test against the server, I see the following:

nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "GET /api/csrf HTTP/1.1" 200 103 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "POST /api/auth/login HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "GET /api/todolists HTTP/1.1" 200 172 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "GET /api/csrf HTTP/1.1" 200 103 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "DELETE /api/todolists/2c16ce48-3e7e-4e46-8982-5ae64d418d56 HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "GET /api/csrf HTTP/1.1" 200 103 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "DELETE /api/todolists/90f863aa-4178-4295-8ef8-2b03898fcbb6 HTTP/1.1" 200 0 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "GET /api/csrf HTTP/1.1" 200 103 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:20 +0000] "POST /api/todolists/add HTTP/1.1" 201 85 "-" "python-requests/2.31.0" "-"
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:23 +0000] "GET /api/csrf HTTP/1.1" 200 103 "-" "python-requests/2.31.0" "-"
internal-1  | Signature verification failed
nginx-1         | 172.19.0.1 - - [23/Sep/2023:19:14:23 +0000] "POST /api/todolists/add HTTP/1.1" 302 199 "-" "python-requests/2.31.0" "-"
internal-1  | Signature verification failed

the first GET for todolists as well as the DELETES are OK, but when the next phase goes on (so, getting another csrf and trying to POST a request to /api/todolists/add) then I get Signature verification failed. Few queries later the verification succeeds, and then fails again.

For the verification I am doing the following:

    @app.before_request
    def before_request():
        # the only routes that do not require authentication are the endpoint to login and to retrieve the csrf
        if request.path in [url_for("api.auth.login"), url_for("api.get_csrf")]:
            return None

        try:
            verify_jwt_in_request()
        except (NoAuthorizationError, ExpiredSignatureError, InvalidSignatureError) as _exc:
            # return static files
        return None

and for the refreshing of the token I have the following:

    @app.after_request
    def refresh_expiring_jwts(response):
        if not request.blueprint or not request.blueprint.startswith('api'):
            return response

        try:
            verify_jwt_in_request(refresh=True)
            access_token = create_access_token(identity=get_jwt_identity())
            set_access_cookies(response, access_token)
            return response
        except (RuntimeError, NoAuthorizationError, InvalidSignatureError):
            pass
        finally:
            return response

Am I doing something wrong?

@flixman
Copy link
Author

flixman commented Sep 24, 2023

And the answer is: yes, I was doing something wrong. The problem was that I was running flask threaded, and I was setting the variables SECRET_KEY and JWT_SECRET_KEY to a random value generated at boot time. Should I change that to a random string that is constant, then everything works (because all the threads are getting the same value).

@flixman flixman closed this as completed Sep 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant