auth0 / auth0-python

Auth0 SDK for Python
https://auth0-python.readthedocs.io
MIT License
509 stars 165 forks source link

Getting info about JWT user #242

Closed michaldev closed 3 years ago

michaldev commented 3 years ago

How Can I check information about authorized users? Like username, and others?

I've created/copied this code:

from auth0.v3.authentication.token_verifier import TokenVerifier, AsymmetricSignatureVerifier

domain = 'dev-my-id.eu.auth0.com'
client_id = 'https://my-host.com'

id_token = 'my_best_token'

jwks_url = 'https://{}/.well-known/jwks.json'.format(domain)
issuer = 'https://{}/'.format(domain)

sv = AsymmetricSignatureVerifier(jwks_url) 
tv = TokenVerifier(signature_verifier=sv, issuer=issuer, audience=client_id)
tv.verify(id_token)

It works nice, but in the documentation doesn't exist any example with the real use case of this. I search in the code of the library and I find Users class, but tokenInfo() method is deprecated (why?) and in userInfo JWT token doesn't work.

Users(domain=domain).tokeninfo(id_token) - 404 error. Users(domain=domain).userinfo(id_token) - 401 error.

lbalmaceda commented 3 years ago

The ID token already contains the user information in its payload. But before accessing that, you should first verify its precedence and that's where you use the TokenVerifier class which would throw an exception if something is invalid. The next step would be using a JWT decoding library to access the payload of that ID token. You can inspect the ID token in a site like jwt.io to understand what information is available.

Alternatively, you could make a second request to the /userinfo endpoint but using the access token instead. The contents you see both inside the id token payload and the user info response should be the same.

michaldev commented 3 years ago

But from can I get access token for "user info"? On https://my-id.auth0.com/oauth/token endpoint I receive only one token - JWT-token.

lbalmaceda commented 3 years ago

There are many variables that can change what you receive. Can you show me the code you're using to authenticate a user, please?

michaldev commented 3 years ago

Zrzut ekranu z 2020-10-02 10-24-40

StigKorsnes commented 3 years ago

@michaldev If you open your application in auth0 dashboard and go to advanced settings at the bottom, there is a tab of endpoints. You use the provided acceess_token you already have with the user info endpoint. Note that it is a requirement that openid is included as a scope in order for the /userinfo endpoint to work.

If you just want to programatically look at stuff, you can use this sdk`s authentication.GetToken class. and authentication.AsymmetricSingatureVerifier. AsymmetricSingatureVerifier.verify_signature() returns the decoded token payload. Tokenverifier uses the results from your signature verifer (payload), but does additional checks on the claims. (payload contents) However, this one does not return anything, only throws errors.

michaldev commented 3 years ago

Maybe auth0 isn't for me. I have a simple use case. I would like to authorize my users in my mobile application written in a flutter. I would like to show them popup (webview) for login and full option (register, forgot password, social auth) and backend (in python/fastapi) should know who is authenticated.

For using auth0 I take a decision because (propably) I don't need to use multiple native libraries for social auth on the frontend (only webview popup) and in python backend I don't need implement mailing, authentication, and others.

Additionally, I have less in app pages/screens which user see once (like login, register, forgot, reset password). Then I will have a cleaner code and I will be able to focus on other screens.

But auth0 I think that exists for others solutions (authentication for multiple websites etc.) and in my use case isn't best solution - right?

michaldev commented 3 years ago

If you open your application in auth0 dashboard and go to advanced settings at the bottom, there is a tab of endpoints. You use the provided acceess_token you already have with the user info endpoint. Note that it is a requirement that openid is included as a scope in order for the /userinfo endpoint to work.

And I have to get only code (from /authorize) in the mobile app and send them to python backend, then in python use code? What is the best way? I was thinking that authorization jwt for my python backend.

Code or JWT do I have to save in my backend DB? I was thinking that in backend I don't have to store anything, and in mobile app I have to store JWT.

lbalmaceda commented 3 years ago

Let me explain it briefly, although I encourage you to watch the identity labs Auth0 published. The idea is that your mobile app first gets the access token, and using it then calls your backend/API.

From the mobile app, you should be performing a "Code + PKCE" flow (read more about this flow here). That's a call to /authorize with the "response_type=code" and a challenge. The successful response would give you back a "code" value that you have to exchange along with the "code verifier" against the /oauth/token endpoint (the "code exchange" part). The result of that second request is a pair of credentials, always including the access token.

In the case of requiring to call an API (like your case), you would also pass an "audience" value in the /authorize call, so the access token that comes back is authorized to be used on that audience (and "aud" claim will be added). You'd first have to create an "Auth0 API" in your dashboard and authorize it for the Auth0 client/application that you want to using. Then, after logging in, you would call your API using that authorized access token that you received.

In your backend side, you'd receive a request authorized with the access token. You should take that access token and perform some checks before trusting the contents.

I've found that most of the above is explained in detail in this article. Note that there are many Auth0 SDKs you could use, depending on the platform, to skip many of the manual steps explained in there. You can also find many quickstarts explaining how to authenticate and call an API in the site.

michaldev commented 3 years ago

Authorization with PKCE is failed.

Zrzut ekranu z 2020-10-02 16-43-58

Zrzut ekranu z 2020-10-02 16-41-15

The code in the second request is code from the first request-response.

Note that there are many Auth0 SDKs you could use, depending on the platform, to skip many of the manual steps explained in there

I use the FastApi.

lbalmaceda commented 3 years ago

I suggest you first follow one of the mobile app quickstart to understand how the authentication is achieved https://auth0.com/docs/quickstart/native.

Once you are able to authenticate and have your access token, then move the attention to the backend / m2m https://auth0.com/docs/quickstart/backend.

michaldev commented 3 years ago

There is not available example for Flutter. Is available one example on any auth0 blog, but with library which doesn't support Web and desktop.

I must implement it from scratch.

What is wrong with my request on screen?

michaldev commented 3 years ago

I can get access token with other method (without PkCe). But as in post - this verification proces very well, but I don't know how can I use this for getting info about user.

lbalmaceda commented 3 years ago

@michaldev you are creating an issue in the auth0-python SDK about a Flutter mobile app that is not working for you... I cannot help you with that, but you can try with the community. What I put above is general guidance and usage for any mobile scenario.

If we forget for a minute about mobile apps and assume that you have a python backend using this SDK and receive an access token through some external request, you could use that access token to call the /userinfo endpoint and get the details associated with the user that the access token is representing. That's what I put in my first comment.

michaldev commented 3 years ago

But I must get user informations on backend (python) side. As I wrote - if I sucessed get user token (without pkce) I can't get user info in python script, because tokenInfo method is deprecated. userInfo method doesn't work. Code from first entry in this post works good. Verify returns true, token is correctly. Problem is when I try to get info about authenticated user.

StigKorsnes commented 3 years ago

@michaldev I'm not a contributer to the package, nor a seasoned developer, so please don't take my comments as official guidelines. I've been struggeling a couple of weeks with the docs and playing around with the various settings in the tennant dashboard, but feel the payoff is worth the effort. The more I work with it, the more I like it, and don`t think auth0 in anyway is to complex for your use-case. I am using a fast-api backend, as you. Authentication is done outside the api, but when I send a request to one of the fastapi endpoints I include the access token in the header. In the API I do validation of the access token using this package. (in the lines of what is suggested by @lbalmaceda in previous posts here).

Regarding tokeninfo() from this package have l'm guessing you already have read the docs and found this (authentication API):

/tokeninfo This endpoint is part of the legacy authentication pipeline and will be disabled for those who use our latest, OIDC conformant, pipeline. We encourage using the /userinfo endpoint instead. For more information on the latest authentication pipeline refer to Introducing OIDC Conformant Authentication.

I`m guessing you have the switch for OIDC-conformant on (which you probably should?), and this is the reason you are getting a 404.

Also tested Users.userinfo() and it works on my end.

michaldev commented 3 years ago

This is weird. When I set scope to openid I get access_token and id_token but if I try logged with them (verify method) I got error: web_1 | auth0.v3.exceptions.TokenValidationError: Authorized Party (azp) claim mismatch in the ID token; expected "https://appaddress.com", found "3he3K4UasIj4in2RKi6000000". (I entry app id where it is required)

In Auth0 logs everything is ok :/

In payload by jwt.io I have:

{
  "iss": "https://dev-ehczh8rt.eu.auth0.com/",
  "sub": "auth0|5f74b17177ff6c0071999701",
  "aud": [
    "https://appaddress.com",
    "https://dev-myid.eu.auth0.com/userinfo"
  ],
  "iat": 1602014699,
  "exp": 1602101099,
  "azp": "3he3K4UasIj4in20000000000",
  "scope": "openid"
}

But Users(domain=domain).userinfo(auth_token) returns for me username. Why now verification is failed?

michaldev commented 3 years ago

Maybe problem is in two audiences? Python-auth0 supports two audiences?

michaldev commented 3 years ago

There is details: https://vimeo.com/466912574 @lbalmaceda

StigKorsnes commented 3 years ago

@michaldev The codebase and readme indicates that TokenVerifier is aimed for ID Tokens only. In addition the docstring says it verifies according to an openid spec. Given this, it might be a bit optimistic to assume it would work equally well for access tokens. Mentioned this in my first post, and also raised an issue on this. Also suggested you use AsymmetricSignatureVerfier alone, although this one only checks signature (as far as I can see). You would have to write additional checks your self, to my understanding. The lack of response from auth0-team is a bit alarming...

lbalmaceda commented 3 years ago

When the ID token has multiple audiences, the azp claim is added by the server and must be verified against the client id value. The ID token verifier accepts this value as the "audience" parameter. The ID token can have as many audiences as it needs, as long as the one you require is listed there and also matches the azp value. That's the error you are seeing above when you attempt to verify the ID token.

Watching your recording, you mention that you are using a "native app" client id for this python backend app and that's where I'm lost. You should be using a different client id, not reusing the same, as this is a separate application. Each must have its right application type depending on what they represent: a mobile app, a backend API, etc. It looks like you are trying to call your API from the mobile app. There's a quickstart showing exactly that, which I already recommended above, have you checked that?

Python API Quickstart -> https://auth0.com/docs/quickstart/backend/python/01-authorization

This is how it should look:

If your use case is different to what I understood, or need a prompt response, you should reach out instead to our support team or ask in the Auth0 community so they can help you better. Cheers

michaldev commented 3 years ago

This is not clearly "quickstart". Without my use case. That presents machine-to-machine (any client_credentials).

I've created now separated applications in my auth0 panel:

But I have problem still. auth0.v3.exceptions.TokenValidationError: Audience (aud) claim mismatch in the ID token; expected "fYAWWX04VdjZdj4LtKvnEqFaLlnD92fb" but found "3he3K4UasIj4in2RKi6NytQWZKWLrsEY"

3he3K4UasIj4in2RKi6NytQWZKWLrsEY - this is native app ID. fYAWWX04VdjZdj4LtKvnEqFaLlnD92fb - this is machine to machine app ID.

In /authorize - I have '3he3K4UasIj4in2RKi6NytQWZKWLrsEY' - native ID. In /oauth/token I have '3he3K4UasIj4in2RKi6NytQWZKWLrsEY' - native ID.

In python script I have 'machine to machine' ID - fYAWWX04VdjZdj4LtKvnEqFaLlnD92fb.

I've tried all combinations with application id. Always errors in token verification.

lbalmaceda commented 3 years ago

Don't use the ID token verifier in your backend. The backend API should not receive an ID token from the mobile app, only an access token.

michaldev commented 3 years ago

Verification by access_token returns error the same like id_token.

StigKorsnes commented 3 years ago

@michaldev He is saying DON'T use TokenVerifier.verify for anything else than validating id tokens. Here is roughly what I'm doing at the moment.


from auth0.v3.authentication.token_verifier import AsymmetricSignatureVerifier
from auth0.v3.exceptions import TokenValidationError
from fastapi import HTTPException,status

signature_verifier=AsymmetricSignatureVerifier(jwks_url=jwks_url)

async def validate_token_signature(api_key:str=Depends(rann_oauth))->AccessTokenPayload:   
    try:
        decoded_token=signature_verifier.verify_signature(token=api_key)
        if decoded_token.get("exp")<time.time():
            raise TokenValidationError("Token has expired")
       #Some other checks?

    except TokenValidationError as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=str(e),
            headers={"WWW-Authenticate": f"Bearer"},
        )

    return AccessTokenPayload(**decoded_token)
michaldev commented 3 years ago

It works, but this is secure? If I'm not compare token with auth0 server?

StigKorsnes commented 3 years ago

@michaldev Well, this is my reasoning (again as mentioned in the first post): A TokenVerifier instance has an AsymmetricSignatureVerifier property (sv from the example). The TokenVerifier instance will, when you invoke tv.verify, first call sv.verify_signature to get the payload. If this does not raise an error, the signature is ok. After this step, tv.Verify will then do checks on the claims according to the open id spec. Please see the code to confirm this.

Alternatively you could get some additional checs from sv.verify_signature by changing some of the options on the class (DISABLE_JWT_CHECKS), but since you can not pass on arguments to jwt.decode only a few would work. This would be a hack, and would not reccomend it. Just mentioning it to motivate you to dig a bit around in the code to see what is going on :)

It is a bit unfortunate that the sdk does not have an access_token verifier. Auth0 has an example online https://auth0.com/docs/quickstart/backend/python/01-authorization . That one uses python jose. While it has similar interface (jwt.decode) it accepts the key set as a dict, while this sdk uses pyjwt, but you can not pass on arguments like leeway,audience, issuer etc. to the jwt.decode function. In my oppinon this sdk should support it, and the docs online should use this sdk in the examples. Everyone knows security is an all elusive topic to grasp, an in my oppinon the current state just adds to the confusion.

As far as I can tell, I see two possible options when it comes to validating an access token:

  1. Use this sdk to verify the signature. Pros: You get the cached keyset if you define the instance outside of the function-call. Cons: Additional check on claims must be implemented yourself. (See the code for tv.Verify for hints on how to)
  2. Drop this sdk, and implement the example. Pros, you at least get checks on audience,issuer and expiry out of the box. Cons: You would have to store or cache the keyset if you want to avoid hitting the jwks url for every request which depends on it.

I guess there are more libraries/sdk's for validating jwt`s than mentioned here, but hopefully @lbalmaceda will confirm my two options are viable solutions.

lbalmaceda commented 3 years ago

The TokenVerifier is only meant to verify ID tokens following the OIDC spec.

If your app is a backend app, and you receive a request that contains an access token, you must verify:

You could use the SignatureVerifier and the JwksFetcher classes if you want. Those expose one public method each to respectively verify an algorithm name and signature or get the JWK Set. You'd have to add the logic to check the remaining conditions. We don't have plans to support this officially, but as mentioned above, there are other JWT libraries that can help you verify a token.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️

cledesma commented 3 years ago

Hello! Any updates here? I can see that this issue is also present for the PHP token verifier, and the team fixed it here https://github.com/auth0/auth0-PHP/issues/422 https://github.com/auth0/auth0-PHP/pull/428

Are we also fixing it for the Python library? I am having the same issues as @michaldev, and I am only following your documentation

lbalmaceda commented 3 years ago

@cledesma we still don't have in the roadmap introducing a verifier for Access Tokens on the python SDK. What is your use case?

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️

authereal commented 3 years ago

Hi I've a similar use case as https://github.com/auth0/auth0-PHP/issues/422 and am wondering if a similar approach to the one that the PHP SDK went with can be used: https://github.com/auth0/auth0-PHP/pull/428. Would you accept a PR for this?

authereal commented 3 years ago

@lbalmaceda friendly ping :)

lbalmaceda commented 3 years ago

@authereal If the ID token that you are trying to validate is not compliant with the OIDC spec, I doubt we will accept a feature request to support that. The ID token verifier implemented here follows the OIDC spec.

If you are suggesting a different feature, please open a separate issue so we can review it. Thanks.