RFC7622 OAuth 2.0 Token Introspection says,
To prevent token scanning attacks, the endpoint MUST also require
some form of authorization to access this endpoint, such as client
authentication as described in OAuth 2.0 [RFC6749] or a separate
OAuth 2.0 access token such as the bearer token described in OAuth
2.0 Bearer Token Usage [RFC6750].
...
In this example, the protected resource uses a client identifier and
client secret to authenticate itself to the introspection endpoint.
So Token Introspection Endpoint should require a protected resource's credential.
But doorkeeper's implementation requires a credential of the client the access token was issued to.
https://github.com/doorkeeper-gem/doorkeeper/blob/a729e80248031c1030c4dc0f89474932fe0d6603/lib/doorkeeper/oauth/token_introspection.rb#L104-L109
Send a request to Token Introspection Endpoint with a protected resource's client_id/client_secret.
Token Introspection Endpoint returns a success response.
Token Introspection Endpoint returns a failure response.
{ active: false }
Doorkeeper initializer:
no customize
Ruby version:
2.5.3
Gemfile.lock:
doorkeeper (5.0.2)
railties (>= 4.2)
Hi @kurodashota-mf . Thanks for reporting this issue!
Let's look:
such as client authentication as described in OAuth 2.0 [RFC6749]
or a separate OAuth 2.0 access token such as the bearer token
described in OAuth 2.0 Bearer Token Usage [RFC6750].
So Doorkeeper can implement either first or second type of authorization (not both), right?
Btw, I agree that we can implement the second solution from the spec. Would you like to submit a PR?
Hi @nbulaj. Thank you for your quick response.
There may be a little misunderstanding.
I don't want to the second solution.
Doorkeeper returns failure response in case The token was issued to a different client than is making this request. But I think it's wrong.
From my understanding, in Token Introspection Endpoint, client_id and client_secret are not for authenticating a client an access_token issued to, but for authenticating a protected resource.
As @kurodashota-mf says, Token Introspection (RFC 7662) is designed for Resource Servers (RS), not for Clients.
This spec enables RS calls Token Introspection API, so that it can detect which client is accessing to its resource with which scopes. It also imply RS has its own client credentials issued from AS.
This spec doesn't prohibit Clients use the API, so that client can use the API to mitigate token replace attack. However, it's not original purpose of the RFC. It's just a side effect.
So, Token Introspection API should accept client credentials not only of OAuth Clients, but of Resource Servers.
ref.) https://tools.ietf.org/html/rfc7662#section-1
Since OAuth 2.0 does not define a protocol for the resource server to learn meta-information about a token that it has received from an authorization server, several different approaches have been developed to bridge this gap.
:
This specification defines a protocol that allows authorized protected resources to query the authorization server to determine the set of metadata for a given token that was presented to them by an OAuth 2.0 client.
Plus, current doorkeeper seems accept whether
First one reject any other clients other than the client to which the token is issued, but the second accept any clients who have valid access tokens. It looks weird.
If the introspected token is valid, anyone can specify it in bearer header too. Then the call success.
I don't get the design concept...
In general, the API would accept whether
ref.) https://tools.ietf.org/html/rfc7662#section-4
For instance, these tests include the following:
- If the token can expire, the authorization server MUST determine whether or not the token has expired.
- If the token can be issued before it is able to be used, the authorization server MUST determine whether or not a token's valid period has started yet.
- If the token can be revoked after it was issued, the authorization server MUST determine whether or not such a revocation has taken place.
- If the token has been signed, the authorization server MUST validate the signature.
- If the token can be used only at certain resource servers, the authorization server MUST determine whether or not the token can be used at the resource server making the introspection call.
Hi @nov. Don't sure I understood you
First one reject any other clients other than the client to which the token is issued, but the second accept any clients who have valid access tokens. It looks weird.
Well, if you take a look at 2.1. Introspection Request of RFC 7662 then you will see:
To prevent token scanning attacks, the endpoint MUST also require
some form of authorization to access this endpoint, such as client
authentication as described in OAuth 2.0 [RFC6749] or a separate
OAuth 2.0 access token such as the bearer token described in OAuth
2.0 Bearer Token Usage [RFC6750]. The methods of managing and
validating these authentication credentials are out of scope of this
specification.
So Doorkeeper check if _any_ valid access tokens (it's a _separate OAuth 2.0 access token_ from the spec) or client credentials. I agree that checking if introspected token belongs to client with credentials from the request is a gem addition (and maybe it need to be removed), and I thought it's for security purposes, but well, seems like it's violate the second case when request authorized using just separate token and allows to check any other token :D :D
I'm for any PR that will fix the problem, I'm currently super-busy to review it myself. Token Introspection definitely need to be fixed.
Introspected token isn't separate access token.
So at least implementation should reject requests including active access token both in body (as introspected token) and bearer header.
If you allow same token in body & bearer header, it can't prevent token spanning attacks.
By _separate_ I meant access token that was used to authorize on the server:
or a separate OAuth 2.0 access token such as the bearer token described in OAuth
2.0 Bearer Token Usage [RFC6750].
For the following:
So at least implementation should reject requests including active access token both in body (as introspected token) and bearer header.
you mean that client can't request token introspection using both ?access_token param and Auth: Bearer %token% header in the same request? Or I missed something?
I mean if clients want to introspect token-a, they need to specify another token (e.g., token-b) in the Authorizaiton header.
So, this is NG.
POST /introspect
Authorization: Bearer <token-a>
access_token=<token-a>
This is OK
POST /introspect
Authorization: Bearer <token-b>
access_token=<token-a>
So Token Introspection Endpoint should require a protected resource's credential.
But doorkeeper's implementation requires a credential of the client the access token was issued to.
OK @kurodashota-mf , I remember from where it:
https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
see _Error Response_ section. Need to investigate more to fix the issue.
Any other error is considered an "inactive" token.
- The token requested does not exist or is invalid
- The token expired
- The token was issued to a different client than is making this request
So Doorkeeper checks if client credentials used to authenticate is the same as for client that token was issued for. I agree with nov that the second check (if no credentials that just check the token to be valid) is cucumbersome and need to be fixed.
Could you please propose a gist RSpec example that fails with current behavior?
@nov could you please point me on such section from RFC?
So at least implementation should reject requests including active access token both in body (as introspected token) and bearer header.
If you allow same token in body & bearer header, it can't prevent token spanning attacks.
Need to know that oauth 2 server must respond with for such cases.
That is what separate mean in the RFC 7662 Section 2.1.
If you want to use bearer tokens for token introspection API access control, you need an access token separated from the introspected token.
To prevent token scanning attacks, the endpoint MUST also require
some form of authorization to access this endpoint, such as client
authentication as described in OAuth 2.0 [RFC6749] or a separate
OAuth 2.0 access token such as the bearer token described in OAuth
2.0 Bearer Token Usage [RFC6750].
Hi @kurodashota-mf . Could you please take a look at this changes https://github.com/doorkeeper-gem/doorkeeper/commit/fe2accf3c00eecf12d04a27b6242ba7f55158ed9#diff-5305ee84a3e2c5367264952b4f1b8610R317 (and previous #1257). Does they solve your problem?
Yes, it solves my problem. Thanks!
Can we close the issue now, @kurodashota-mf ?
If there are still some improvements you may suggest, you're very welcome with the PR :)
Can we close the issue now?
OK, please.
I appreciate your support 馃憤
Now we have configuration option to customize Token Introspection endpoint auth logic so you can define your custom rules (check auth token for specific scope, validate client, check superapp and so on):
Closing this PR
Most helpful comment
I mean if clients want to introspect
token-a, they need to specify another token (e.g.,token-b) in the Authorizaiton header.So, this is NG.
This is OK