oauth2 webhook: `granted_scopes` always empty
Preflight checklist
- [X] I could not find a solution in the existing issues, docs, nor discussions.
- [X] I agree to follow this project's Code of Conduct.
- [X] I have read and am following this repository's Contribution Guidelines.
- [X] I have joined the Ory Community Slack.
- [ ] I am signed up to the Ory Security Patch Newsletter.
Ory Network Project
No response
Describe the bug
The documentation outlines that the webhok should receive request-details.
The request has a field called granted_scopes, but it's always an empty list instead of the granted scopes by the consent application.
{
...
"request": {
"client_id": "some-client-id",
"granted_scopes": [], // <- THIS IS ALWAYS EMPTY
"granted_audience": [],
"grant_types": [
"authorization_code"
],
"payload": {}
}
}
Previous slack discussions around this topic:
- https://archive.ory.sh/t/13537419/hi-all-im-currently-trying-out-https-www-ory-sh-docs-hydra-g
- https://archive.ory.sh/t/14170318/hi-i-was-wondering-if-there-s-a-possibility-to-get-the-grant
Reproducing the bug
Clone hydra
git clone https://github.com/ory/hydra.git
cd hydra
Run server
enable the token_hook in contrib/quickstart/5-min/hydra.yml have this token_hook log the request
oauth2:
token_hook: http://host.docker.internal:1323
docker-compose -f quickstart.yml \
-f quickstart-postgres.yml \
-f quickstart-tracing.yml \
up --build
Create client and run auth code flow
Note: accept at least 1 of the scopes
code_client=$(docker-compose -f quickstart.yml exec hydra \
hydra create client \
--endpoint http://127.0.0.1:4445 \
--grant-type authorization_code,refresh_token \
--response-type code,id_token \
--format json \
--scope openid --scope offline \
--redirect-uri http://127.0.0.1:5555/callback)
code_client_id=$(echo $code_client | jq -r '.client_id')
code_client_secret=$(echo $code_client | jq -r '.client_secret')
docker-compose -f quickstart.yml exec hydra \
hydra perform authorization-code \
--client-id $code_client_id \
--client-secret $code_client_secret \
--endpoint http://127.0.0.1:4444/ \
--port 5555 \
--scope openid --scope offline
Check
In the request that the token_hook receives. The request.grant_types remains empty.
Relevant log output
No response
Relevant configuration
oauth2:
token_hook: http://host.docker.internal:1323
Version
oryd/hydra:v2.2.0-rc.3
On which operating system are you observing this issue?
None
In which environment are you deploying?
None
Additional Context
- Issue also present on master
- Not related to OS or deployment
I'm having the same issue in v2.1.1. Running the project locally, I can see that the hook is executed before the requester is granted any scopes (https://github.com/ory/hydra/blob/89b1b1bd35d7d906b8cf9048dcc34d416aa3a9b0/oauth2/handler.go#L1005-L1020) which is according to the docs.
Thing is, during the authorization_code flow when the webhook handler calls requester.GetGrantedScopes() no scope was granted yet.
@aeneasr Do you see any problems in running the hook after the internal logic is executed (i.e after the NewAccessResponse(ctx, accessRequest) call)? In this scenario I think the begin/commit of the transactions that invalidates the refresh and authorization code in the database needs to be moved to the outer scope (will need changes in ory/fosite too).
Alternatively, what are you thoughts in using the requester.RequestedScopes() instead of requester.GrantedScopes() in this case?
@aeneasr any thoughts on that?
Hi, we are having the same issue, would you have any updates please ? @hperl @dastein1 @aeneasr We are running on v2.1.2
Unfortunately there's still no feedback from the core team. I brought that topic up on slack twice but it did not lead anywhere (@hperl @aeneasr @kmherrmann).
For the time being, I have worked around that problem by only doing stuff in the token-hook that does not depend on the granted_scopes and the rest within the consent application (although that means to duplicate some functionality between services).
Same here! After accepting the consent request with the given Parameters (grantScopes included) the request details remains empty:
public String acceptConsentRequest(Map<String, Object> idToken, List<String> grantScopes, String consentChallenge) {
AcceptConsentRequest consentRequest = new AcceptConsentRequest();
consentRequest.setRemember(true);
// If set to '0', the authorization will be remembered indefinitely.
consentRequest.setRememberFor(0L);
consentRequest.setGrantScope(grantScopes);
AcceptConsentRequestSession session = new AcceptConsentRequestSession();
session.setIdToken(idToken);
consentRequest.setSession(session);
ResteasyWebTarget target = getWebTargetInstance();
Response response = target.proxy(OAuthProxy.class).acceptConsentRequest(consentChallenge, consentRequest);
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
CompletedRequest completedRequest = response.readEntity(new GenericType<CompletedRequest>() {
});
if (completedRequest != null) {
return completedRequest.getRedirectTo();
}
}
return null;
}
The redirect URI looks as follows:
https://localhost:9000/oauth2/auth?client_id=084c2a4e-de40-4537-8cdd-9a0c937acde3&consent_verifier=6eb972e5b86743bd96f86dbabef2c6b5&redirect_uri=http%3A%2F%2Flocalhost%3A2003&response_type=code&scope=openid+email&state=ur_kol1shpflj8h95snqnh1d8halg
Calling the URL will provide this:
{
"session": {
"id_token": {
"id_token_claims": {
"jti": "",
"iss": "https://localhost:9000/",
"sub": "[email protected]",
"aud": [
"084c2a4e-de40-4537-8cdd-9a0c937acde3"
],
"nonce": "",
"exp": "0001-01-01T00:00:00Z",
"iat": "2024-08-14T12:26:25Z",
"rat": "2024-08-14T12:26:13Z",
"auth_time": "2024-08-14T11:38:40Z",
"at_hash": "",
"acr": "",
"amr": [
],
"c_hash": "",
"ext": {
"email": "[email protected]",
"email_verified": true,
"given_name": "Hansen",
"name": "Hansen Pansen",
}
},
"headers": {
"extra": {
"kid": "b932e971-a105-4151-927c-6e8759ce6881"
}
},
"expires_at": {
"access_token": "2024-08-14T13:26:26Z",
"authorize_code": "2024-08-14T12:36:25.561520554Z",
"refresh_token": "2024-09-13T12:26:26Z"
},
"username": "",
"subject": "[email protected]"
},
"extra": {
},
"kid": "",
"client_id": "084c2a4e-de40-4537-8cdd-9a0c937acde3",
"consent_challenge": "6a70d27d82cb460cb5ff6c301a877555",
"exclude_not_before_claim": false,
"allowed_top_level_claims": [
]
},
"request": {
"client_id": "084c2a4e-de40-4537-8cdd-9a0c937acde3",
"granted_scopes": [
],
"granted_audience": [
],
"grant_types": [
"authorization_code"
],
"payload": {
}
}
}
As you can see, the "granted_scopes" are empty.
I've spent some time on investigation and here are my notes:
granted_scopesas wellgranted_auidienceis always empty- even though consent is approved it does not work for
authorization_codegrant type - if you try
refresh_tokengrant type,granted_scopesas wellgranted_auidienceis properly populated
We were using kratos-selfservice-ui-node to provide consent screen and we were skipping consent, so all grants and scope were passed through and accepted.