feat(core): add OAuth2 consent flow support#584
Conversation
|
|
@Jorgagu is attempting to deploy a commit to the ory Team on Vercel. A member of the Team first needs to authorize it. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #584 +/- ##
===========================================
+ Coverage 42.43% 55.68% +13.25%
===========================================
Files 136 178 +42
Lines 2008 3369 +1361
Branches 288 494 +206
===========================================
+ Hits 852 1876 +1024
- Misses 1149 1416 +267
- Partials 7 77 +70
🚀 New features to boost your workflow:
|
|
@vinckr @jonas-jonas @aeneasr Happy New Year ! 🎉 Could you please review this one ? |
|
hi @Jorgagu, thank you very much for this contribution, and happy new year! We'll take a look at this in the coming weeks. We do have some code for this already; it just wasn't ready to be published, so we might need to do some merging with that. And just a heads-up, we're quite busy ramping up after the holidays again, so it might take a couple days longer for us to get to this. |
- Add getConsentFlow, acceptConsentRequest, rejectConsentRequest in @ory/nextjs - Add consent page and API routes for app-router, pages-router, custom-components - Display OAuth2 client logo and subtitle on login/registration cards - Add ConsentFooter and custom scope checkbox for custom-components example - Export getConsentNodeKey, isFooterNode, isUiNodeInput, UiNodeInput utilities - Optimize rewriteUrls to single-pass replacement with OAuth2 path exclusion - Add null/undefined handling in rewriteJsonResponse - Add unit tests for consent utilities, card-consent functions, and rewrite
…consent flows Add shared utility getConfigWithOAuth2Logo to override project logo with OAuth2 client logo when available. Apply to Login, Registration, and Consent flows to display the OAuth2 client's logo during OAuth2-initiated flows.
75d637e to
70b3e84
Compare
Add security validation to prevent consent hijacking attacks where an attacker could use a stolen consent_challenge to grant or reject consent on behalf of a different user. Changes: - Pages Router: verify session cookie and compare identity with subject - App Router: add identityId parameter to accept/reject functions - Return 401 for missing session, 403 for identity mismatch
70b3e84 to
73020fc
Compare
|
hi thanks for this contrib @Jorgagu ! this really helps our team to understand more context on the Ory FE stack here |
jonas-jonas
left a comment
There was a problem hiding this comment.
Thank you for this contribution! Overall it looks solid. I do have one note about the missing CSRF protection. Happy to discuss this in more detail, if needed!
| consentChallenge={consentRequest} | ||
| session={session} | ||
| config={config} | ||
| csrfToken="" |
There was a problem hiding this comment.
The endpoints used in this component are susceptible to CSRF attacks. This is also the reason the consent screen is not yet part of the elements examples.
Since the <Consent /> component does not yet support server actions, the CSRF protection must be done through middleware. This is kind of awkward to implement, and there aren't many libraries out there that support this.
Ideally, we'd move the Consent screen to a server actions-based form submission because they're CSRF protected by default, but that would make support for the pages router impossible.
https://www.npmjs.com/package/@edge-csrf/nextjs?activeTab=readme is the best library for this (even though it is unmaintained, and vulnerable to subdomain attacks). Could you integrate that into this change?
There was a problem hiding this comment.
Hey @jonas-jonas, thanks for the review!
A few things worth noting about the current state:
- The consent form submits via
fetch()withContent-Type: application/json(line 161 ofuseOryFormSubmit.ts), so cross-origin CSRF is blocked by CORS preflight. - I've added server-side session identity validation on both routers so the server checks
session.identity.idmatches
consentRequest.subjectbefore processing. - The
csrfToken=""is not validated anywhere server-side though, which is a gap if the submission mechanism ever changes.
Regarding @edge-csrf/nextjs, it uses the Naive Double-Submit Cookie Pattern (vulnerable to subdomain attacks) and the maintainer is looking to hand off the project (last stable release: October 2024).
Here are the options I see:
| Approach | Trade-offs |
|---|---|
@edge-csrf/nextjs |
Subdomain vulnerability, uncertain maintenance |
| Signed Double Submit Cookie (custom) | No dependency, works with both routers, no subdomain vulnerability (OWASP recommended) |
| Custom request header | Simplest with fetch(), breaks if we ever switch to HTML form POST |
| Server Actions | CSRF-protected by default, but kills Pages Router support |
I'd lean toward the Signed Double Submit Cookie. What's your preference?
There was a problem hiding this comment.
Thanks for the investigation.
Signed double submit seems like the best solution. However, we generally try not to reinvent the wheel (unless necessary!), and CSRF seems like a well-solved issue, so ideally we use a library to solve this. "Don't roll your own security" applies here.
However, when I last looked into this, there were few libraries that actually solved this for Next.js specifically. Do you know of any?
There was a problem hiding this comment.
I looked into Next.js CSRF libraries that implement the Signed Double Submit Cookie pattern. Here's what I found:
| Package | Pattern | Version | Latest Release | Monthly Downloads | App Router | Pages Router | Status |
|---|---|---|---|---|---|---|---|
@edge-csrf/nextjs |
Naive Double Submit | v2.5.3 | Nov 2024 | 117,278 | Yes | Yes | Maintainer stepping down, subdomain vulnerability |
next-csrf |
Synchronizer Token (stateful) | v0.2.1 | Apr 2022 | 23,608 | No | Yes | Abandoned |
@csrf-armor/nextjs |
Signed Double Submit (HMAC) | v1.4.1 | Feb 2026 | 694 | Yes | Yes | Active |
@simple-csrf/next |
Signed Double Submit | v0.1.1 | Apr 2025 | 176 | Yes | No | Requires React 19 |
@csrf-armor/nextjs is the only library that checks all the boxes: Signed Double Submit (HMAC), both routers, Edge
Runtime compatible, zero dependencies, TypeScript, and CodeQL analysis. The adoption is low, but the code is auditable
and the core package (@csrf-armor/core) is framework-agnostic, which
could allow building a Nuxt adapter on top of it for the Vue/Nuxt side of ory-elements in the future.
What do you think about going with @csrf-armor/nextjs?
There was a problem hiding this comment.
Thanks for the compilation. I think that's right; let's use @csrf-armor/nextjs here. Would you mind adjusting the examples to use the package?
Add complete OAuth2 consent flow support to
@ory/nextjsand@ory/elements-reactpackages, enabling applications to handle OAuth2 authorization consent screens with Ory Hydra.Related Issue or Design Document
Fixex #327
Add complete OAuth2 consent flow support to
@ory/nextjsand@ory/elements-reactpackages, enabling applications to handle OAuth2 authorization consent screens with Ory Hydra.Features
Consent Flow Utilities (
@ory/nextjs)getConsentFlow- Fetch consent challenge from Ory HydraacceptConsentRequest- Accept consent with selected scopesrejectConsentRequest- Reject consent requestOAuth2 Client Logo Display
getConfigWithOAuth2Logoutility for consistent behaviorExample Implementations
Exported Utilities
getConsentNodeKey,isFooterNodefrom card-consentisUiNodeInput,UiNodeInputtype helpersImprovements
rewriteUrlsto single-pass regex replacementrewriteJsonResponseTests
Checklist
If this pull request addresses a security vulnerability,
I confirm that I got approval (please contact security@ory.sh) from the maintainers to push the changes.
Further comments
This implementation follows the pattern established in kratos-selfservice-ui-node for handling OAuth2 flows. The OAuth2 client logo is displayed by overriding the project configuration's
logo_light_urlwhen an OAuth2 client logo is available, keeping the existingDefaultCardLogocomponent unchanged.