Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add forbidden and unauthorized APIs#72785

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
huozhi merged 7 commits intocanaryfrom11-13-pick_up_conventions
Nov 21, 2024
Merged

Conversation

huozhi
Copy link
Member

@huozhihuozhi commentedNov 14, 2024
edited
Loading

What

Support theforbidden.js andunauthorized.js convention from the app directory and expose two experimental APIforbidden() andunauthorized() in thenext/navigation. Add restriction that it's only working on canary for now.

Why

Providing these 2 new APIs and error boundaries to fill more missing pieces of the navigation story in Next.js.

Our goal is not trying to mirror the HTTP status codes as APIs, it's providing the semantic actions that act as communication channels between the different parts of the system or the different libraries composability, similar concept toSuspense in React.

The set is limited rather than infinite, we're not going to add different boundaries for each HTTP code. These two are the ones coming up so far which are useful after the application is started and allow you to show the restrictions to the resources you're trying to access. On the application side or library side you just learn the curated sets that framework picked for you.

The distinction is valuable. E.g.unauthorized() would likely take you to a login screen, where asforbidden() might be caught but a child boundary and just indicate that part of the screen isn't available to this user. This also gives library flexibility to just call the abstract API and the upper parent or application side can implement the boundary to handle these errors, instead of deletgating everything to the library to decide what to do.

Example

// next.config.jsmodule.exports={experimental:{authInterrupts:true}}
// app/page.jsimport{forbidden}from'next/navigation'exportdefaultfunctionPage(){forbidden()}

ClosesNDX-298

ykzts, gaojude, and controversial reacted with heart emojilaugharn, alvarlagerlof, and gaojude reacted with rocket emoji
@huozhiGraphite App
Copy link
MemberAuthor

huozhi commentedNov 14, 2024
edited
Loading

@ijjk
Copy link
Member

ijjk commentedNov 14, 2024
edited
Loading

Tests Passed

@ijjk
Copy link
Member

ijjk commentedNov 14, 2024
edited
Loading

Stats from current PR

Default Build (Increase detected⚠️)
General Overall increase⚠️
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
buildDuration19.3s16.8sN/A
buildDurationCached15.4s15.5sN/A
nodeModulesSize405 MB405 MB⚠️ +207 kB
nextStartRea..uration (ms)468ms487msN/A
Client Bundles (main, webpack) Overall increase⚠️
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
0b69cffb-HASH.js gzip52.6 kB52.6 kBN/A
1924.HASH.js gzip169 B169 B
195-HASH.js gzip48.4 kB48.8 kB⚠️ +432 B
8589-HASH.js gzip5.27 kB5.28 kBN/A
framework-HASH.js gzip57.4 kB57.4 kBN/A
main-app-HASH.js gzip232 B230 BN/A
main-HASH.js gzip33.1 kB33.1 kBN/A
webpack-HASH.js gzip1.71 kB1.71 kBN/A
Overall change48.5 kB49 kB⚠️ +432 B
Legacy Client Bundles (polyfills)
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
polyfills-HASH.js gzip39.4 kB39.4 kB
Overall change39.4 kB39.4 kB
Client Pages
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
_app-HASH.js gzip193 B193 B
_error-HASH.js gzip192 B190 BN/A
amp-HASH.js gzip510 B510 B
css-HASH.js gzip341 B343 BN/A
dynamic-HASH.js gzip1.84 kB1.84 kBN/A
edge-ssr-HASH.js gzip266 B266 B
head-HASH.js gzip362 B364 BN/A
hooks-HASH.js gzip393 B392 BN/A
image-HASH.js gzip4.42 kB4.42 kBN/A
index-HASH.js gzip268 B268 B
link-HASH.js gzip2.34 kB2.35 kBN/A
routerDirect..HASH.js gzip328 B327 BN/A
script-HASH.js gzip398 B396 BN/A
withRouter-HASH.js gzip325 B322 BN/A
1afbb74e6ecf..834.css gzip106 B106 B
Overall change1.34 kB1.34 kB
Client Build Manifests
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
_buildManifest.js gzip748 B749 BN/A
Overall change0 B0 B
Rendered Page Sizes
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
index.html gzip522 B523 BN/A
link.html gzip537 B537 B
withRouter.html gzip519 B518 BN/A
Overall change537 B537 B
Edge SSR bundle Size Overall increase⚠️
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
edge-ssr.js gzip128 kB128 kBN/A
page.js gzip199 kB200 kB⚠️ +632 B
Overall change199 kB200 kB⚠️ +632 B
Middleware size
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
middleware-b..fest.js gzip670 B668 BN/A
middleware-r..fest.js gzip156 B155 BN/A
middleware.js gzip30.9 kB31 kBN/A
edge-runtime..pack.js gzip844 B844 B
Overall change844 B844 B
Next Runtimes Overall increase⚠️
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
732-experime...dev.js gzip322 B322 B
732.runtime.dev.js gzip314 B314 B
app-page-exp...dev.js gzip322 kB323 kB⚠️ +537 B
app-page-exp..prod.js gzip125 kB125 kB⚠️ +266 B
app-page-tur..prod.js gzip138 kB138 kB⚠️ +267 B
app-page-tur..prod.js gzip133 kB133 kB⚠️ +261 B
app-page.run...dev.js gzip313 kB314 kB⚠️ +541 B
app-page.run..prod.js gzip121 kB121 kB⚠️ +270 B
app-route-ex...dev.js gzip36 kB36.1 kBN/A
app-route-ex..prod.js gzip24.4 kB24.4 kBN/A
app-route-tu..prod.js gzip24.4 kB24.4 kBN/A
app-route-tu..prod.js gzip24.2 kB24.2 kBN/A
app-route.ru...dev.js gzip37.6 kB37.7 kBN/A
app-route.ru..prod.js gzip24.2 kB24.2 kBN/A
pages-api-tu..prod.js gzip9.57 kB9.57 kB
pages-api.ru...dev.js gzip11.4 kB11.4 kB
pages-api.ru..prod.js gzip9.56 kB9.56 kB
pages-turbo...prod.js gzip21 kB21 kB
pages.runtim...dev.js gzip26.6 kB26.6 kB
pages.runtim..prod.js gzip21 kB21 kB
server.runti..prod.js gzip916 kB916 kBN/A
Overall change1.25 MB1.25 MB⚠️ +2.14 kB
build cache Overall increase⚠️
vercel/next.js canaryvercel/next.js 11-13-pick_up_conventionsChange
0.pack gzip2.01 MB2.03 MB⚠️ +14.8 kB
index.pack gzip147 kB148 kB⚠️ +527 B
Overall change2.16 MB2.17 MB⚠️ +15.3 kB
Diff details
Diff forpage.js

Diff too large to display

Diff formiddleware.js

Diff too large to display

Diff foredge-ssr.js

Diff too large to display

Diff forimage-HASH.js
@@ -1,7 +1,7 @@ (self["webpackChunk_N_E"] = self["webpackChunk_N_E"] || []).push([   [2983],   {-    /***/ 6745: /***/ (+    /***/ 7391: /***/ (       __unused_webpack_module,       __unused_webpack_exports,       __webpack_require__@@ -9,7 +9,7 @@       (window.__NEXT_P = window.__NEXT_P || []).push([         "/image",         function () {-          return __webpack_require__(5675);+          return __webpack_require__(1489);         },       ]);       if (false) {@@ -18,7 +18,7 @@       /***/     },-    /***/ 9053: /***/ (module, exports, __webpack_require__) => {+    /***/ 9313: /***/ (module, exports, __webpack_require__) => {       "use strict";       /* __next_internal_client_entry_do_not_use__  cjs */       Object.defineProperty(exports, "__esModule", {@@ -40,17 +40,17 @@         __webpack_require__(6093)       );       const _head = /*#__PURE__*/ _interop_require_default._(-        __webpack_require__(8808)+        __webpack_require__(7964)       );-      const _getimgprops = __webpack_require__(1945);-      const _imageconfig = __webpack_require__(7668);-      const _imageconfigcontextsharedruntime = __webpack_require__(1694);-      const _warnonce = __webpack_require__(1876);-      const _routercontextsharedruntime = __webpack_require__(5575);+      const _getimgprops = __webpack_require__(8821);+      const _imageconfig = __webpack_require__(664);+      const _imageconfigcontextsharedruntime = __webpack_require__(6418);+      const _warnonce = __webpack_require__(7360);+      const _routercontextsharedruntime = __webpack_require__(4203);       const _imageloader = /*#__PURE__*/ _interop_require_default._(-        __webpack_require__(3589)+        __webpack_require__(2489)       );-      const _usemergedref = __webpack_require__(6746);+      const _usemergedref = __webpack_require__(2454);       // This is replaced by webpack define plugin       const configEnv = {         deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],@@ -371,7 +371,7 @@       /***/     },-    /***/ 6746: /***/ (module, exports, __webpack_require__) => {+    /***/ 2454: /***/ (module, exports, __webpack_require__) => {       "use strict";        Object.defineProperty(exports, "__esModule", {@@ -432,7 +432,7 @@       /***/     },-    /***/ 1945: /***/ (+    /***/ 8821: /***/ (       __unused_webpack_module,       exports,       __webpack_require__@@ -448,9 +448,9 @@           return getImgProps;         },       });-      const _warnonce = __webpack_require__(1876);-      const _imageblursvg = __webpack_require__(6704);-      const _imageconfig = __webpack_require__(7668);+      const _warnonce = __webpack_require__(7360);+      const _imageblursvg = __webpack_require__(5884);+      const _imageconfig = __webpack_require__(664);       const VALID_LOADING_VALUES =         /* unused pure expression or super */ null && [           "lazy",@@ -824,7 +824,7 @@       /***/     },-    /***/ 6704: /***/ (__unused_webpack_module, exports) => {+    /***/ 5884: /***/ (__unused_webpack_module, exports) => {       "use strict";       /**        * A shared function, used on both client and server, to generate a SVG blur placeholder.@@ -879,7 +879,7 @@       /***/     },-    /***/ 965: /***/ (+    /***/ 9345: /***/ (       __unused_webpack_module,       exports,       __webpack_require__@@ -906,10 +906,10 @@         },       });       const _interop_require_default = __webpack_require__(1739);-      const _getimgprops = __webpack_require__(1945);-      const _imagecomponent = __webpack_require__(9053);+      const _getimgprops = __webpack_require__(8821);+      const _imagecomponent = __webpack_require__(9313);       const _imageloader = /*#__PURE__*/ _interop_require_default._(-        __webpack_require__(3589)+        __webpack_require__(2489)       );       function getImageProps(imgProps) {         const { props } = (0, _getimgprops.getImgProps)(imgProps, {@@ -941,7 +941,7 @@       /***/     },-    /***/ 3589: /***/ (__unused_webpack_module, exports) => {+    /***/ 2489: /***/ (__unused_webpack_module, exports) => {       "use strict";        Object.defineProperty(exports, "__esModule", {@@ -976,7 +976,7 @@       /***/     },-    /***/ 5675: /***/ (+    /***/ 1489: /***/ (       __unused_webpack_module,       __webpack_exports__,       __webpack_require__@@ -993,8 +993,8 @@        // EXTERNAL MODULE: ./node_modules/.pnpm/react@19.0.0-rc-380f5d67-20241113/node_modules/react/jsx-runtime.js       var jsx_runtime = __webpack_require__(6322);-      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+main-repo+packages+next+next-packed.tgz_react-dom@19.0.0-rc-380f5d67-20241113_re_d7fg766ptstyt4prarg74ol27i/node_modules/next/image.js-      var next_image = __webpack_require__(1695);+      // EXTERNAL MODULE: ./node_modules/.pnpm/next@file+..+diff-repo+packages+next+next-packed.tgz_react-dom@19.0.0-rc-380f5d67-20241113_re_k6jswiqskvoeqe45yhuljotqne/node_modules/next/image.js+      var next_image = __webpack_require__(8106);       var image_default = /*#__PURE__*/ __webpack_require__.n(next_image); // ./pages/nextjs.png       /* harmony default export */ const nextjs = {         src: "/_next/static/media/nextjs.cae0b805.png",@@ -1024,12 +1024,12 @@       /***/     },-    /***/ 1695: /***/ (+    /***/ 8106: /***/ (       module,       __unused_webpack_exports,       __webpack_require__     ) => {-      module.exports = __webpack_require__(965);+      module.exports = __webpack_require__(9345);        /***/     },@@ -1039,7 +1039,7 @@     /******/ var __webpack_exec__ = (moduleId) =>       __webpack_require__((__webpack_require__.s = moduleId));     /******/ __webpack_require__.O(0, [636, 6593, 8792], () =>-      __webpack_exec__(6745)+      __webpack_exec__(7391)     );     /******/ var __webpack_exports__ = __webpack_require__.O();     /******/ _N_E = __webpack_exports__;
Diff for195-HASH.js

Diff too large to display

Diff forapp-page-exp..ntime.dev.js
failed to diff
Diff forapp-page-exp..time.prod.js

Diff too large to display

Diff forapp-page-tur..time.prod.js

Diff too large to display

Diff forapp-page-tur..time.prod.js

Diff too large to display

Diff forapp-page.runtime.dev.js

Diff too large to display

Diff forapp-page.runtime.prod.js

Diff too large to display

Diff forapp-route-ex..ntime.dev.js

Diff too large to display

Diff forapp-route-ex..time.prod.js

Diff too large to display

Diff forapp-route-tu..time.prod.js

Diff too large to display

Diff forapp-route-tu..time.prod.js

Diff too large to display

Diff forapp-route.runtime.dev.js

Diff too large to display

Diff forapp-route.ru..time.prod.js

Diff too large to display

Diff forserver.runtime.prod.js

Diff too large to display

Commit:8c05d12

@simoncave
Copy link

I'mvery happy this is happening. Two notes:

  1. Theunauthorized() function andunauthorized.js file will work really well for simple authentication schemes. If my component sends a request to an API that returns a401 error, I can callunauthorized(), then I can use a globalunauthorized.js boundary file to redirect the user to the login page, or just render the login page directly.

    But for more complex authentication schemes, the401 response could mean one of two things:

    1. Your authentication token is missing or expired — you need to authenticate.
    2. Your authentication token does not have the requiredassurance orscopes for this particular request — you need tostep-up your authentication.1

    In cases where we want to step-up authentication (e.g. by using a stronger form of authentication, like a security key, or approval from a trusted device, or simply reauthenticating for a particular transaction), I would want to be able provide parameters tounauthorized() that are available inunauthorized.js. Ideally, this would be any structured data that can be serialized by React, but even just a single string would suffice.

    Theoretically, there might be cases where you would want to have the same ability to send data along withforbidden() andnotFound() too.

  2. Withunauthorized(),forbidden(), andnotFound(), we have abstractions over HTTP 401, 403, and 404 errors respectively. It begs the question, are there more4xx statuses that might warrant a similar treatment? Would it be useful to be able to callgone() to trigger a410 response that I can catch ingone.js2? And should there be a general abstraction overall 4xx HTTP statuses?

    If the answer to that last question isyes, then I have a proposal...

    Thenext/navigation package would export areject function. For the 3xx HTTP status class, you useredirect/permanentRedirect. For the 4xx HTTP status class, you usereject. It can be called with no arguments, which results in a 400 status. But typically, you will call it with a status code (e.g.reject(401),reject(403),reject(404)).

    Therejected.js boundary file can be added to catch rejection errors (but more specific boundaries likenot-found.js take precedence). It receives the status as a prop.

    Per my first note, it could support an optionaldetail (orcause?) argument as the second parameter (support for serializable structured data would be great, but a string, at least initially, would suffice).

Footnotes

  1. Instinctively, this might seem like a 403 scenario, but the devil is in the details. 403 means"I know you you are, and you're not allowed to do that". 401 typically means"I don't know who you are, so I can't let you do that", but it can also mean"I don't have enough assurance that you are who you say you are to do that".

  2. And logically, should I be able to callimATeapot() to trigger a418 response that I can catch inim-a-teapot.js?

darthmaim, ludwighogstrom, panteliselef, and MartinCura reacted with thumbs up emojiMartinCura reacted with laugh emoji

@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch fromd247e63 toe5ec2deCompareNovember 15, 2024 23:13
@ijjkijjk added create-next-appRelated to our CLI tool for quickly starting a new Next.js application. examplesIssue was opened via the examples template. labelsNov 15, 2024
Base automatically changed from11-13-error_detector tocanaryNovember 18, 2024 10:24
@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch fromf6caf88 to35e1233CompareNovember 18, 2024 10:36
@huozhi
Copy link
MemberAuthor

@simoncave I can answer the 2n question now. Our goal is not trying to mirror the HTTP status codes as APIs, it's providing the semantic actions that act as communication channels between the different parts of the system or the different libraries composability, similar concept toSuspense in React.

The set is limited rather than infinite, we're not going to add different boundaries for each HTTP code. These two are the ones coming up so far which are useful after the application is started and allow you to show the restrictions to the resources you're trying to access. On the application side or library side you just learn the curated sets that framework picked for you.

The distinction is valuable. E.g.unauthorized() would likely take you to a login screen, where asforbidden() might be caught but a child boundary and just indicate that part of the screen isn't available to this user. This also gives library flexibility to just call the abstract API and the upper parent or application side can implement the boundary to handle these errors, instead of deletgating everything to the library to decide what to do.

@huozhihuozhi changed the titlepick up the convention in structureAdd forbidden and unauthorized APIsNov 19, 2024
@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch from917b0bf to91c6b0dCompareNovember 19, 2024 15:09
@huozhihuozhi marked this pull request as ready for reviewNovember 19, 2024 17:23
@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch 2 times, most recently from388f020 tof8437f1CompareNovember 20, 2024 22:33
@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch 2 times, most recently fromcab2e9d to671a3f4CompareNovember 21, 2024 15:36
fix test fixtureimprove testhandle no matched boundary casetest: reorganize the default testupdate metadata error convention (#72834)add element validation and remove unused boundariesuse flagpass down flag everywhere
@huozhihuozhiforce-pushed the11-13-pick_up_conventions branch from671a3f4 to64c3d67CompareNovember 21, 2024 16:13
@huozhihuozhi requested a review fromztannerNovember 21, 2024 18:59
@huozhihuozhi merged commit37f26ac intocanaryNov 21, 2024
108 of 113 checks passed
@huozhihuozhi deleted the 11-13-pick_up_conventions branchNovember 21, 2024 19:59
wyattjoh pushed a commit that referenced this pull requestNov 28, 2024
@github-actionsgithub-actionsbot locked asresolvedand limited conversation to collaboratorsDec 6, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@darthmaimdarthmaimdarthmaim left review comments

@ztannerztannerztanner approved these changes

@gnoffgnoffgnoff approved these changes

Assignees
No one assigned
Labels
create-next-appRelated to our CLI tool for quickly starting a new Next.js application.created-by: Next.js teamPRs by the Next.js team.examplesIssue was opened via the examples template.lockedtestsTurbopackRelated to Turbopack with Next.js.type: next
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

6 participants
@huozhi@ijjk@simoncave@ztanner@darthmaim@gnoff

[8]ページ先頭

©2009-2025 Movatter.jp