FastAPI: 400 Bad Request

I have setup an auth0 single web page application and an API which I use in Angular and FastAPI respectively.

After I login via the Angular web app, a GET request is sent to FastAPI which fails mentioning 400 Bad request. On the browser console, the response error is “invalid issuer” but the api audience is the one on my auth0 dashboard.

When I use Postman to make the request, the message mentions “invalid payload padding”.

This is the code used to verify the token:

token_auth_schema = HTTPBearer()


def token_auth(token: str = Depends(token_auth_schema)):
    result = VerifyToken(token.credentials).verify()
    if result.get("status"):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=result)
    return result

class VerifyToken:
    """Does all the token verification using PyJWT"""

    def __init__(self, token, permissions=None, scopes=None):
        self.token = token
        self.permissions = permissions
        self.scopes = scopes
        self.config = set_up()
        # This gets the JWKS from a given URL and does processing so you can use any of
        # the keys available
        jwks_url = f'https://{self.config["DOMAIN"]}/.well-known/jwks.json'
        self.jwks_client = jwt.PyJWKClient(jwks_url)

    def verify(self):
        # This gets the 'kid' from the passed token
        try:
            self.signing_key = self.jwks_client.get_signing_key_from_jwt(self.token).key
        except jwt.exceptions.PyJWKClientError as error:
            return {"status": "error", "msg": f"PyJWKClientError: {str(error)}"}
        except jwt.exceptions.DecodeError as error:
            return {"status": "error", "msg": f"DecodeError: {str(error)}"}

        try:
            payload = jwt.decode(
                self.token,
                self.signing_key,
                algorithms=self.config["ALGORITHMS"],
                audience=self.config["API_AUDIENCE"],
                issuer="https://"+self.config["DOMAIN"],
            )
        except Exception as e:
            return {"status": "error", "message": str(e)}

        if self.scopes:
            result = self._check_claims(payload, "scope", str, self.scopes.split(" "))
            if result.get("error"):
                return result

        if self.permissions:
            result = self._check_claims(payload, "permissions", list, self.permissions)
            if result.get("error"):
                return result

        return payload

    def _check_claims(self, payload, claim_name, claim_type, expected_value):
        instance_check = isinstance(payload[claim_name], claim_type)
        result = {"status": "success", "status_code": 200}

        payload_claim = payload[claim_name]

        if claim_name not in payload or not instance_check:
            result["status"] = "error"
            result["status_code"] = 400

            result["code"] = f"missing_{claim_name}"
            result["msg"] = f"No claim '{claim_name}' found in token."
            return result

        if claim_name == "scope":
            payload_claim = payload[claim_name].split(" ")

        for value in expected_value:
            if value not in payload_claim:
                result["status"] = "error"
                result["status_code"] = 403

                result["code"] = f"insufficient_{claim_name}"
                result["msg"] = (
                    f"Insufficient {claim_name} ({value}). You don't have "
                    "access to this resource"
                )
                return result
        return result

Hey there @bakmo welcome to the community!

Issuer is a different claim than audience - Can you confirm the issuer in a sample user access token is exactly the same as the issuer in jwt.decode? Assuming the token has an audience and isn’t opaque you should be able to decode it at jwt.io. Feel free to share the decoded token here (any sensitive info redacted) if you’d like me to take a look.

Hi @tyf, thank you for your reply.
My mistake about the issuer and audience.
I can confirm that a sample token when decoded in jwt.io gives me the correct issuer and audience.
However, my code does not even get to the jwt.decode part as it crashes when attempting to get the signing_key in the verify method.

@tyf is there any more information you need in order to help me?

1 Like

Hey there @bakmo sorry for the delayed response!

Thanks for further clarification. I’m not sure what else could be causing the behavior you’re seeing but I’m actually looking into the sample now to see if I can reproduce in my own environment.

Hey again @bakmo !

Do you mind sharing your .config file via DM? I think I know what’s going on - The issuer claim in auth0 jwts will always have a trailing slash. In your jwt.decode method you are utilizing self.config["DOMAIN] which will result in https://your_domain instead of https://yourdomain/ which is required to match the issuer claim of the token.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.