├── .eslintrc ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2021-07-15-hello-world.md │ └── 2021-09-28-nextshield-example.md ├── docs │ ├── intro.md │ ├── props │ │ ├── LoadingComponent.md │ │ ├── RBAC.md │ │ ├── _category_.json │ │ ├── accessRoute.md │ │ ├── hybridRoutes.md │ │ ├── isAuth.md │ │ ├── isLoading.md │ │ ├── loginRoute.md │ │ ├── privateRoutes.md │ │ ├── publicRoutes.md │ │ ├── router.md │ │ └── userRole.md │ ├── protect-components │ │ ├── ComponentShield.md │ │ └── _category_.json │ ├── tutorial-basics │ │ ├── _category_.json │ │ ├── configure-your-shield.md │ │ ├── congratulations.md │ │ ├── create-the-pages.md │ │ ├── install-nextshield.md │ │ └── test-it.md │ └── tutorial-extras │ │ ├── RBAC.md │ │ ├── _category_.json │ │ └── typescript.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── components │ │ ├── HomepageFeatures.js │ │ └── HomepageFeatures.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ └── index.module.css └── static │ ├── .nojekyll │ └── img │ ├── RBAC.svg │ ├── code.svg │ ├── easy.svg │ ├── favicon.ico │ ├── flash.svg │ ├── focus.svg │ ├── logo.svg │ ├── nextshield.png │ └── routes.svg ├── example ├── .npmignore ├── .parcel-cache │ ├── data.mdb │ └── lock.mdb ├── index.html ├── index.tsx ├── package-lock.json ├── package.json └── tsconfig.json ├── images └── nextshield.png ├── package-lock.json ├── package.json ├── src ├── components │ ├── ComponentShield.tsx │ └── NextShield.tsx ├── index.ts ├── libs │ └── routes.ts └── types │ ├── Component.ts │ ├── common.ts │ └── props.ts ├── test └── verifyPath.spec.tsx └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | pull_request: 10 | branches: [main] 11 | 12 | jobs: 13 | quality: 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | matrix: 18 | node-version: [16.x] 19 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 20 | os: [ubuntu-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v2 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm test 31 | 32 | publish: 33 | strategy: 34 | matrix: 35 | node-version: [16.x] 36 | os: [ubuntu-latest] 37 | 38 | runs-on: ${{ matrix.os }} 39 | if: ${{ github.ref == 'refs/heads/main' }} 40 | needs: [quality] 41 | 42 | steps: 43 | - uses: actions/checkout@v2 44 | - name: Use Node.js ${{ matrix.node-version }} 45 | uses: actions/setup-node@v2 46 | with: 47 | node-version: ${{ matrix.node-version }} 48 | - run: npm ci 49 | - run: npm run semantic-release 50 | env: 51 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 90, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "trailingComma": "es5", 15 | "vueIndentScriptAndStyle": false 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jorge Acero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Gatsby GastbyFire 3 |

4 |

5 | NextShield 6 |

7 | 8 | 😉 The shield that every Next.js app needs. 9 | 10 | ## Motivation 11 | 12 | After creating a lot of apps with the same boilerplate code for authentication & authorization I realized that I hate dealing with it, so I create this package to never face the same frustrating situation again. 13 | 14 | ## Philosophy 15 | 16 | - Never deal with auth code again. 17 | - Never hardcode a redirect, let your state handle it for you. 18 | - Easy To Use. 19 | 20 | ## Features 21 | 22 | - No Flashy Content. 23 | - RBAC. 24 | - Completely Agnostic. 25 | 26 | ## Documentation 27 | 28 | You can find the documentation at the [official website](https://imjulianeral.github.io/next-shield/). 29 | 30 | ## Update 2025 31 | 32 | With the addition of middleware in Next.js maybe you don't need this package anymore. 33 | 34 | 37 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2021-07-15-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello World! 4 | author: '@imjulianeral' 5 | author_title: Author of NextShield 6 | author_url: https://github.com/imjulianeral 7 | author_image_url: https://avatars.githubusercontent.com/u/41587947?v=4 8 | tags: [hello, ignition] 9 | --- 10 | 11 | # Welcome to NextShield! 12 | 13 | 14 | 15 | Today NextShield is released! 16 | 17 | Hope you can get a lot of fun with it! 18 | 19 | ## How to Start? 20 | 21 | 1. If you have 5 min please start by reading the [props](../docs/props/isAuth). 22 | 1. Then I recommend you to start the [tutorial](../docs/intro). 23 | 1. Learn how to use ComponentShield to avoid using ternaries in your JSX code: [ComponentShield](../docs/protect-components/ComponentShield.md) 24 | -------------------------------------------------------------------------------- /docs/blog/2021-09-28-nextshield-example.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: nextshield-private-routes-and-rbac 3 | title: RBAC & Private Routes in Next.js using next-shield 4 | author: '@imjulianeral' 5 | author_title: Author of NextShield 6 | author_url: https://github.com/imjulianeral 7 | author_image_url: https://avatars.githubusercontent.com/u/41587947?v=4 8 | tags: [examples] 9 | --- 10 | 11 | # RBAC & Private Routes in Next.js using next-shield. 12 | 13 | 14 | 15 | Protecting routes and hiding features whether the **user** is authenticated or authorized is a common thing to do since always but even so in the react world it's quite frustrating; the rerenders that React has, is a behavior easy to understand but hard to master, when you don't have control of it you can get a lot of issues like the **flashy content**: 16 | 17 | ![Peek 2021-09-23 11-13.gif](https://cdn.hashnode.com/res/hashnode/image/upload/v1632413694415/VIwhYE1BZ.gif) 18 | 19 | This happens because in the first render React read the auth state as `null` Why? because in the first render the API request was not resolved yet, is until the second render when React has resolved the request making the change to the state, triggering a rerender but now with the data **available**. 20 | 21 | In summary, React is reading the state values before they are **available**, getting a [falsy value](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), that's the problem. 22 | 23 | How can you solve this? There are many solutions, some people handle that on the frontend and implement a _hacky_ way using `windows.location` or on the backend with Next.js using the `getServerSideProps` method on your pages which has a `redirect` property to force a redirect to another page, and as you know is executed before rendering the page, which is perfect, right? Well... yes but actually no. 24 | 25 | ![confused](https://cdn.hashnode.com/res/hashnode/image/upload/v1632415824864/TVwJxmb72.gif) 26 | 27 | If your backend server gets frozen the only thing your user is gonna see is a blank page, because of that you can't give any feedback to your user, not even a spinner, the best solution is still on the frontend, so may you ask "how can I implement the hacky solution using `windows.location`?" I don't recommend that also, so what solution do I recommend? 28 | 29 | ## The Solution. 30 | 31 | Frameworks like Laravel or Ruby on Rails have this solved, so why a cutting-edge framework like Next.js has not resolved this yet? Well, maybe we never know it (or maybe there is a reason for it but I didn't realize), but I crafted my own solution called [NextShield](https://imjulianeral.github.io/next-shield/): 32 | 33 | ![logo](https://cdn.hashnode.com/res/hashnode/image/upload/v1632417104791/PczV3QiWs.png) 34 | 35 | ### Philosophy. 36 | 37 | - Never deal with authorization code again. 38 | - Never hardcode a redirect, let your state handle it for you. 39 | - Easy To Use. 40 | 41 | ### Features. 42 | 43 | - No Flashy Content. 44 | - RBAC. 45 | - Completely Agnostic. 46 | 47 | Let's create a simple example to see the API. 48 | 49 | ## NextShield Example. 50 | 51 | - Create a new Next.js app: 52 | 53 | ```shell 54 | npx create-next-app --ts shield-example 55 | ``` 56 | 57 | - Install NextShield: 58 | 59 | ```shell 60 | npm i next-shield 61 | ``` 62 | 63 | - Install the spinners: 64 | 65 | ```shell 66 | npm i react-epic-spinners 67 | ``` 68 | 69 | - Copy the following styles in your styles/globals.css file: 70 | 71 | ```css 72 | * { 73 | box-sizing: border-box; 74 | } 75 | 76 | html, 77 | body { 78 | padding: 0; 79 | margin: 0; 80 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, 81 | Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 82 | background-color: black; 83 | color: white; 84 | } 85 | 86 | nav { 87 | display: flex; 88 | flex-direction: column; 89 | } 90 | 91 | @media (min-width: 768px) { 92 | nav { 93 | flex-direction: row; 94 | justify-content: space-evenly; 95 | } 96 | } 97 | 98 | a { 99 | display: block; 100 | text-align: center; 101 | text-decoration: none; 102 | color: white; 103 | padding: 1rem; 104 | transition: all ease-in-out 0.3s; 105 | } 106 | 107 | a:hover { 108 | color: black; 109 | background-color: white; 110 | } 111 | 112 | button { 113 | padding: 0.5rem 1rem; 114 | cursor: pointer; 115 | background-color: white; 116 | color: black; 117 | border: none; 118 | font-weight: 700; 119 | font-size: 1.2rem; 120 | transition: all ease-in-out 0.3s; 121 | } 122 | 123 | button:hover { 124 | background-color: #00d1ff; 125 | color: white; 126 | } 127 | 128 | .center { 129 | height: 40vh; 130 | display: grid; 131 | place-items: center; 132 | text-align: center; 133 | } 134 | 135 | @media (min-width: 768px) { 136 | .center { 137 | height: 90vh; 138 | } 139 | } 140 | 141 | .loading { 142 | margin: 40vh auto; 143 | } 144 | ``` 145 | 146 | - Add the following types: 147 | 148 | ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632418919919/C09y6I4j0.png) 149 | 150 | ```jsx 151 | // Components.ts 152 | 153 | import type { ReactNode } from 'react' 154 | 155 | export interface Children { 156 | children: ReactNode; 157 | } 158 | 159 | export type LayoutProps = { 160 | title: string, 161 | } & Children 162 | ``` 163 | 164 | ```jsx 165 | // User.ts 166 | 167 | export interface Profile { 168 | id: string 169 | name: string 170 | role: string 171 | } 172 | 173 | 174 | ``` 175 | 176 | - Create the `Nav` Component: 177 | 178 | ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632418727457/eGrL9Aut0.png) 179 | 180 | ```jsx 181 | import Link from 'next/link' 182 | 183 | export function Nav() { 184 | return ( 185 | 208 | ) 209 | } 210 | ``` 211 | 212 | - Add the following components: 213 | 214 | ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1632418141135/QnsMHR0sE.png) 215 | 216 | ```jsx 217 | // Layout.tsx 218 | 219 | import Head from 'next/head' 220 | import { LayoutProps } from '@/types/Components' 221 | import { Nav } from '../ui/Nav' 222 | 223 | export function Layout({ children, title }: LayoutProps) { 224 | return ( 225 | <> 226 | 227 | NextShield | {title} 228 | 229 | 230 | 231 | 232 |