-
Notifications
You must be signed in to change notification settings - Fork 819
Description
Hi. I am not entirely sure if this is a bug or I am misunderstanding something about OAuth2, I'd like to apologize in advance if it's the latter.
Describe the bug
I followed the tutorial to create an access token and tried using the IntrospectTokenView with that token. This view is a ClientProtectedScopedResourceView that includes required_scopes = ["introspection"]. My understanding of the documentation around these made me believe that if I use a bearer token without the appropriate scope, my request will fail. To my surprise, the call succeeds regardless of the scopes in the token I use.
To Reproduce
- Set up a basic OAuth2 provider as suggested by the tutorial.
- Use these steps to create an access token.
- Try accessing the
introspectendpoint like so:curl -X POST -H "Authorization: Basic ${CREDENTIAL}" -H "Cache-Control: no-cache" -H "Content-Type: application/x-www-form-urlencoded" "http://127.0.0.1:8000/o/introspect/" -d "token=..."
Expected behavior
Even when using the client-credentials flow, I expect a token that doesn't have the introspection scope to be rejected when calling an endpoint declaring required_scopes = ["introspection"].
Version
2.4.0
- I have tested with the latest published release and it's still a problem.I have tested with the master branch and it's still a problem.To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.
Additional context
I googled around and found this Auth0 thread for a similar question. There it is suggested that scopes in a token are not considered at all if using client-credentials. However, in that example the client itself has associated scopes. This isn't the case for the default setup of DOT, as far as I can see.
As it stands, when using client-credentials, any token can be inspected by any client. If this is the desired behavior that's fine with me. If not, however, you might consider a small change to IntrospectTokenView.get_token_response that uses cls.required_scopes:
@classmethod
def get_token_response(cls, token_value=None):
try:
...
except ObjectDoesNotExist:
return JsonResponse({"active": False}, status=200)
else:
if token.is_valid(cls.required_scopes):
...
Activity
n2ygk commentedon Aug 8, 2024
I think you are right that the introspection client (what DOT calls an application) should be somehow constrained in whether it is authorized to introspect. However I don't think using
token.is_valid(cls.required_scopes)is correct as that is checking the token that is being introspected rather than the token of the introspector. Also only aAuthorization: Bearer <token>would have an access token with a scope. WhenAuthorization: Basic <base64(user:pass)>is used, there is no place for a scope as there is no Access Token and technically this is not a client credentials flow as implemented, so it seems any application with client_credentials grant_type can be used for introspection. Hopefully most actual users have authorization_code grant_type.Introspection Basic auth was added pretty recently (2019 in #725:-) so I suspect the earlier Bearer-only version of introspection worked correctly [1]. When support for Basic was added it broke this checking. Prior to that PR, introspection could only be done with a Bearer access token.
I expect a PR to fix this would somehow have to constrain applications with the client_credentials as discussed in #709
BTW, the part of the discussion in #709 about constraining which applications a particular introspection application can introspect might be a little too much. In my experience with a commercial product, an introspector can introspect any access token. And, with OIDC, anyone with a given Access Token can essentially introspect it via the Userinfo endpoint.
However, in the commercial product, the introspector is specially identified with a "fake" grant type of
ACCESS_TOKEN_VALIDATION. In other words it is flagged as somehow special and allowed to invoke the introspection endpoint rather than just allowing anyone with client_credentials to do so. I believe this historically predates the creation of a standardized introspection endpoint which came out 3 years after OAuth2So to summarize, using
Authorization: Basicis technically not an OAuth2 client credentials flow at all and conflating that was probably a mistake. If it were a real client credentials flow, the introspector client would have made anAuthorization: Basicclient credentials request to the AS's token endpoint, been given an Access Token, and then presented that Access Token in anAuthorization: Bearerheader.[1] Further, given that any client can request any scope, I think that means that any client's access token (Bearer) can be an introspector!
n2ygk commentedon Aug 8, 2024
Confirmed in testing that anyone can request
introspectionscope (e.g. using authorization code flow) and then use the Bearer token to introspect any access token.Not a huge concern since the access token needs to be protected no matter what as possession means access to whatever resources allow it.
makulatur commentedon Aug 9, 2024
Hey, thanks for the very quick response time.
facepalm - of course.
Sorry, I am still new to this thing. According to this article on the OAuth website the "client needs to authenticate themselves for this request. Typically the service will allow either additional request parameters client_id and client_secret, or accept the client ID and secret in the HTTP Basic auth header." This reads to me as if it's acceptable to use auth basic in the client credentials flow. Maybe I'm missing something.
I dug around a bit and it seems to me like the
OAuth2Validatoralready has most of the tooling to retrieve the client from a request with basic auth, but the functions are not yet refactored in a way that they are easy to use inside theIntrospectTokenView.Thanks for the summary, this seems logical to me (although I still wonder about the article above).