hydra
hydra copied to clipboard
Difference in behavior when user denied consent for HTTPS and non-HTTPS redirect URI
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.
- [ ] This issue affects my Ory Network project.
- [ ] I have joined the Ory Community Slack.
- [X] I am signed up to the Ory Security Patch Newsletter.
Describe the bug
I am implementing my own User Login and Consent flow for ORY Hydra. I'm noticing a difference in redirect behavior when a user denies consent.
When the auth is invoked from the OAuth client in the mobile app i.e. using non-HTTPS redirect URI, Hydra's error endpoint is invoked (one set in the config YAML)
URL on completion of deny consent: https://account.acme.localhost/error?error=consent_denied&error_description=Consent+denied.+Please+try+again+and+consent+the+data+sharing
But when the same OAuth Client in the Web app with HTTPS redirect URI initiates the flow, it actually calls the Web app's redirect URI.
URL on completion of deny consent: https://service.acme.localhost/callback?error=consent_denied&error_description=Consent+denied.+Please+try+again+and+consent+the+data+sharing
Describe your ideal solution
HYDRA should call localhost.acme.service://callback?error=consent_denied&error_description=Consent+denied.+Please+try+again+and+consent+the+data+sharing
for the non-HTTPS redirect URIs.
Reproducing the bug
- Start with 5min-tutorial
- Add the OAuth Client with details as mentioned in the Additional Context section
- Create a simple web app to test OAuth workflow.
- Create a simple iOS app to test the OAuth workflow
- Perform the OAuth on both apps and press deny consent.
Relevant log output
No response
Relevant configuration
urls:
self:
issuer: https://account.acme.localhost
consent: https://account.acme.localhost/consent
login: https://account.acme.localhost/login
logout: https://account.acme.localhost/logout
error: https://account.acme.localhost/error
Version
v1.11.9
On which operating system are you observing this issue?
Other
In which environment are you deploying?
Docker Compose
Additional Context
Here is the OAuth Client information
{
"client_id": "TestOAuth2Client",
"client_name": "Test OAuth2Client",
"redirect_uris": [
"localhost.acme.service://callback",
"https://service.acme.localhost/callback"
],
"grant_types": [
"authorization_code"
],
"response_types": [
"code"
],
"scope": "openid email profile offline offline_access",
"audience": [],
"owner": "",
"policy_uri": "***",
"allowed_cors_origins": [],
"tos_uri": "***",
"client_uri": "***",
"logo_uri": "***",
"contacts": [],
"client_secret_expires_at": 0,
"subject_type": "public",
"jwks": {},
"token_endpoint_auth_method": "client_secret_basic",
"userinfo_signed_response_alg": "none",
"created_at": "***",
"updated_at": "***",
"metadata": {},
"authorization_code_grant_access_token_lifespan": null,
"authorization_code_grant_id_token_lifespan": null,
"authorization_code_grant_refresh_token_lifespan": null,
"client_credentials_grant_access_token_lifespan": null,
"implicit_grant_access_token_lifespan": null,
"implicit_grant_id_token_lifespan": null,
"jwt_bearer_grant_access_token_lifespan": null,
"password_grant_access_token_lifespan": null,
"password_grant_refresh_token_lifespan": null,
"refresh_token_grant_id_token_lifespan": null,
"refresh_token_grant_access_token_lifespan": null,
"refresh_token_grant_refresh_token_lifespan": null
}
Here is the NodeJS code which handles the user consent granted/deny
if (req.body.consentGranted) {
// get the consent request first so it that fails no need to query user details in generateConsentSession
const consentRequest = await Hydra.getConsentRequest(challenge);
const session = {
access_token: {},
id_token: {...},
};
const acceptConsent = await Hydra.acceptConsentRequest(challenge, {
grant_scope: grantScope,
grant_access_token_audience:
consentRequest.data.requested_access_token_audience,
remember: true,
remember_for: 0,
session,
});
return res.status(acceptConsent.status).send({ redirect: accept.data.redirect_to });
}
const denyConsent = await Hydra.rejectConsentRequest(challenge, {
error: 'consent_denied',
error_description: 'Consent denied.',
error_debug: JSON.stringify({ grantScope, challenge }, null, 2),
error_hint: 'Please try again and consent the data sharing.',
});
return res.status(denyConsent.status).send({
error: 'Consent denied. Please try again and consent the data sharing',
redirect: denyConsent.data.redirect_to,
});
Here is output of the denyConsent.data.redirect_to
- For the web app:
https://account.acme.localhost/oauth2/auth?client_id=TestOAuth2Client
& code_challenge=***
& code_challenge_method=S256
& consent_verifier=***
& nonce=***
& redirect_uri=https%3A%2F%2Fservice.acme.localhost%2Fcallback
& response_type=code
& scope=openid+email+profile+offline+offline_access
& state=***
- For the mobile app:
https://account.acme.localhost/oauth2/auth?client_id=TestOAuth2Client
& code_challenge=***
& code_challenge_method=S256
& consent_verifier=***
& nonce=***
& redirect_uri=localhost.acme.service%3A%2F%2Fcallback
& response_type=code
& scope=openid+profile+email+offline_access+offline
& state=***