superset icon indicating copy to clipboard operation
superset copied to clipboard

Cannot autheticate on API. Get the tokens (JWT and csrf) but all responses are "401"

Open pedrohdemedeiros opened this issue 3 years ago • 29 comments

I cannot use the API succefully for any request (except to get the secutiry tokens), always getting " Response 401" for any request.

How to reproduce the bug

On the docker container where the superset is running:

>>>
import requests

session = requests.session()

jwt_token = session.post(
    url='http://localhost:8088/api/v1/security/login',
    json={
    "username": "admin",
    "password": "admin",
    "refresh": False,
    "provider": "db"
    }
).json()["access_token"]

csrf_token = session.get(
    url='http://localhost:8088/api/v1/security/csrf_token/',
    headers={
        'Authorization': f'Bearer {jwt_token}',
    }
).json()["result"]

headers = {
    'accept': 'application/json',
    'Authorization': f'Bearer {jwt_token}',
    'X-CSRFToken': csrf_token,
}

#trying to use the "current user" request as a test
response = requests.get('http://localhost:8088/api/v1/me', headers=headers)

session.close()`

response

Expected results

{ "result": { "email": "[email protected]", "first_name": "Superset", "id": 1, "is_active": true, "is_anonymous": false, "last_name": "Admin", "username": "admin" } }

Actual results

>>> response
<Response [401]>

Screenshots

Environment

(please complete the following information):

  • superset version: v1.0.0
  • python version: Python 3.8.12

Checklist

  • [ x] I have checked the superset logs for python stacktraces and included it here as text if there are any.
  • [x ] I have reproduced the issue with at least the latest released version of superset.
  • [ x] I have checked the issue tracker for the same issue and I haven't found one similar.

pedrohdemedeiros avatar Apr 05 '22 00:04 pedrohdemedeiros

did you try with 1.4.2?

MM-Lehmann avatar Apr 12 '22 09:04 MM-Lehmann

Instead of

#trying to use the "current user" request as a test
response = requests.get('http://localhost:8088/api/v1/me', headers=headers)

you should try:

session.headers.update(headers)

response = session.get('http://localhost:8088/api/v1/me') 

MarcinZegar avatar Apr 18 '22 16:04 MarcinZegar

First of all, thank you @MM-Lehmann and @MarcinZegar .

I've tried with 1.4.0 and most of my API problems were solved but one: I still cannot upload a CSV using it. I've tried a lot of different things, learned a lot about requests using python, curl and so on but it seens that it simply doesn't work.

Not a big problem since I've decided to update my database by MySQL directly (witch I had to learn also).

I will keep an eye on the next versions just to test if, someday, it will be possible

pedrohdemedeiros avatar Apr 19 '22 17:04 pedrohdemedeiros

Closing this since most issues were resolved. It's also been a while since this thread was active! Hopefully things are working better in 1.5.x or 2.0.x but if there are still bugs being found, let us know and we can re-open this, no problem!

rusackas avatar Jan 06 '23 23:01 rusackas

Instead of

#trying to use the "current user" request as a test
response = requests.get('http://localhost:8088/api/v1/me', headers=headers)

you should try:

session.headers.update(headers)

response = session.get('http://localhost:8088/api/v1/me') 

This didn't work now. The code is accessing g.user from flask_login. The flask_login doesn't load the user when the request is sent from the api. I think there should be a better solution for the new version of code.

raghulprashath avatar Jan 20 '23 07:01 raghulprashath

I think i'm experiencing the same issue with superset 2.0.1.

this is the script that I run to get jwt token, csrf token, and finally get my user's info:

const base_url = "https://<server_addr>/api/v1/";

async function get_csrf_token(access_token) {
  const res = await fetch(base_url + "security/csrf_token", {
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + access_token,
    },
    method: "GET",
  });

  const complete_res = await res.json();
  return complete_res.result;
}

async function login() {
  const res = await fetch(base_url + "security/login", {
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      username: "user",
      password: "pass",
      provider: "db",
      refresh: true,
    }),
    method: "POST",
  });

  const complete_res = await res.json();
  return complete_res.access_token;
}

async function get_me(access_token, csrf_token) {
  const res = await fetch(base_url + "me", {
    credentials: "same-origin",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + access_token,
      "X-CSRFToken": csrf_token,
    },
    method: "GET",
  });

  return await res.json();
}

(async () => {
  const access_token = await login();
  console.log("res is ", access_token);

  const csrf_token = await get_csrf_token(access_token);
  console.log("res is ", csrf_token);

  const me_res = await get_me(access_token, csrf_token);
  console.log("me is ", me_res);
})();

when I run the script the result is:

$ node ./superset_api_test.js
res is  <jwt token>
res is  <csrf token>
me is  { message: 'Not authorized' }

and I think @raghulprashath is right. lines 35-63 of get_me method of superset.views.users.api.CurrentUserRestApi are like:

    @expose("/", methods=["GET"])
    @safe
    def get_me(self) -> Response:
        """Get the user object corresponding to the agent making the request
        ---
        get:
          description: >-
            Returns the user object corresponding to the agent making the request,
            or returns a 401 error if the user is unauthenticated.
          responses:
            200:
              description: The current user
              content:
                application/json:
                  schema:
                    type: object
                    properties:
                      result:
                        $ref: '#/components/schemas/UserResponseSchema'
            401:
              $ref: '#/components/responses/401'
        """
        try:
            if g.user is None or g.user.is_anonymous:
                return self.response_401()
        except NoAuthorizationError:
            return self.response_401()

        return self.response(200, result=user_response_schema.dump(g.user))

i can confirm that the same workflow works totally fine for version 1.2.0, albeit on a view other than /me

versions:

superset: 2.0.1
python: 3.8.16

sazary avatar Feb 02 '23 02:02 sazary

Still facing this issue.

versions

superset: v2021.41.0-4182-gdf91664 ( `git clone` -> `docker compose -f docker-compose-non-dev.yml up -d` )
Python 3.8.13

have tried

  • node ./superset_api_test.js as @sazary brought up. Result is { message: 'Not authorized' }
  • hit /api/v1/me with Bearer token obtained from hitting /api/v1/security/login with admin, no luck.

The superset service is launched in docker, with superset_config_docker.py as

SESSION_COOKIE_SAMESITE = None
ENABLE_PROXY_FIX = True
PUBLIC_ROLE_LIKE_GAMMA = True
FEATURE_FLAGS = {
  "EMBEDDED SUPERSET" : True
}
CORS_OPTIONS = {
  'supports_credentials': True,
  'allow_headers': ['*'],
  'resources' : ['*'],
  'origins': ['http://localhost:8088', 'http://localhost:8888']
}
CSRF_ENABLED = False
WTF_CSRF_ENABLED = False

Wonder if anyone could point me to possible solutions for this?

nicolaskodak avatar Mar 21 '23 04:03 nicolaskodak

Actually I was able to solve this problem.

import requests
# from bs4 import BeautifulSoup

username = "admin"
password = "admin"
session = requests.session()

login_form = session.post('http://localhost:8088/login')
# soup = BeautifulSoup(login_form.text, 'html.parser')
# csrf_token = soup.find('input',{'id':'csrf_token'})['value']
data = {
  'username': username,
  'password': password,
  # 'csrf_token': csrf_token
}
response = session.post('http://localhost:8088/login', data=data)
response = session.get('http://localhost:8088/api/v1/me')

This is a work around to make this api work. Comment out the lines in above code, if you want CSRF token i.e iff you enabled the flag in config

raghulprashath avatar Mar 22 '23 19:03 raghulprashath

I can confirm that this issue still exists with the latest release. Expected behavior, what goes wrong and steps to reproduce are the same as my first report.

versions:

apache-superset==2.1.0
Python 3.8.16

@rusackas Could you please re open this issue?

sazary avatar Apr 11 '23 14:04 sazary

After a couple of hours doing debugging it seems that I could resolve the problem with a temporary solution. In my case, I was requesting POST:api/v1/chart/data using the JWT authentication method. For this endpoint we have permission_str=can_read and the class_permission_name=Chart. somewhere in flask_appbuilder/security/decorators.py:84 we have:

            if current_app.appbuilder.sm.is_item_public(
                permission_str, class_permission_name
            ):

In my running superset instance, reading a chart was a public action but only some of the charts were actually public. So the decorator prevents execution of verify_jwt_in_request() before processing the request, because of can read on Chart is in permissions of the Public role.

Temporary Solution

On the superset UI I edited the public role and removed can read on Chart from its permissions.

partizaans avatar May 29 '23 09:05 partizaans

I think the Referer header is missing could be the culprit?

Wrote a guide on it, hopefully, it helps someone 😄 Choose your preferred media 😝

https://huamichaelchen.substack.com/p/end-to-end-example-of-setting-up

https://medium.com/@huamichaelchen/end-to-end-example-of-setting-up-superset-embedded-dashboard-f72fc985559

huamichaelchen avatar Aug 31 '23 17:08 huamichaelchen

Hi Guys,

We are facing similar issue while authenticating superset API request (Superset version - 2.0.1). I have tried all the solution listed here but unfortunately nothing worked for me. Do we have any update on the issue?

Thanks!

Krishnamohan1604 avatar Sep 14 '23 08:09 Krishnamohan1604

This issue seems to be present in version 3.0.0 as well. Running with Docker Compose. Requests to /api/v1/me/roles is always returning 401. This seems to break the embed dashboard feature. It would be great if anyone on the Superset side could look into this.

larshelge avatar Oct 24 '23 14:10 larshelge

same issue on 3.0.2

lucidprojects avatar Dec 27 '23 14:12 lucidprojects

Temporary Solution

On the superset UI I edited the role of public and removed can read on Chart from its permissions.

We've just had this issue when upgrading 3.0.1 to 3.1.0rc3 and this solved it. Thanks @partizaans

hhhonzik avatar Dec 29 '23 11:12 hhhonzik

3.0.2 Roles was 401, but after turning Talisman on - all seems work. Except other then security API endpoints :(

m1zzo avatar Jan 18 '24 12:01 m1zzo

Closing this as it seems the issue was resolved in recent versions of Superset. Please reopen if that's not the case.

michael-s-molina avatar Feb 23 '24 21:02 michael-s-molina

@michael-s-molina Hi, I am using the lastest version of Superset, but still facing this issue, can we re-open it? All settings are leave as default except FAB_ADD_SECURITY_API = True I am able to login, get csrf_token and call every other api except /api/v1/me/, it return 401

EuphoriaCelestial avatar Mar 21 '24 09:03 EuphoriaCelestial

Closing this as it seems the issue was resolved in recent versions of Superset. Please reopen if that's not the case.

Faced the same problem after following the upgrade instruction to the latest stable version (4.0.2) and happened again. @michael-s-molina

partizaans avatar Aug 05 '24 14:08 partizaans

Closing this as it seems the issue was resolved in recent versions of Superset. Please reopen if that's not the case.

Faced the same problem after following the upgrade instruction to the latest stable version (4.0.2) and happened again. @michael-s-molina

Same thing here, did you manage to solve it?

lsfc02 avatar Aug 21 '24 13:08 lsfc02

Closing this as it seems the issue was resolved in recent versions of Superset. Please reopen if that's not the case.

Faced the same problem after following the upgrade instruction to the latest stable version (4.0.2) and happened again. @michael-s-molina

Same thing here, did you manage to solve it?

Unfortunately no. Had to do some manual actions after upgrading was completed, described as the Temporary Solution here: https://github.com/apache/superset/issues/19525#issuecomment-1566791040

partizaans avatar Aug 24 '24 09:08 partizaans

Same here. Getting a 404 when calling /api/v1/dashboard/14

1. Possible Explanation:

  • When the API is called with a session cookie, the user is identified through Flask-Login using current_user, and the @with_dashboard decorator can retrieve the dashboard based on this user.
  • With a JWT, the decorator might not know how to access the same user context. If @with_dashboard relies on current_user, and current_user is not properly defined when using the JWT, the lookup may fail, resulting in a 404.

2. User Context (current_user)

  • In the case of session cookies, Flask-Login properly sets up current_user to identify the user. However, when using a JWT, verify_jwt_in_request() verifies the token but does not automatically update current_user used by Flask to reference the authenticated user.
  • If the subsequent code (such as the @with_dashboard decorator) uses current_user to access the dashboard, and current_user is not properly set with the JWT, the system may fail to find the dashboard, returning a 404.

3. Differences in Initialization of Global Variables (g or current_user)

  • Flask often uses global variables such as g.user or current_user to store the current user. When the call is made via JWT, these variables may not be correctly initialized, resulting in an inability to locate the resources.
  • Potential Solution: Make sure that current_user is correctly set after JWT authentication. You can do this by extending the JWT validation process to explicitly set current_user.

julienbrodier avatar Oct 07 '24 13:10 julienbrodier

Why is def get_me(self) -> Response: not decorated with @protect() ?

I'll confess to not knowing the code that well, but is there another way if g.user is None or g.user.is_anonymous: can return false without @protect() ?

mat-codehaus avatar Nov 04 '24 09:11 mat-codehaus

Is anyone still facing this or seeing it happen in Superset 4.1.2/5.0.0? This has been silent for upward of 6 months, and is a pretty ancient bug report, so we could probably use some context updates here if it's still relevant.

rusackas avatar Apr 21 '25 22:04 rusackas

I'm seeing this in 4.1.2.

Using @sazary's code above, the output is:

me is { message: 'Not authorized' }

I know the /api/v1/security/login enpoint works because providing an incorrect password fails to authenticate. Using JWT tokens as Bearer auth seems as if it is no supported.

richard-ive-m4 avatar May 14 '25 12:05 richard-ive-m4