Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oauth2 webhook: granted_scopes always empty #3620

Open
4 of 5 tasks
dastein1 opened this issue Aug 25, 2023 · 8 comments · May be fixed by #3891
Open
4 of 5 tasks

oauth2 webhook: granted_scopes always empty #3620

dastein1 opened this issue Aug 25, 2023 · 8 comments · May be fixed by #3891
Labels
bug Something is not working.

Comments

@dastein1
Copy link
Contributor

Preflight checklist

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:

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
@dastein1 dastein1 added the bug Something is not working. label Aug 25, 2023
@yanuehara-mb
Copy link

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 (

hydra/oauth2/handler.go

Lines 1005 to 1020 in 89b1b1b

for _, hook := range h.r.AccessRequestHooks() {
if err := hook(ctx, accessRequest); err != nil {
h.logOrAudit(err, r)
h.r.OAuth2Provider().WriteAccessError(ctx, w, accessRequest, err)
events.Trace(ctx, events.TokenExchangeError, events.WithRequest(accessRequest))
return
}
}
accessResponse, err := h.r.OAuth2Provider().NewAccessResponse(ctx, accessRequest)
if err != nil {
h.logOrAudit(err, r)
h.r.OAuth2Provider().WriteAccessError(ctx, w, accessRequest, err)
events.Trace(ctx, events.TokenExchangeError, events.WithRequest(accessRequest))
return
}
) 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?

@dastein1
Copy link
Contributor Author

@aeneasr any thoughts on that?

@jossbnd
Copy link

jossbnd commented Dec 18, 2023

Hi, we are having the same issue, would you have any updates please ? @hperl @dastein1 @aeneasr
We are running on v2.1.2

@dastein1
Copy link
Contributor Author

dastein1 commented Jan 15, 2024

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).

@vmuth85
Copy link

vmuth85 commented Aug 14, 2024

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.

@dfilicko-at-pacellico
Copy link

I've spent some time on investigation and here are my notes:

  • granted_scopes as well granted_auidience is always empty
  • even though consent is approved it does not work for authorization_code grant type
  • if you try refresh_token grant type, granted_scopes as well granted_auidience is 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.

@vmuth85
Copy link

vmuth85 commented Oct 15, 2024

Any feedback from the core team @aeneasr on this?

@3schwartz 3schwartz linked a pull request Nov 18, 2024 that will close this issue
7 tasks
@3schwartz
Copy link

3schwartz commented Nov 18, 2024

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 (

hydra/oauth2/handler.go

Lines 1005 to 1020 in 89b1b1b

for _, hook := range h.r.AccessRequestHooks() {
if err := hook(ctx, accessRequest); err != nil {
h.logOrAudit(err, r)
h.r.OAuth2Provider().WriteAccessError(ctx, w, accessRequest, err)
events.Trace(ctx, events.TokenExchangeError, events.WithRequest(accessRequest))
return
}
}
accessResponse, err := h.r.OAuth2Provider().NewAccessResponse(ctx, accessRequest)
if err != nil {
h.logOrAudit(err, r)
h.r.OAuth2Provider().WriteAccessError(ctx, w, accessRequest, err)
events.Trace(ctx, events.TokenExchangeError, events.WithRequest(accessRequest))
return
}

) 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?

We encountered the same issue, and I’ve created a PR implementing your suggestion to add RequestedScopes() to the webhook response.
#3891

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something is not working.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants