[{"data":1,"prerenderedAt":2336},["ShallowReactive",2],{"post-\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie":3,"post-link-en-f47ef865-e311-48e6-ab6e-6f7ee6bde6e8":279},{"id":4,"title":5,"body":6,"contentId":263,"date":264,"description":265,"extension":266,"meta":267,"navigation":268,"path":269,"rawbody":270,"robots":271,"schemaOrg":271,"seo":272,"sitemap":273,"stem":277,"__hash__":278},"posts\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie.md","Build SaaS Apps With Confidence on NuxtHub with Gavarnie",{"type":7,"value":8,"toc":257},"minimark",[9,26,35,42,45,70,84,94,99,102,106,112,119,136,154,159,163,166,173,182,191,200,213,216],[10,11,12],"blockquote",{},[13,14,15,19,20,25],"p",{},[16,17,18],"span",{},"!NOTE","\nThis starter was crafted during my free time. If you find it useful, please consider ",[21,22,24],"a",{"href":23},"\u002Fsponsorship","supporting my work",".",[13,27,28,29],{},"TL;DR: ",[21,30,34],{"href":31,"rel":32},"https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie",[33],"nofollow","Gavarnie",[13,36,37],{},[38,39],"img",{"alt":40,"src":41},"OpenGraph image of Gavarnie project","\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png",[13,43,44],{},"Before exploring this starter, let me share the motivation behind its creation.",[13,46,47,48,53,54,59,60,65,66],{},"Several months ago, ",[21,49,52],{"href":50,"rel":51},"https:\u002F\u002Fgithub.com\u002Fatinux",[33],"Sébastien Chopin",", the creator of Nuxt, introduced ",[21,55,58],{"href":56,"rel":57},"https:\u002F\u002Fhub.nuxt.com",[33],"NuxtHub",", a platform to deploy and manage Nuxt applications, powered by ",[21,61,64],{"href":62,"rel":63},"https:\u002F\u002Fcloudflare.com",[33],"Cloudflare",". Briefly, you can create applications by leveraging Cloudflare infrastructure like D1 (database) and R2 (storage), enabling easy deployment and management. ",[67,68,69],"em",{},"It's akin to Vercel or Netlify but for Nuxt applications, and I find it exceptional.",[13,71,72,73,77,78,83],{},"However, this operates on what's known as ",[74,75,76],"strong",{},"the edge",", which differs from traditional application deployment methods like VPS. The edge uses workers, similar to web workers in your browser, but on the server side. This system allows for cost-effective and speedy deployment, though it comes with several constraints such as ",[21,79,82],{"href":80,"rel":81},"https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F",[33],"available APIs"," and limited compute time. It's a trade-off.",[13,85,86,87,90,91],{},"The edge introduces a new conceptual approach where limitations necessitate innovative solutions. ",[67,88,89],{},"While challenges exist, rest assured, you can still achieve your desired outcomes."," This new landscape requires building a comprehensive ecosystem around it. ",[67,92,93],{},"Do not expect ready-to-use solutions for everything; I'll expand on this in another post.",[10,95,96],{},[13,97,98],{},"Gradually, the edge should be mastered.",[13,100,101],{},"Here is where Gavarnie makes its entrance.",[103,104,34],"h2",{"id":105},"gavarnie",[13,107,108],{},[38,109],{"alt":110,"src":111},"Cirque de Gavarnie by Jean-Christophe BENOIST,\u003Cbr>CC BY 3.0 https:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F3.0, via Wikimedia Commons","\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fgavarnie.jpg",[13,113,114,115,118],{},"Gavarnie is a starter intended for use with NuxtHub, specifically on the Cloudflare infrastructure. ",[67,116,117],{},"If you require compatibility with Supabase, Turso, or Vercel, this may not be the right fit."," It features pre-configured social authentication with multiple providers, email verification, and prioritizes architectural and security best practices.",[13,120,121,122,125,126,129,130,125,133,25],{},"The most important, it's ",[74,123,124],{},"free"," and ",[74,127,128],{},"open-source",", allowing you to commence your SaaS project now or leverage it ",[74,131,132],{},"for learning",[74,134,135],{},"to foster ecosystem development",[13,137,138,139,142,143,146,147,150,151],{},"Indeed, one of my objectives with Gavarnie is not to create just another starter but to further the initiative to advance Nuxt and NuxtHub. I am convinced that by establishing common, robust foundations, we can collectively strengthen the ecosystem surrounding NuxtHub and the edge. Thus, Gavarnie serves as ",[74,140,141],{},"a starting point",", ",[74,144,145],{},"not an end",", and it's up ",[74,148,149],{},"to us to nurture its growth",". ",[67,152,153],{},"Please, join me in enhancing it.",[10,155,156],{},[13,157,158],{},"Gavarnie is a beginning, not a conclusion.",[103,160,162],{"id":161},"features","Features",[13,164,165],{},"Gavarnie is developed on Nuxt and NuxtHub. These two serve as the backbone of this project, with all elements constructed around them.",[13,167,168,169,172],{},"In its current form, ",[21,170,34],{"href":31,"rel":171},[33]," enables users to create an account using their social profiles (GitHub and Twitch), link social accounts, change their email address using a verification mechanism, update their profile and display picture, and delete their account.",[13,174,175,176,181],{},"The email service is facilitated by ",[21,177,180],{"href":178,"rel":179},"https:\u002F\u002Fresend.io",[33],"Resend",", simplifying email transactions.",[13,183,184,185,190],{},"The user interface is powered by ",[21,186,189],{"href":187,"rel":188},"https:\u002F\u002Fui.nuxt.com",[33],"Nuxt UI Pro",", a UI toolkit provided by the Nuxt team for Nuxt applications.",[13,192,193,194,199],{},"Social authentication is offered via the ",[21,195,198],{"href":196,"rel":197},"https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils",[33],"nuxt-auth-utils"," module, which simplifies social authentication in Nuxt. It supports several providers such as Google, Facebook, Twitter, GitHub, among others.",[13,201,202,203,208,209,212],{},"Moreover, I incorporated the ",[21,204,207],{"href":205,"rel":206},"https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity",[33],"Nuxt Security"," module to enhance application security. This starter includes pre-configured CSP, a CSRF ",[67,210,211],{},"which I plan to improve",", and a rate limiter to prevent abuse of sensitive endpoints.",[13,214,215],{},"Gavarnie is a continuation to bring full-stack capabilities to Nuxt applications.",[217,218,220],"callout-read-more",{"id":219},"f47ef865-e311-48e6-ab6e-6f7ee6bde6e8",[217,221,223,231,235,242,248,254],{"id":222},"6df6b96b-58fc-4294-8a38-d209f2b36d19",[13,224,225,226],{},"Explore the ",[227,228,230],"git-hub-link",{"repo":229},"barbapapazes\u002Fgavarnie"," repository to discover more about its features and how to use them.",[103,232,234],{"id":233},"step-by-step","Step by step",[13,236,237,238,241],{},"This marks just the ",[74,239,240],{},"beginning"," of the journey.",[13,243,244,245],{},"Gavarnie is a dynamic project that will evolve. We can envision incorporating better session management, an admin dashboard, an API token system, a billing solution, and more. ",[67,246,247],{},"The opportunities are vast, and it's up to us to drive its development.",[13,249,250,251],{},"Having acquainted yourself with ",[227,252,253],{"repo":229},", feel free to explore it, extend it, experiment, and create practical applications with it.",[13,255,256],{},"Do not hesitate to give a star, start a discussion on the GitHub repository for any questions or suggestions to enhance the starter! 💛",{"title":258,"searchDepth":259,"depth":259,"links":260},"",2,[261,262],{"id":105,"depth":259,"text":34},{"id":161,"depth":259,"text":162},"882ebfc2-25f7-46f3-b829-2d44a25b6f86","2024-08-20","Build and deploy NuxtHub SaaS apps easily with Gavarnie. Offers social login, email verification, is free, and open-source!","md",{},true,"\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","---\ncontentId: 882ebfc2-25f7-46f3-b829-2d44a25b6f86\ntitle: Build SaaS Apps With Confidence on NuxtHub with Gavarnie\ndescription: Build and deploy NuxtHub SaaS apps easily with Gavarnie. Offers social login, email verification, is free, and open-source!\ndate: 2024-08-20\n---\n\n> [!NOTE]\n> This starter was crafted during my free time. If you find it useful, please consider [supporting my work](\u002Fsponsorship).\n\nTL;DR: [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie)\n\n![OpenGraph image of Gavarnie project](\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fog.png)\n\nBefore exploring this starter, let me share the motivation behind its creation.\n\nSeveral months ago, [Sébastien Chopin](https:\u002F\u002Fgithub.com\u002Fatinux), the creator of Nuxt, introduced [NuxtHub](https:\u002F\u002Fhub.nuxt.com), a platform to deploy and manage Nuxt applications, powered by [Cloudflare](https:\u002F\u002Fcloudflare.com). Briefly, you can create applications by leveraging Cloudflare infrastructure like D1 (database) and R2 (storage), enabling easy deployment and management. _It's akin to Vercel or Netlify but for Nuxt applications, and I find it exceptional._\n\nHowever, this operates on what's known as **the edge**, which differs from traditional application deployment methods like VPS. The edge uses workers, similar to web workers in your browser, but on the server side. This system allows for cost-effective and speedy deployment, though it comes with several constraints such as [available APIs](https:\u002F\u002Fdevelopers.cloudflare.com\u002Fworkers\u002Fruntime-apis\u002F) and limited compute time. It's a trade-off.\n\nThe edge introduces a new conceptual approach where limitations necessitate innovative solutions. _While challenges exist, rest assured, you can still achieve your desired outcomes._ This new landscape requires building a comprehensive ecosystem around it. _Do not expect ready-to-use solutions for everything; I'll expand on this in another post._\n\n> Gradually, the edge should be mastered.\n\nHere is where Gavarnie makes its entrance.\n\n## Gavarnie\n\n![Cirque de Gavarnie by Jean-Christophe BENOIST,\u003Cbr>CC BY 3.0 \u003Chttps:\u002F\u002Fcreativecommons.org\u002Flicenses\u002Fby\u002F3.0>, via Wikimedia Commons](\u002Fposts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie\u002Fgavarnie.jpg)\n\nGavarnie is a starter intended for use with NuxtHub, specifically on the Cloudflare infrastructure. _If you require compatibility with Supabase, Turso, or Vercel, this may not be the right fit._ It features pre-configured social authentication with multiple providers, email verification, and prioritizes architectural and security best practices.\n\nThe most important, it's **free** and **open-source**, allowing you to commence your SaaS project now or leverage it **for learning** and **to foster ecosystem development**.\n\nIndeed, one of my objectives with Gavarnie is not to create just another starter but to further the initiative to advance Nuxt and NuxtHub. I am convinced that by establishing common, robust foundations, we can collectively strengthen the ecosystem surrounding NuxtHub and the edge. Thus, Gavarnie serves as **a starting point**, **not an end**, and it's up **to us to nurture its growth**. _Please, join me in enhancing it._\n\n> Gavarnie is a beginning, not a conclusion.\n\n## Features\n\nGavarnie is developed on Nuxt and NuxtHub. These two serve as the backbone of this project, with all elements constructed around them.\n\nIn its current form, [Gavarnie](https:\u002F\u002Fgithub.com\u002Fbarbapapazes\u002Fgavarnie) enables users to create an account using their social profiles (GitHub and Twitch), link social accounts, change their email address using a verification mechanism, update their profile and display picture, and delete their account.\n\nThe email service is facilitated by [Resend](https:\u002F\u002Fresend.io), simplifying email transactions.\n\nThe user interface is powered by [Nuxt UI Pro](https:\u002F\u002Fui.nuxt.com), a UI toolkit provided by the Nuxt team for Nuxt applications.\n\nSocial authentication is offered via the [nuxt-auth-utils](https:\u002F\u002Fgithub.com\u002Fatinux\u002Fnuxt-auth-utils) module, which simplifies social authentication in Nuxt. It supports several providers such as Google, Facebook, Twitter, GitHub, among others.\n\nMoreover, I incorporated the [Nuxt Security](https:\u002F\u002Fnuxt.com\u002Fmodules\u002Fsecurity) module to enhance application security. This starter includes pre-configured CSP, a CSRF _which I plan to improve_, and a rate limiter to prevent abuse of sensitive endpoints.\n\nGavarnie is a continuation to bring full-stack capabilities to Nuxt applications.\n\n\u003CCalloutReadMore id=\"f47ef865-e311-48e6-ab6e-6f7ee6bde6e8\" \u002F>\n\n\u003CCalloutReadMore id=\"6df6b96b-58fc-4294-8a38-d209f2b36d19\" \u002F>\n\nExplore the \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F> repository to discover more about its features and how to use them.\n\n## Step by step\n\nThis marks just the **beginning** of the journey.\n\nGavarnie is a dynamic project that will evolve. We can envision incorporating better session management, an admin dashboard, an API token system, a billing solution, and more. _The opportunities are vast, and it's up to us to drive its development._\n\nHaving acquainted yourself with \u003CGitHubLink repo=\"barbapapazes\u002Fgavarnie\" \u002F>, feel free to explore it, extend it, experiment, and create practical applications with it.\n\nDo not hesitate to give a star, start a discussion on the GitHub repository for any questions or suggestions to enhance the starter! 💛\n",null,{"title":5,"description":265},{"loc":269,"images":274},[275,276],{"loc":41},{"loc":111},"posts\u002Fbuild-saas-apps-with-confidence-on-nutxhub-with-gavarnie","_9-PMLU3hSeuv4S2XM46UGDRRIl6I5vqe3UIx5BqsCA",{"id":280,"title":281,"body":282,"contentId":219,"date":2327,"description":2328,"extension":266,"meta":2329,"navigation":268,"path":2330,"rawbody":2331,"robots":271,"schemaOrg":271,"seo":2332,"sitemap":2333,"stem":2334,"__hash__":2335},"posts\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization.md","Nuxt Going Full-Stack: How to Handle Authorization?",{"type":7,"value":283,"toc":2316},[284,298,313,323,326,329,2312],[13,285,286,287,290,291,294,295,25],{},"When developing an application, it is essential to ",[74,288,289],{},"grant"," or ",[74,292,293],{},"restrict"," access to specific areas or data based on a user's role or permissions. This concept is known as ",[74,296,297],{},"authorization",[13,299,300,301,304,305,308,309,312],{},"Authorization is a critical aspect of any application for ",[74,302,303],{},"security"," purposes. It is imperative to protect ",[74,306,307],{},"sensitive data",", such as a user's email address or password, and prevent the disclosure of ",[74,310,311],{},"private information"," that could compromise user privacy and your business integrity.",[13,314,315,316,319,320,322],{},"To clarify: ",[74,317,318],{},"authentication"," involves verifying a user's identity, whereas ",[74,321,297],{}," involves permitting or denying access to resources based on the user's role or permissions.",[13,324,325],{},"While building Orion, I confronted this challenge and experimented with various approaches. In this article, I'll share several strategies to manage authorization in a NuxtHub application, ranging from the simplest to the most sophisticated methods. It is crucial to understand that the most complex method is not necessarily the best for every application.",[13,327,328],{},"Orion serves as a community-driven collection of templates for your projects, from landing pages to complete web applications. NuxtHub functions as a deployment and management platform for Nuxt, powered by Cloudflare.",[217,330,332,336,343,583,600,611,620,624,634,648,661,737,744,748,762,1002,1011,1082,1092,1095,1099,1106,1112,1476,1479,1496,1499,1519,1525,1531,1539,1543,1554,1561,1569,1574,1580,1583,1590,1606,1609,1613,1623,1629,1702,1705,1758,1761,1768,1774,1870,1885,1992,1995,2002,2009,2142,2153,2166,2178,2275,2280,2284,2293,2296,2299,2309],{"id":331},"c21a371e-abdd-4325-b296-dadcb8deec25",[103,333,335],{"id":334},"the-context","The context",[13,337,338,339,342],{},"Consider this ",[74,340,341],{},"endpoint"," in your Nuxt application:",[344,345,349],"pre",{"className":346,"code":347,"language":348,"meta":258,"style":258},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","export default defineEventHandler(async (event) => {\n  const id = getRouteParam(event, 'id')\n\n  const publication = await db.query.publications.findOne({\n    where: { id },\n    columns: {\n      title: true,\n      content: true,\n      status: true,\n      authorId: true,\n    },\n  })\n\n  return publication\n})\n","ts",[350,351,352,392,427,433,469,486,496,511,523,535,547,553,561,566,575],"code",{"__ignoreMap":258},[16,353,356,360,363,367,371,375,379,383,386,389],{"class":354,"line":355},"line",1,[16,357,359],{"class":358},"s7zQu","export",[16,361,362],{"class":358}," default",[16,364,366],{"class":365},"s2Zo4"," defineEventHandler",[16,368,370],{"class":369},"sTEyZ","(",[16,372,374],{"class":373},"spNyl","async",[16,376,378],{"class":377},"sMK4o"," (",[16,380,382],{"class":381},"sHdIc","event",[16,384,385],{"class":377},")",[16,387,388],{"class":373}," =>",[16,390,391],{"class":377}," {\n",[16,393,394,397,400,403,406,409,411,414,417,421,424],{"class":354,"line":259},[16,395,396],{"class":373},"  const",[16,398,399],{"class":369}," id",[16,401,402],{"class":377}," =",[16,404,405],{"class":365}," getRouteParam",[16,407,370],{"class":408},"swJcz",[16,410,382],{"class":369},[16,412,413],{"class":377},",",[16,415,416],{"class":377}," '",[16,418,420],{"class":419},"sfazB","id",[16,422,423],{"class":377},"'",[16,425,426],{"class":408},")\n",[16,428,430],{"class":354,"line":429},3,[16,431,432],{"emptyLinePlaceholder":268},"\n",[16,434,436,438,441,443,446,449,451,454,456,459,461,464,466],{"class":354,"line":435},4,[16,437,396],{"class":373},[16,439,440],{"class":369}," publication",[16,442,402],{"class":377},[16,444,445],{"class":358}," await",[16,447,448],{"class":369}," db",[16,450,25],{"class":377},[16,452,453],{"class":369},"query",[16,455,25],{"class":377},[16,457,458],{"class":369},"publications",[16,460,25],{"class":377},[16,462,463],{"class":365},"findOne",[16,465,370],{"class":408},[16,467,468],{"class":377},"{\n",[16,470,472,475,478,481,483],{"class":354,"line":471},5,[16,473,474],{"class":408},"    where",[16,476,477],{"class":377},":",[16,479,480],{"class":377}," {",[16,482,399],{"class":369},[16,484,485],{"class":377}," },\n",[16,487,489,492,494],{"class":354,"line":488},6,[16,490,491],{"class":408},"    columns",[16,493,477],{"class":377},[16,495,391],{"class":377},[16,497,499,502,504,508],{"class":354,"line":498},7,[16,500,501],{"class":408},"      title",[16,503,477],{"class":377},[16,505,507],{"class":506},"sfNiH"," true",[16,509,510],{"class":377},",\n",[16,512,514,517,519,521],{"class":354,"line":513},8,[16,515,516],{"class":408},"      content",[16,518,477],{"class":377},[16,520,507],{"class":506},[16,522,510],{"class":377},[16,524,526,529,531,533],{"class":354,"line":525},9,[16,527,528],{"class":408},"      status",[16,530,477],{"class":377},[16,532,507],{"class":506},[16,534,510],{"class":377},[16,536,538,541,543,545],{"class":354,"line":537},10,[16,539,540],{"class":408},"      authorId",[16,542,477],{"class":377},[16,544,507],{"class":506},[16,546,510],{"class":377},[16,548,550],{"class":354,"line":549},11,[16,551,552],{"class":377},"    },\n",[16,554,556,559],{"class":354,"line":555},12,[16,557,558],{"class":377},"  }",[16,560,426],{"class":408},[16,562,564],{"class":354,"line":563},13,[16,565,432],{"emptyLinePlaceholder":268},[16,567,569,572],{"class":354,"line":568},14,[16,570,571],{"class":358},"  return",[16,573,574],{"class":369}," publication\n",[16,576,578,581],{"class":354,"line":577},15,[16,579,580],{"class":377},"}",[16,582,426],{"class":369},[13,584,585,586,589,590,142,593,596,597,25],{},"You can access this endpoint by making a ",[350,587,588],{},"GET \u002Fapi\u002Fpublication\u002F\u003Cid>"," request. This endpoint returns a publication. At first glance, there is no issue with this endpoint. However, a closer examination reveals that a publication has a status that can be ",[350,591,592],{},"draft",[350,594,595],{},"published",", or ",[350,598,599],{},"deleted",[13,601,602,603,606,607,610],{},"The concern is that anyone can access this endpoint and view a ",[74,604,605],{},"draft publication"," or a ",[74,608,609],{},"deleted publication",". Some might be able to view the content of a publication before it is published, which could contain vital information such as a press release or a product launch. Such a scenario could significantly harm your business.",[13,612,613,614,619],{},"This is a security issue that necessitates restricting access to this endpoint, utilizing the ",[21,615,618],{"href":616,"rel":617},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege",[33],"principle of least privilege",". Security is not merely a feature but should be ingrained in the system's design.",[103,621,623],{"id":622},"authenticated-or-not","Authenticated or not",[13,625,626,627,633],{},"The following section is premised on the utilization of the Nuxt ",[21,628,631],{"href":629,"rel":630},"https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils",[33],[74,632,198],{}," package. Nevertheless, its usage is not essential.",[13,635,636,637,639,640,643,644,647],{},"The initial step in securing this ",[74,638,341],{}," is verifying if the user is ",[74,641,642],{},"authenticated",". If the user lacks authentication, it should return a ",[350,645,646],{},"401 Unauthorized"," error.",[13,649,650,651,653,654,657,658,660],{},"Upon exploring the ",[74,652,198],{}," package, I discovered a server utility termed ",[350,655,656],{},"requireUserSession",". This utility can be employed at an endpoint's inception to attempt retrieving the user's session and triggering a ",[350,659,646],{}," error if there is no session, indicating the user is unauthenticated.",[344,662,664],{"className":346,"code":663,"language":348,"meta":258,"style":258},"export default defineEventHandler(async (event) => {\n  await requireUserSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n",[350,665,666,688,702,706,721,725,731],{"__ignoreMap":258},[16,667,668,670,672,674,676,678,680,682,684,686],{"class":354,"line":355},[16,669,359],{"class":358},[16,671,362],{"class":358},[16,673,366],{"class":365},[16,675,370],{"class":369},[16,677,374],{"class":373},[16,679,378],{"class":377},[16,681,382],{"class":381},[16,683,385],{"class":377},[16,685,388],{"class":373},[16,687,391],{"class":377},[16,689,690,693,696,698,700],{"class":354,"line":259},[16,691,692],{"class":358},"  await",[16,694,695],{"class":365}," requireUserSession",[16,697,370],{"class":408},[16,699,382],{"class":369},[16,701,426],{"class":408},[16,703,704],{"class":354,"line":429},[16,705,432],{"emptyLinePlaceholder":268},[16,707,708,710,712,714,717],{"class":354,"line":435},[16,709,396],{"class":373},[16,711,440],{"class":369},[16,713,402],{"class":377},[16,715,716],{"class":377}," {}",[16,718,720],{"class":719},"sHwdD"," \u002F\u002F ...\n",[16,722,723],{"class":354,"line":471},[16,724,432],{"emptyLinePlaceholder":268},[16,726,727,729],{"class":354,"line":488},[16,728,571],{"class":358},[16,730,574],{"class":369},[16,732,733,735],{"class":354,"line":498},[16,734,580],{"class":377},[16,736,426],{"class":369},[13,738,739,740,743],{},"This is the preliminary step towards safeguarding the endpoint against ",[74,741,742],{},"unauthenticated users",". However, if anyone can register an account, they may still access the content of a draft publication. Further action is necessary.",[103,745,747],{"id":746},"only-admins","Only admins",[13,749,750,751,753,754,757,758,761],{},"Building on ",[350,752,656],{},", I can readily devise a new utility named ",[350,755,756],{},"requireAdminSession",". This session will verify if the user is authenticated and has admin rights. If the user is not an admin, a ",[350,759,760],{},"403 Forbidden"," error should be returned. Unauthorized relates to authentication, while Forbidden pertains to authorization.",[344,763,765],{"className":346,"code":764,"language":348,"meta":258,"style":258},"export async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await requireUserSession(event)\n  const { user } = userSession\n\n  if (user.roleType !== 'admin') {\n    throw createError({\n      statusCode: opts.statusCode || 403,\n      message: opts.message || 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n",[350,766,767,840,859,875,879,909,921,944,969,976,981,985,997],{"__ignoreMap":258},[16,768,769,771,774,777,780,782,784,786,790,792,795,797,799,802,805,808,810,813,815,818,821,823,826,829,832,835,838],{"class":354,"line":355},[16,770,359],{"class":358},[16,772,773],{"class":373}," async",[16,775,776],{"class":373}," function",[16,778,779],{"class":365}," requireAdminSession",[16,781,370],{"class":377},[16,783,382],{"class":381},[16,785,477],{"class":377},[16,787,789],{"class":788},"sBMFI"," H3Event",[16,791,413],{"class":377},[16,793,794],{"class":381}," opts",[16,796,477],{"class":377},[16,798,480],{"class":377},[16,800,801],{"class":408}," statusCode",[16,803,804],{"class":377},"?:",[16,806,807],{"class":788}," number",[16,809,413],{"class":377},[16,811,812],{"class":408}," message",[16,814,804],{"class":377},[16,816,817],{"class":788}," string",[16,819,820],{"class":377}," }",[16,822,402],{"class":377},[16,824,825],{"class":377}," {}):",[16,827,828],{"class":788}," Promise",[16,830,831],{"class":377},"\u003C",[16,833,834],{"class":788},"UserSessionRequired",[16,836,837],{"class":377},">",[16,839,391],{"class":377},[16,841,842,844,847,849,851,853,855,857],{"class":354,"line":259},[16,843,396],{"class":373},[16,845,846],{"class":369}," userSession",[16,848,402],{"class":377},[16,850,445],{"class":358},[16,852,695],{"class":365},[16,854,370],{"class":408},[16,856,382],{"class":369},[16,858,426],{"class":408},[16,860,861,863,865,868,870,872],{"class":354,"line":429},[16,862,396],{"class":373},[16,864,480],{"class":377},[16,866,867],{"class":369}," user",[16,869,820],{"class":377},[16,871,402],{"class":377},[16,873,874],{"class":369}," userSession\n",[16,876,877],{"class":354,"line":435},[16,878,432],{"emptyLinePlaceholder":268},[16,880,881,884,886,889,891,894,897,899,902,904,907],{"class":354,"line":471},[16,882,883],{"class":358},"  if",[16,885,378],{"class":408},[16,887,888],{"class":369},"user",[16,890,25],{"class":377},[16,892,893],{"class":369},"roleType",[16,895,896],{"class":377}," !==",[16,898,416],{"class":377},[16,900,901],{"class":419},"admin",[16,903,423],{"class":377},[16,905,906],{"class":408},") ",[16,908,468],{"class":377},[16,910,911,914,917,919],{"class":354,"line":488},[16,912,913],{"class":358},"    throw",[16,915,916],{"class":365}," createError",[16,918,370],{"class":408},[16,920,468],{"class":377},[16,922,923,926,928,930,932,935,938,942],{"class":354,"line":498},[16,924,925],{"class":408},"      statusCode",[16,927,477],{"class":377},[16,929,794],{"class":369},[16,931,25],{"class":377},[16,933,934],{"class":369},"statusCode",[16,936,937],{"class":377}," ||",[16,939,941],{"class":940},"sbssI"," 403",[16,943,510],{"class":377},[16,945,946,949,951,953,955,958,960,962,965,967],{"class":354,"line":513},[16,947,948],{"class":408},"      message",[16,950,477],{"class":377},[16,952,794],{"class":369},[16,954,25],{"class":377},[16,956,957],{"class":369},"message",[16,959,937],{"class":377},[16,961,416],{"class":377},[16,963,964],{"class":419},"Unauthorized",[16,966,423],{"class":377},[16,968,510],{"class":377},[16,970,971,974],{"class":354,"line":525},[16,972,973],{"class":377},"    }",[16,975,426],{"class":408},[16,977,978],{"class":354,"line":537},[16,979,980],{"class":377},"  }\n",[16,982,983],{"class":354,"line":549},[16,984,432],{"emptyLinePlaceholder":268},[16,986,987,989,991,994],{"class":354,"line":555},[16,988,571],{"class":358},[16,990,846],{"class":369},[16,992,993],{"class":358}," as",[16,995,996],{"class":788}," UserSessionRequired\n",[16,998,999],{"class":354,"line":563},[16,1000,1001],{"class":377},"}\n",[13,1003,1004,1005,1007,1008,1010],{},"Subsequently, ",[350,1006,656],{}," can be replaced with ",[350,1009,756],{}," in the endpoint.",[344,1012,1014],{"className":346,"code":1013,"language":348,"meta":258,"style":258},"export default defineEventHandler(async (event) => {\n  await requireAdminSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n",[350,1015,1016,1038,1050,1054,1066,1070,1076],{"__ignoreMap":258},[16,1017,1018,1020,1022,1024,1026,1028,1030,1032,1034,1036],{"class":354,"line":355},[16,1019,359],{"class":358},[16,1021,362],{"class":358},[16,1023,366],{"class":365},[16,1025,370],{"class":369},[16,1027,374],{"class":373},[16,1029,378],{"class":377},[16,1031,382],{"class":381},[16,1033,385],{"class":377},[16,1035,388],{"class":373},[16,1037,391],{"class":377},[16,1039,1040,1042,1044,1046,1048],{"class":354,"line":259},[16,1041,692],{"class":358},[16,1043,779],{"class":365},[16,1045,370],{"class":408},[16,1047,382],{"class":369},[16,1049,426],{"class":408},[16,1051,1052],{"class":354,"line":429},[16,1053,432],{"emptyLinePlaceholder":268},[16,1055,1056,1058,1060,1062,1064],{"class":354,"line":435},[16,1057,396],{"class":373},[16,1059,440],{"class":369},[16,1061,402],{"class":377},[16,1063,716],{"class":377},[16,1065,720],{"class":719},[16,1067,1068],{"class":354,"line":471},[16,1069,432],{"emptyLinePlaceholder":268},[16,1071,1072,1074],{"class":354,"line":488},[16,1073,571],{"class":358},[16,1075,574],{"class":369},[16,1077,1078,1080],{"class":354,"line":498},[16,1079,580],{"class":377},[16,1081,426],{"class":369},[13,1083,1084,1085,1088,1089,1091],{},"Now, only ",[74,1086,1087],{},"admins",", users assigned the ",[350,1090,901],{}," role, can access a publication's content.",[13,1093,1094],{},"However, this approach is not entirely effective, as if the publication is published, I wish to permit all users to access it. Currently, only admins can view the content of a publication, irrespective of the publication's status.",[103,1096,1098],{"id":1097},"issues-with-these-approaches","Issues with these approaches",[13,1100,1101,1102,1105],{},"The aforementioned strategies lack sufficient flexibility for a detailed ",[74,1103,1104],{},"authorization system",". They cannot accommodate situations where the author of the publication should have access, even if the publication is unpublished.",[13,1107,1108,1109,477],{},"To address this, I could draft another utility termed ",[350,1110,1111],{},"requirePublicationAccess",[344,1113,1115],{"className":346,"code":1114,"language":348,"meta":258,"style":258},"export async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n  const { user } = userSession\n\n  if (publication.status === 'published')\n    return userSession\n\n  if (!userSession) {\n    throw createError({\n      statusCode: opts.statusCode || 401,\n      message: opts.message || 'Unauthorized',\n    })\n  }\n\n  if (publication.authorId === user.id)\n    return userSession\n\n  if (user.roleType === 'admin')\n    return userSession\n\n  throw createError({\n    statusCode: opts.statusCode || 403,\n    message: opts.message || 'Forbidden',\n  })\n}\n",[350,1116,1117,1183,1202,1216,1220,1245,1252,1256,1272,1282,1301,1323,1329,1333,1337,1360,1367,1372,1395,1402,1407,1419,1439,1464,1471],{"__ignoreMap":258},[16,1118,1119,1121,1123,1125,1128,1130,1132,1134,1136,1138,1140,1142,1145,1147,1149,1151,1153,1155,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181],{"class":354,"line":355},[16,1120,359],{"class":358},[16,1122,773],{"class":373},[16,1124,776],{"class":373},[16,1126,1127],{"class":365}," requirePublicationAccess",[16,1129,370],{"class":377},[16,1131,382],{"class":381},[16,1133,477],{"class":377},[16,1135,789],{"class":788},[16,1137,413],{"class":377},[16,1139,440],{"class":381},[16,1141,477],{"class":377},[16,1143,1144],{"class":788}," Publication",[16,1146,413],{"class":377},[16,1148,794],{"class":381},[16,1150,477],{"class":377},[16,1152,480],{"class":377},[16,1154,801],{"class":408},[16,1156,804],{"class":377},[16,1158,807],{"class":788},[16,1160,413],{"class":377},[16,1162,812],{"class":408},[16,1164,804],{"class":377},[16,1166,817],{"class":788},[16,1168,820],{"class":377},[16,1170,402],{"class":377},[16,1172,825],{"class":377},[16,1174,828],{"class":788},[16,1176,831],{"class":377},[16,1178,834],{"class":788},[16,1180,837],{"class":377},[16,1182,391],{"class":377},[16,1184,1185,1187,1189,1191,1193,1196,1198,1200],{"class":354,"line":259},[16,1186,396],{"class":373},[16,1188,846],{"class":369},[16,1190,402],{"class":377},[16,1192,445],{"class":358},[16,1194,1195],{"class":365}," getUserSession",[16,1197,370],{"class":408},[16,1199,382],{"class":369},[16,1201,426],{"class":408},[16,1203,1204,1206,1208,1210,1212,1214],{"class":354,"line":429},[16,1205,396],{"class":373},[16,1207,480],{"class":377},[16,1209,867],{"class":369},[16,1211,820],{"class":377},[16,1213,402],{"class":377},[16,1215,874],{"class":369},[16,1217,1218],{"class":354,"line":435},[16,1219,432],{"emptyLinePlaceholder":268},[16,1221,1222,1224,1226,1229,1231,1234,1237,1239,1241,1243],{"class":354,"line":471},[16,1223,883],{"class":358},[16,1225,378],{"class":408},[16,1227,1228],{"class":369},"publication",[16,1230,25],{"class":377},[16,1232,1233],{"class":369},"status",[16,1235,1236],{"class":377}," ===",[16,1238,416],{"class":377},[16,1240,595],{"class":419},[16,1242,423],{"class":377},[16,1244,426],{"class":408},[16,1246,1247,1250],{"class":354,"line":488},[16,1248,1249],{"class":358},"    return",[16,1251,874],{"class":369},[16,1253,1254],{"class":354,"line":498},[16,1255,432],{"emptyLinePlaceholder":268},[16,1257,1258,1260,1262,1265,1268,1270],{"class":354,"line":513},[16,1259,883],{"class":358},[16,1261,378],{"class":408},[16,1263,1264],{"class":377},"!",[16,1266,1267],{"class":369},"userSession",[16,1269,906],{"class":408},[16,1271,468],{"class":377},[16,1273,1274,1276,1278,1280],{"class":354,"line":525},[16,1275,913],{"class":358},[16,1277,916],{"class":365},[16,1279,370],{"class":408},[16,1281,468],{"class":377},[16,1283,1284,1286,1288,1290,1292,1294,1296,1299],{"class":354,"line":537},[16,1285,925],{"class":408},[16,1287,477],{"class":377},[16,1289,794],{"class":369},[16,1291,25],{"class":377},[16,1293,934],{"class":369},[16,1295,937],{"class":377},[16,1297,1298],{"class":940}," 401",[16,1300,510],{"class":377},[16,1302,1303,1305,1307,1309,1311,1313,1315,1317,1319,1321],{"class":354,"line":549},[16,1304,948],{"class":408},[16,1306,477],{"class":377},[16,1308,794],{"class":369},[16,1310,25],{"class":377},[16,1312,957],{"class":369},[16,1314,937],{"class":377},[16,1316,416],{"class":377},[16,1318,964],{"class":419},[16,1320,423],{"class":377},[16,1322,510],{"class":377},[16,1324,1325,1327],{"class":354,"line":555},[16,1326,973],{"class":377},[16,1328,426],{"class":408},[16,1330,1331],{"class":354,"line":563},[16,1332,980],{"class":377},[16,1334,1335],{"class":354,"line":568},[16,1336,432],{"emptyLinePlaceholder":268},[16,1338,1339,1341,1343,1345,1347,1350,1352,1354,1356,1358],{"class":354,"line":577},[16,1340,883],{"class":358},[16,1342,378],{"class":408},[16,1344,1228],{"class":369},[16,1346,25],{"class":377},[16,1348,1349],{"class":369},"authorId",[16,1351,1236],{"class":377},[16,1353,867],{"class":369},[16,1355,25],{"class":377},[16,1357,420],{"class":369},[16,1359,426],{"class":408},[16,1361,1363,1365],{"class":354,"line":1362},16,[16,1364,1249],{"class":358},[16,1366,874],{"class":369},[16,1368,1370],{"class":354,"line":1369},17,[16,1371,432],{"emptyLinePlaceholder":268},[16,1373,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393],{"class":354,"line":1374},18,[16,1376,883],{"class":358},[16,1378,378],{"class":408},[16,1380,888],{"class":369},[16,1382,25],{"class":377},[16,1384,893],{"class":369},[16,1386,1236],{"class":377},[16,1388,416],{"class":377},[16,1390,901],{"class":419},[16,1392,423],{"class":377},[16,1394,426],{"class":408},[16,1396,1398,1400],{"class":354,"line":1397},19,[16,1399,1249],{"class":358},[16,1401,874],{"class":369},[16,1403,1405],{"class":354,"line":1404},20,[16,1406,432],{"emptyLinePlaceholder":268},[16,1408,1410,1413,1415,1417],{"class":354,"line":1409},21,[16,1411,1412],{"class":358},"  throw",[16,1414,916],{"class":365},[16,1416,370],{"class":408},[16,1418,468],{"class":377},[16,1420,1422,1425,1427,1429,1431,1433,1435,1437],{"class":354,"line":1421},22,[16,1423,1424],{"class":408},"    statusCode",[16,1426,477],{"class":377},[16,1428,794],{"class":369},[16,1430,25],{"class":377},[16,1432,934],{"class":369},[16,1434,937],{"class":377},[16,1436,941],{"class":940},[16,1438,510],{"class":377},[16,1440,1442,1445,1447,1449,1451,1453,1455,1457,1460,1462],{"class":354,"line":1441},23,[16,1443,1444],{"class":408},"    message",[16,1446,477],{"class":377},[16,1448,794],{"class":369},[16,1450,25],{"class":377},[16,1452,957],{"class":369},[16,1454,937],{"class":377},[16,1456,416],{"class":377},[16,1458,1459],{"class":419},"Forbidden",[16,1461,423],{"class":377},[16,1463,510],{"class":377},[16,1465,1467,1469],{"class":354,"line":1466},24,[16,1468,558],{"class":377},[16,1470,426],{"class":408},[16,1472,1474],{"class":354,"line":1473},25,[16,1475,1001],{"class":377},[13,1477,1478],{},"This solution is adequate until I need to manage additional endpoints. This approach will result in excessive code duplication concerning error handling. Additionally, the code becomes more challenging to test due to the necessity of managing an entire H3 event.",[13,1480,1481,1482,1485,1486,1490,1491,125,1493,25],{},"Without a superior approach, I continue developing Orion but soon encountered another challenge: I need to implement the same ",[74,1483,1484],{},"authorization logic on the client side",". I do not want to show an edit button to a user who is neither the author of the publication nor an admin, and I do not wish to hide this edit button from the admin if I decide to permit publication editing on the server. Similar to ",[21,1487,1489],{"href":1488},".\u002Fnuxt-going-full-stack-how-to-streamline-form-validation","forms validation",", duplicating logic can result in substantial inconsistencies between the client and the server, leading to customer frustration. The utilities I have created thus far are solely server-side due to the use of ",[350,1492,656],{},[350,1494,1495],{},"getUserSession",[13,1497,1498],{},"Let's take a step back to comprehend how this issue might be resolved. Authorization consists of three components:",[1500,1501,1502,1509,1514],"ul",{},[1503,1504,1505,1508],"li",{},[74,1506,1507],{},"allow"," access to a resource",[1503,1510,1511,1508],{},[74,1512,1513],{},"deny",[1503,1515,1516,1508],{},[74,1517,1518],{},"authorize",[13,1520,1521,1522,1524],{},"The first two are straightforward conditions yielding a boolean response to questions like \"Can I access this publication? Yes or No\". The developer must manually handle this response. The third one grants access to the resource. For the question \"Can I access this publication?\", there is neither a \"yes\" nor \"no\"; nothing happens if the answer is \"yes\", but an error is raised if it is \"no\". The ",[350,1523,1518],{}," sends an error automatically.",[13,1526,1527,1528,25],{},"These elements hold no direct relation to the client, server, or the framework in use or the authentication system. Armed with this knowledge, I began working on a local module for Orion to manage authorization more flexibly. After several days of effort, I am proud to introduce ",[74,1529,1530],{},"nuxt-authorization",[13,1532,1533,1534,25],{},"Initially, I aimed to develop both a Nitro and a Nuxt package, but Nitro modules are not ready yet. Nonetheless, I submitted some pull requests to the Nitro repository to enhance the module system. One of these is the ",[21,1535,1538],{"href":1536,"rel":1537},"https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508",[33],"module author guide",[103,1540,1542],{"id":1541},"introducing-nuxt-authorization","Introducing nuxt-authorization",[13,1544,1545,1546,1548,1549,25],{},"This module offers a simple yet powerful method for managing ",[74,1547,297],{}," in a Nuxt application, applicable to both client and server. It is authentication system agnostic but can be seamlessly integrated with ",[21,1550,1552],{"href":629,"rel":1551},[33],[74,1553,198],{},[13,1555,1556,1557],{},"For instructions on its usage, visit the GitHub repository: ",[227,1558,1560],{"repo":1559},"barbapapazes\u002Fnuxt-authorization",". To delve into the design and implementation, continue reading.",[13,1562,1563,1564,25],{},"The module remains in its early stages of development, and if you have any feedback or suggestions for improvement, feel free to open an issue on the GitHub repository or contact me on ",[21,1565,1568],{"href":1566,"rel":1567},"https:\u002F\u002Fx.com\u002Fbarbapapazes",[33],"X",[1570,1571,1573],"h3",{"id":1572},"problems-i-want-to-solve","Problems I want to solve",[13,1575,1576,1577,1579],{},"Before describing what has been accomplished with ",[74,1578,1530],{},", let's examine the problems I intended to solve.",[13,1581,1582],{},"Much like forms validation, I aim to maintain a single source of truth and a single API for both the app and server. This is crucial to avoid discrepancies between the app and server.",[13,1584,1585,1586,1589],{},"I also seek the ability to define ",[74,1587,1588],{},"authorization logic"," simply and grouped by resource. This approach enhances readability, maintainability, and consistency across various application aspects, such as forms validation.",[13,1591,1592,1593,290,1595,1597,1598,1600,1601,1603,1604,25],{},"The authorization logic must be adaptable enough to accommodate a varied range of use cases, from ",[350,1594,1507],{},[350,1596,1513],{}," conditions to more complex ",[350,1599,1518],{}," conditions. The errors produced by the ",[350,1602,1518],{}," condition should be customizable to enhance user experience. Not all unauthorized errors need to be a ",[350,1605,760],{},[13,1607,1608],{},"Finally, I desire components that ease integrating authorization logic into templates. For instance, I want the ability to show or hide a button based on authorization logic.",[1570,1610,1612],{"id":1611},"design","Design",[13,1614,1615,1616,1619,1620,25],{},"The module comprises two components: the ",[74,1617,1618],{},"ability"," and the ",[74,1621,1622],{},"bouncer",[13,1624,1625,1626,1628],{},"Abilities constitute the rules that embody the ",[74,1627,1588],{},". They take a user and one or more resources and yield a deny or an allow condition. While they can be grouped by resource, they remain independent of one another.",[344,1630,1632],{"className":346,"code":1631,"language":348,"meta":258,"style":258},"export const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n",[350,1633,1634,1677,1696],{"__ignoreMap":258},[16,1635,1636,1638,1641,1644,1647,1650,1652,1654,1656,1658,1661,1663,1666,1668,1671,1673,1675],{"class":354,"line":355},[16,1637,359],{"class":358},[16,1639,1640],{"class":373}," const",[16,1642,1643],{"class":369}," editBook ",[16,1645,1646],{"class":377},"=",[16,1648,1649],{"class":365}," defineAbility",[16,1651,370],{"class":369},[16,1653,370],{"class":377},[16,1655,888],{"class":381},[16,1657,477],{"class":377},[16,1659,1660],{"class":788}," User",[16,1662,413],{"class":377},[16,1664,1665],{"class":381}," book",[16,1667,477],{"class":377},[16,1669,1670],{"class":788}," Book",[16,1672,385],{"class":377},[16,1674,388],{"class":373},[16,1676,391],{"class":377},[16,1678,1679,1681,1683,1685,1687,1689,1691,1693],{"class":354,"line":259},[16,1680,571],{"class":358},[16,1682,867],{"class":369},[16,1684,25],{"class":377},[16,1686,420],{"class":369},[16,1688,1236],{"class":377},[16,1690,1665],{"class":369},[16,1692,25],{"class":377},[16,1694,1695],{"class":369},"authorId\n",[16,1697,1698,1700],{"class":354,"line":429},[16,1699,580],{"class":377},[16,1701,426],{"class":369},[13,1703,1704],{},"In the above example, only the author of a book is allowed to edit it. By default, abilities are executed only if the user is authenticated, but it is possible to permit guest access to certain resources.",[344,1706,1708],{"className":346,"code":1707,"language":348,"meta":258,"style":258},"export const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n",[350,1709,1710,1745,1752],{"__ignoreMap":258},[16,1711,1712,1714,1716,1719,1721,1723,1725,1727,1729,1731,1733,1736,1739,1741,1743],{"class":354,"line":355},[16,1713,359],{"class":358},[16,1715,1640],{"class":373},[16,1717,1718],{"class":369}," listBooks ",[16,1720,1646],{"class":377},[16,1722,1649],{"class":365},[16,1724,370],{"class":369},[16,1726,370],{"class":377},[16,1728,888],{"class":381},[16,1730,477],{"class":377},[16,1732,1660],{"class":788},[16,1734,1735],{"class":377}," |",[16,1737,1738],{"class":788}," null",[16,1740,385],{"class":377},[16,1742,388],{"class":373},[16,1744,391],{"class":377},[16,1746,1747,1749],{"class":354,"line":259},[16,1748,571],{"class":358},[16,1750,1751],{"class":506}," true\n",[16,1753,1754,1756],{"class":354,"line":429},[16,1755,580],{"class":377},[16,1757,426],{"class":369},[13,1759,1760],{},"In this instance, everyone, including unauthenticated users, is permitted to list books.",[13,1762,1763,1764,1767],{},"The ",[350,1765,1766],{},"defineAbility"," function acts as a factory that creates an ability but nothing beyond that. It needs to be used with bouncer functions.",[13,1769,1770,1771,1773],{},"In a server endpoint, the ",[350,1772,1518],{}," function can be employed to grant access to a resource based on the abilities.",[344,1775,1777],{"className":346,"code":1776,"language":348,"meta":258,"style":258},"export default defineEventHandler(async (event) => {\n  await authorize(event, listBooks)\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n",[350,1778,1779,1801,1819,1823,1853,1857,1864],{"__ignoreMap":258},[16,1780,1781,1783,1785,1787,1789,1791,1793,1795,1797,1799],{"class":354,"line":355},[16,1782,359],{"class":358},[16,1784,362],{"class":358},[16,1786,366],{"class":365},[16,1788,370],{"class":369},[16,1790,374],{"class":373},[16,1792,378],{"class":377},[16,1794,382],{"class":381},[16,1796,385],{"class":377},[16,1798,388],{"class":373},[16,1800,391],{"class":377},[16,1802,1803,1805,1808,1810,1812,1814,1817],{"class":354,"line":259},[16,1804,692],{"class":358},[16,1806,1807],{"class":365}," authorize",[16,1809,370],{"class":408},[16,1811,382],{"class":369},[16,1813,413],{"class":377},[16,1815,1816],{"class":369}," listBooks",[16,1818,426],{"class":408},[16,1820,1821],{"class":354,"line":429},[16,1822,432],{"emptyLinePlaceholder":268},[16,1824,1825,1827,1830,1832,1834,1836,1838,1840,1842,1845,1847,1850],{"class":354,"line":435},[16,1826,396],{"class":373},[16,1828,1829],{"class":369}," books",[16,1831,402],{"class":377},[16,1833,445],{"class":358},[16,1835,448],{"class":369},[16,1837,25],{"class":377},[16,1839,453],{"class":369},[16,1841,25],{"class":377},[16,1843,1844],{"class":369},"books",[16,1846,25],{"class":377},[16,1848,1849],{"class":365},"findMany",[16,1851,1852],{"class":408},"()\n",[16,1854,1855],{"class":354,"line":471},[16,1856,432],{"emptyLinePlaceholder":268},[16,1858,1859,1861],{"class":354,"line":488},[16,1860,571],{"class":358},[16,1862,1863],{"class":369}," books\n",[16,1865,1866,1868],{"class":354,"line":498},[16,1867,580],{"class":377},[16,1869,426],{"class":369},[13,1871,1763,1872,1874,1875,1877,1878,290,1881,1884],{},[350,1873,1518],{}," function will trigger a ",[350,1876,760],{}," error if the user is not permitted to list books. More granular control can be achieved using the ",[350,1879,1880],{},"allows",[350,1882,1883],{},"denies"," functions.",[344,1886,1888],{"className":346,"code":1887,"language":348,"meta":258,"style":258},"export default defineEventHandler(async (event) => {\n  if (await denies(event, listBooks, book))\n    return []\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n",[350,1889,1890,1912,1939,1946,1950,1976,1980,1986],{"__ignoreMap":258},[16,1891,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910],{"class":354,"line":355},[16,1893,359],{"class":358},[16,1895,362],{"class":358},[16,1897,366],{"class":365},[16,1899,370],{"class":369},[16,1901,374],{"class":373},[16,1903,378],{"class":377},[16,1905,382],{"class":381},[16,1907,385],{"class":377},[16,1909,388],{"class":373},[16,1911,391],{"class":377},[16,1913,1914,1916,1918,1921,1924,1926,1928,1930,1932,1934,1936],{"class":354,"line":259},[16,1915,883],{"class":358},[16,1917,378],{"class":408},[16,1919,1920],{"class":358},"await",[16,1922,1923],{"class":365}," denies",[16,1925,370],{"class":408},[16,1927,382],{"class":369},[16,1929,413],{"class":377},[16,1931,1816],{"class":369},[16,1933,413],{"class":377},[16,1935,1665],{"class":369},[16,1937,1938],{"class":408},"))\n",[16,1940,1941,1943],{"class":354,"line":429},[16,1942,1249],{"class":358},[16,1944,1945],{"class":408}," []\n",[16,1947,1948],{"class":354,"line":435},[16,1949,432],{"emptyLinePlaceholder":268},[16,1951,1952,1954,1956,1958,1960,1962,1964,1966,1968,1970,1972,1974],{"class":354,"line":471},[16,1953,396],{"class":373},[16,1955,1829],{"class":369},[16,1957,402],{"class":377},[16,1959,445],{"class":358},[16,1961,448],{"class":369},[16,1963,25],{"class":377},[16,1965,453],{"class":369},[16,1967,25],{"class":377},[16,1969,1844],{"class":369},[16,1971,25],{"class":377},[16,1973,1849],{"class":365},[16,1975,1852],{"class":408},[16,1977,1978],{"class":354,"line":488},[16,1979,432],{"emptyLinePlaceholder":268},[16,1981,1982,1984],{"class":354,"line":498},[16,1983,571],{"class":358},[16,1985,1863],{"class":369},[16,1987,1988,1990],{"class":354,"line":513},[16,1989,580],{"class":377},[16,1991,426],{"class":369},[13,1993,1994],{},"Instead of triggering an error, an empty array is returned if the user is not allowed to list books. The appropriate method for handling unauthorized access will depend on your business logic.",[13,1996,1997,1998,2001],{},"For example, returning a ",[350,1999,2000],{},"404 Not Found"," error if the user is not authorized to access a specific resource can preserve data privacy. Disclosing the existence of the resource could pose a security risk.",[13,2003,2004,2005,125,2007,1884],{},"This customization is attainable within the abilities through the ",[350,2006,1507],{},[350,2008,1513],{},[344,2010,2012],{"className":346,"code":2011,"language":348,"meta":258,"style":258},"export const viewBook = defineAbility((user: User, book: Book) => {\n  if (book.status === 'draft') {\n    return deny({\n      statusCode: 404,\n      message: 'Not Found',\n    })\n  }\n\n  return allow()\n})\n",[350,2013,2014,2051,2076,2087,2098,2113,2119,2123,2127,2136],{"__ignoreMap":258},[16,2015,2016,2018,2020,2023,2025,2027,2029,2031,2033,2035,2037,2039,2041,2043,2045,2047,2049],{"class":354,"line":355},[16,2017,359],{"class":358},[16,2019,1640],{"class":373},[16,2021,2022],{"class":369}," viewBook ",[16,2024,1646],{"class":377},[16,2026,1649],{"class":365},[16,2028,370],{"class":369},[16,2030,370],{"class":377},[16,2032,888],{"class":381},[16,2034,477],{"class":377},[16,2036,1660],{"class":788},[16,2038,413],{"class":377},[16,2040,1665],{"class":381},[16,2042,477],{"class":377},[16,2044,1670],{"class":788},[16,2046,385],{"class":377},[16,2048,388],{"class":373},[16,2050,391],{"class":377},[16,2052,2053,2055,2057,2060,2062,2064,2066,2068,2070,2072,2074],{"class":354,"line":259},[16,2054,883],{"class":358},[16,2056,378],{"class":408},[16,2058,2059],{"class":369},"book",[16,2061,25],{"class":377},[16,2063,1233],{"class":369},[16,2065,1236],{"class":377},[16,2067,416],{"class":377},[16,2069,592],{"class":419},[16,2071,423],{"class":377},[16,2073,906],{"class":408},[16,2075,468],{"class":377},[16,2077,2078,2080,2083,2085],{"class":354,"line":429},[16,2079,1249],{"class":358},[16,2081,2082],{"class":365}," deny",[16,2084,370],{"class":408},[16,2086,468],{"class":377},[16,2088,2089,2091,2093,2096],{"class":354,"line":435},[16,2090,925],{"class":408},[16,2092,477],{"class":377},[16,2094,2095],{"class":940}," 404",[16,2097,510],{"class":377},[16,2099,2100,2102,2104,2106,2109,2111],{"class":354,"line":471},[16,2101,948],{"class":408},[16,2103,477],{"class":377},[16,2105,416],{"class":377},[16,2107,2108],{"class":419},"Not Found",[16,2110,423],{"class":377},[16,2112,510],{"class":377},[16,2114,2115,2117],{"class":354,"line":488},[16,2116,973],{"class":377},[16,2118,426],{"class":408},[16,2120,2121],{"class":354,"line":498},[16,2122,980],{"class":377},[16,2124,2125],{"class":354,"line":513},[16,2126,432],{"emptyLinePlaceholder":268},[16,2128,2129,2131,2134],{"class":354,"line":525},[16,2130,571],{"class":358},[16,2132,2133],{"class":365}," allow",[16,2135,1852],{"class":408},[16,2137,2138,2140],{"class":354,"line":537},[16,2139,580],{"class":377},[16,2141,426],{"class":369},[13,2143,2144,2145,2147,2148,2150,2151,25],{},"Now, the ",[350,2146,1518],{}," function will throw a ",[350,2149,2000],{}," error if the user is not permitted to view a draft book, rather than the default ",[350,2152,760],{},[13,2154,2155,2156,142,2158,2160,2161,2163,2164,25],{},"On the client side, the ",[350,2157,1880],{},[350,2159,1883],{},", and ",[350,2162,1518],{}," functions can also be utilized to administer the ",[74,2165,1588],{},[13,2167,2168,2169,125,2172,2175,2176,25],{},"In addition, these functions offer two components: ",[350,2170,2171],{},"Can",[350,2173,2174],{},"Cannot",". These components enable portions of a template to be shown or hidden based on the ",[74,2177,1588],{},[344,2179,2183],{"className":2180,"code":2181,"language":2182,"meta":258,"style":258},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Edit\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n","vue",[350,2184,2185,2195,2238,2258,2267],{"__ignoreMap":258},[16,2186,2187,2189,2192],{"class":354,"line":355},[16,2188,831],{"class":377},[16,2190,2191],{"class":408},"template",[16,2193,2194],{"class":377},">\n",[16,2196,2197,2200,2202,2205,2207,2209,2212,2215,2217,2219,2222,2224,2226,2229,2231,2234,2236],{"class":354,"line":259},[16,2198,2199],{"class":377},"  \u003C",[16,2201,2171],{"class":408},[16,2203,2204],{"class":377}," :",[16,2206,1618],{"class":373},[16,2208,1646],{"class":377},[16,2210,2211],{"class":377},"\"",[16,2213,2214],{"class":369},"editBook",[16,2216,2211],{"class":377},[16,2218,2204],{"class":377},[16,2220,2221],{"class":373},"args",[16,2223,1646],{"class":377},[16,2225,2211],{"class":377},[16,2227,2228],{"class":377},"[",[16,2230,2059],{"class":369},[16,2232,2233],{"class":377},"]",[16,2235,2211],{"class":377},[16,2237,2194],{"class":377},[16,2239,2240,2243,2246,2248,2251,2254,2256],{"class":354,"line":429},[16,2241,2242],{"class":377},"    \u003C",[16,2244,2245],{"class":408},"button",[16,2247,837],{"class":377},[16,2249,2250],{"class":369},"Edit",[16,2252,2253],{"class":377},"\u003C\u002F",[16,2255,2245],{"class":408},[16,2257,2194],{"class":377},[16,2259,2260,2263,2265],{"class":354,"line":435},[16,2261,2262],{"class":377},"  \u003C\u002F",[16,2264,2171],{"class":408},[16,2266,2194],{"class":377},[16,2268,2269,2271,2273],{"class":354,"line":471},[16,2270,2253],{"class":377},[16,2272,2191],{"class":408},[16,2274,2194],{"class":377},[13,2276,2277,2278],{},"Consult the GitHub repository for more information: ",[227,2279,25],{"repo":1559},[1570,2281,2283],{"id":2282},"final-thoughts","Final thoughts",[13,2285,2286,2287,2292],{},"Both the code and the design of this package draw significant inspiration from the ",[21,2288,2291],{"href":2289,"rel":2290},"https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization",[33],"Adonis Bouncer",". It is well-crafted, and reinventing concepts unnecessarily is often futile.",[13,2294,2295],{},"I am already implementing this package in Orion, and the experience has been quite enjoyable. I am confident there is still potential for enhancement, but this module represents an initial step in empowering Nuxt developers and advancing Nuxt's full-stack capabilities. Contributions and suggestions for new features or improvements are welcome.",[13,2297,2298],{},"Orion is a community-driven collection of templates for your projects, spanning from landing pages to comprehensive web applications.",[13,2300,2301,2302,2304,2305,2308],{},"I hope this article and module assist you in managing ",[74,2303,297],{}," within your Nuxt application, thereby preventing and restricting ",[74,2306,2307],{},"unauthorized access"," to your data. Security is imperative.",[13,2310,2311],{},"Happy coding!",[2313,2314,2315],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":258,"searchDepth":259,"depth":259,"links":2317},[2318,2319,2320,2321,2322],{"id":334,"depth":259,"text":335},{"id":622,"depth":259,"text":623},{"id":746,"depth":259,"text":747},{"id":1097,"depth":259,"text":1098},{"id":1541,"depth":259,"text":1542,"children":2323},[2324,2325,2326],{"id":1572,"depth":429,"text":1573},{"id":1611,"depth":429,"text":1612},{"id":2282,"depth":429,"text":2283},"2024-06-09","Secure your Nuxt app with advanced authorization techniques. Learn to restrict unauthorized access and safeguard sensitive data seamlessly.",{},"\u002Fposts\u002Fnuxt-going-full-stack-how-to-handle-authorization","---\ncontentId: f47ef865-e311-48e6-ab6e-6f7ee6bde6e8\ntitle: \"Nuxt Going Full-Stack: How to Handle Authorization?\"\ndescription: Secure your Nuxt app with advanced authorization techniques. Learn to restrict unauthorized access and safeguard sensitive data seamlessly.\ndate: 2024-06-09\n---\n\nWhen developing an application, it is essential to **grant** or **restrict** access to specific areas or data based on a user's role or permissions. This concept is known as **authorization**.\n\nAuthorization is a critical aspect of any application for **security** purposes. It is imperative to protect **sensitive data**, such as a user's email address or password, and prevent the disclosure of **private information** that could compromise user privacy and your business integrity.\n\nTo clarify: **authentication** involves verifying a user's identity, whereas **authorization** involves permitting or denying access to resources based on the user's role or permissions.\n\nWhile building Orion, I confronted this challenge and experimented with various approaches. In this article, I'll share several strategies to manage authorization in a NuxtHub application, ranging from the simplest to the most sophisticated methods. It is crucial to understand that the most complex method is not necessarily the best for every application.\n\nOrion serves as a community-driven collection of templates for your projects, from landing pages to complete web applications. NuxtHub functions as a deployment and management platform for Nuxt, powered by Cloudflare.\n\n\u003CCalloutReadMore id=\"c21a371e-abdd-4325-b296-dadcb8deec25\" \u002F>\n\n## The context\n\nConsider this **endpoint** in your Nuxt application:\n\n```ts\nexport default defineEventHandler(async (event) => {\n  const id = getRouteParam(event, 'id')\n\n  const publication = await db.query.publications.findOne({\n    where: { id },\n    columns: {\n      title: true,\n      content: true,\n      status: true,\n      authorId: true,\n    },\n  })\n\n  return publication\n})\n```\n\nYou can access this endpoint by making a `GET \u002Fapi\u002Fpublication\u002F\u003Cid>` request. This endpoint returns a publication. At first glance, there is no issue with this endpoint. However, a closer examination reveals that a publication has a status that can be `draft`, `published`, or `deleted`.\n\nThe concern is that anyone can access this endpoint and view a **draft publication** or a **deleted publication**. Some might be able to view the content of a publication before it is published, which could contain vital information such as a press release or a product launch. Such a scenario could significantly harm your business.\n\nThis is a security issue that necessitates restricting access to this endpoint, utilizing the [principle of least privilege](https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FPrinciple_of_least_privilege). Security is not merely a feature but should be ingrained in the system's design.\n\n## Authenticated or not\n\nThe following section is premised on the utilization of the Nuxt [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils) package. Nevertheless, its usage is not essential.\n\nThe initial step in securing this **endpoint** is verifying if the user is **authenticated**. If the user lacks authentication, it should return a `401 Unauthorized` error.\n\nUpon exploring the **nuxt-auth-utils** package, I discovered a server utility termed `requireUserSession`. This utility can be employed at an endpoint's inception to attempt retrieving the user's session and triggering a `401 Unauthorized` error if there is no session, indicating the user is unauthenticated.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireUserSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n```\n\nThis is the preliminary step towards safeguarding the endpoint against **unauthenticated users**. However, if anyone can register an account, they may still access the content of a draft publication. Further action is necessary.\n\n## Only admins\n\nBuilding on `requireUserSession`, I can readily devise a new utility named `requireAdminSession`. This session will verify if the user is authenticated and has admin rights. If the user is not an admin, a `403 Forbidden` error should be returned. Unauthorized relates to authentication, while Forbidden pertains to authorization.\n\n```ts\nexport async function requireAdminSession(event: H3Event, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await requireUserSession(event)\n  const { user } = userSession\n\n  if (user.roleType !== 'admin') {\n    throw createError({\n      statusCode: opts.statusCode || 403,\n      message: opts.message || 'Unauthorized',\n    })\n  }\n\n  return userSession as UserSessionRequired\n}\n```\n\nSubsequently, `requireUserSession` can be replaced with `requireAdminSession` in the endpoint.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await requireAdminSession(event)\n\n  const publication = {} \u002F\u002F ...\n\n  return publication\n})\n```\n\nNow, only **admins**, users assigned the `admin` role, can access a publication's content.\n\nHowever, this approach is not entirely effective, as if the publication is published, I wish to permit all users to access it. Currently, only admins can view the content of a publication, irrespective of the publication's status.\n\n## Issues with these approaches\n\nThe aforementioned strategies lack sufficient flexibility for a detailed **authorization system**. They cannot accommodate situations where the author of the publication should have access, even if the publication is unpublished.\n\nTo address this, I could draft another utility termed `requirePublicationAccess`:\n\n```ts\nexport async function requirePublicationAccess(event: H3Event, publication: Publication, opts: { statusCode?: number, message?: string } = {}): Promise\u003CUserSessionRequired> {\n  const userSession = await getUserSession(event)\n  const { user } = userSession\n\n  if (publication.status === 'published')\n    return userSession\n\n  if (!userSession) {\n    throw createError({\n      statusCode: opts.statusCode || 401,\n      message: opts.message || 'Unauthorized',\n    })\n  }\n\n  if (publication.authorId === user.id)\n    return userSession\n\n  if (user.roleType === 'admin')\n    return userSession\n\n  throw createError({\n    statusCode: opts.statusCode || 403,\n    message: opts.message || 'Forbidden',\n  })\n}\n```\n\nThis solution is adequate until I need to manage additional endpoints. This approach will result in excessive code duplication concerning error handling. Additionally, the code becomes more challenging to test due to the necessity of managing an entire H3 event.\n\nWithout a superior approach, I continue developing Orion but soon encountered another challenge: I need to implement the same **authorization logic on the client side**. I do not want to show an edit button to a user who is neither the author of the publication nor an admin, and I do not wish to hide this edit button from the admin if I decide to permit publication editing on the server. Similar to [forms validation](.\u002Fnuxt-going-full-stack-how-to-streamline-form-validation.md), duplicating logic can result in substantial inconsistencies between the client and the server, leading to customer frustration. The utilities I have created thus far are solely server-side due to the use of `requireUserSession` and `getUserSession`.\n\nLet's take a step back to comprehend how this issue might be resolved. Authorization consists of three components:\n\n- **allow** access to a resource\n- **deny** access to a resource\n- **authorize** access to a resource\n\nThe first two are straightforward conditions yielding a boolean response to questions like \"Can I access this publication? Yes or No\". The developer must manually handle this response. The third one grants access to the resource. For the question \"Can I access this publication?\", there is neither a \"yes\" nor \"no\"; nothing happens if the answer is \"yes\", but an error is raised if it is \"no\". The `authorize` sends an error automatically.\n\nThese elements hold no direct relation to the client, server, or the framework in use or the authentication system. Armed with this knowledge, I began working on a local module for Orion to manage authorization more flexibly. After several days of effort, I am proud to introduce **nuxt-authorization**.\n\nInitially, I aimed to develop both a Nitro and a Nuxt package, but Nitro modules are not ready yet. Nonetheless, I submitted some pull requests to the Nitro repository to enhance the module system. One of these is the [module author guide](https:\u002F\u002Fgithub.com\u002Funjs\u002Fnitro\u002Fpull\u002F2508).\n\n## Introducing nuxt-authorization\n\nThis module offers a simple yet powerful method for managing **authorization** in a Nuxt application, applicable to both client and server. It is authentication system agnostic but can be seamlessly integrated with [**nuxt-auth-utils**](https:\u002F\u002Fgithub.com\u002FAtinux\u002Fnuxt-auth-utils).\n\nFor instructions on its usage, visit the GitHub repository: \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>. To delve into the design and implementation, continue reading.\n\nThe module remains in its early stages of development, and if you have any feedback or suggestions for improvement, feel free to open an issue on the GitHub repository or contact me on [X](https:\u002F\u002Fx.com\u002Fbarbapapazes).\n\n### Problems I want to solve\n\nBefore describing what has been accomplished with **nuxt-authorization**, let's examine the problems I intended to solve.\n\nMuch like forms validation, I aim to maintain a single source of truth and a single API for both the app and server. This is crucial to avoid discrepancies between the app and server.\n\nI also seek the ability to define **authorization logic** simply and grouped by resource. This approach enhances readability, maintainability, and consistency across various application aspects, such as forms validation.\n\nThe authorization logic must be adaptable enough to accommodate a varied range of use cases, from `allow` or `deny` conditions to more complex `authorize` conditions. The errors produced by the `authorize` condition should be customizable to enhance user experience. Not all unauthorized errors need to be a `403 Forbidden`.\n\nFinally, I desire components that ease integrating authorization logic into templates. For instance, I want the ability to show or hide a button based on authorization logic.\n\n### Design\n\nThe module comprises two components: the **ability** and the **bouncer**.\n\nAbilities constitute the rules that embody the **authorization logic**. They take a user and one or more resources and yield a deny or an allow condition. While they can be grouped by resource, they remain independent of one another.\n\n```ts\nexport const editBook = defineAbility((user: User, book: Book) => {\n  return user.id === book.authorId\n})\n```\n\nIn the above example, only the author of a book is allowed to edit it. By default, abilities are executed only if the user is authenticated, but it is possible to permit guest access to certain resources.\n\n```ts\nexport const listBooks = defineAbility((user: User | null) => {\n  return true\n})\n```\n\nIn this instance, everyone, including unauthenticated users, is permitted to list books.\n\nThe `defineAbility` function acts as a factory that creates an ability but nothing beyond that. It needs to be used with bouncer functions.\n\nIn a server endpoint, the `authorize` function can be employed to grant access to a resource based on the abilities.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  await authorize(event, listBooks)\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n```\n\nThe `authorize` function will trigger a `403 Forbidden` error if the user is not permitted to list books. More granular control can be achieved using the `allows` or `denies` functions.\n\n```ts\nexport default defineEventHandler(async (event) => {\n  if (await denies(event, listBooks, book))\n    return []\n\n  const books = await db.query.books.findMany()\n\n  return books\n})\n```\n\nInstead of triggering an error, an empty array is returned if the user is not allowed to list books. The appropriate method for handling unauthorized access will depend on your business logic.\n\nFor example, returning a `404 Not Found` error if the user is not authorized to access a specific resource can preserve data privacy. Disclosing the existence of the resource could pose a security risk.\n\nThis customization is attainable within the abilities through the `allow` and `deny` functions.\n\n```ts\nexport const viewBook = defineAbility((user: User, book: Book) => {\n  if (book.status === 'draft') {\n    return deny({\n      statusCode: 404,\n      message: 'Not Found',\n    })\n  }\n\n  return allow()\n})\n```\n\nNow, the `authorize` function will throw a `404 Not Found` error if the user is not permitted to view a draft book, rather than the default `403 Forbidden`.\n\nOn the client side, the `allows`, `denies`, and `authorize` functions can also be utilized to administer the **authorization logic**.\n\nIn addition, these functions offer two components: `Can` and `Cannot`. These components enable portions of a template to be shown or hidden based on the **authorization logic**.\n\n```vue\n\u003Ctemplate>\n  \u003CCan :ability=\"editBook\" :args=\"[book]\">\n    \u003Cbutton>Edit\u003C\u002Fbutton>\n  \u003C\u002FCan>\n\u003C\u002Ftemplate>\n```\n\nConsult the GitHub repository for more information: \u003CGitHubLink repo=\"barbapapazes\u002Fnuxt-authorization\" \u002F>.\n\n### Final thoughts\n\nBoth the code and the design of this package draw significant inspiration from the [Adonis Bouncer](https:\u002F\u002Fdocs.adonisjs.com\u002Fguides\u002Fsecurity\u002Fauthorization). It is well-crafted, and reinventing concepts unnecessarily is often futile.\n\nI am already implementing this package in Orion, and the experience has been quite enjoyable. I am confident there is still potential for enhancement, but this module represents an initial step in empowering Nuxt developers and advancing Nuxt's full-stack capabilities. Contributions and suggestions for new features or improvements are welcome.\n\nOrion is a community-driven collection of templates for your projects, spanning from landing pages to comprehensive web applications.\n\nI hope this article and module assist you in managing **authorization** within your Nuxt application, thereby preventing and restricting **unauthorized access** to your data. Security is imperative.\n\nHappy coding!\n",{"title":281,"description":2328},{"loc":2330},"posts\u002Fnuxt-going-full-stack-how-to-handle-authorization","Nb1IQ9J-6zpzK79Vmw92JpvkKoqjtukuLEsLIQ35_5Q",1790086901693]