├── .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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
233 |
234 | {children}
235 |
236 | >
237 | )
238 | }
239 | ```
240 |
241 | ```jsx
242 | // Loading.tsx
243 |
244 | import { BreedingRhombusSpinner } from 'react-epic-spinners'
245 |
246 | export function Loading() {
247 | return
248 | }
249 | ```
250 |
251 | And finally the `NextShield` setup.
252 |
253 | ### NextShield Setup.
254 |
255 | In `Shield.tsx` file import the `useRouter`, the `Loading.tsx` and `next-shield`:
256 |
257 | ```jsx
258 | import { useRouter } from 'next/router'
259 | import { NextShield, NextShieldProps } from 'next-shield'
260 |
261 | import { Children } from '@/types/Components'
262 | import { Loading } from './Loading'
263 |
264 | export function Shield({ children }: Children) {
265 | const router = useRouter()
266 |
267 | return <>{children}>
268 | }
269 | ```
270 |
271 | Just after the router create an object implementing the `NextShieldProps` called `shieldProps`:
272 |
273 | ```jsx
274 | const shieldProps: NextShieldProps = {}
275 | ```
276 |
277 | You need to pass some generics to the type, the first one is an array of the private routes of your application, and the second one is an array with the public routes:
278 |
279 | ```jsx
280 | const shieldProps: NextShieldProps<
281 | ['/profile', '/dashboard', '/users', '/users/[id]'],
282 | ['/', '/login']
283 | > = {}
284 | ```
285 |
286 | Then you need to pass the following **props**:
287 |
288 | #### Router.
289 |
290 | ⇆ Instance of your router.
291 |
292 | #### isAuth.
293 |
294 | 🔑 This value must be provided by the state of your app. Indicates if the user is authenticated or not.
295 |
296 | #### isLoading.
297 |
298 | ⏳ This value must be provided by the state of your app. Indicates if the user's data is already available or not.
299 |
300 | #### privateRoutes.
301 |
302 | 🚧 🚧 🚧 Array of private routes. These are only accessible when the user is authenticated.
303 |
304 | #### publicRoutes.
305 |
306 | 👀 👀 👀 Array of public routes. These are only accessible when the user is **NOT** authenticated.
307 |
308 | #### hybridRoutes.
309 |
310 | 🚦🚦🚦 Array of hybrid routes. These are always accessible; doesn't matter the auth state.
311 |
312 | #### loginRoute.
313 |
314 | 📋 Login page. Must be a public route.
315 |
316 | #### accessRoute.
317 |
318 | 🚧 Route where your user is going to access after login. Must be a private route.
319 |
320 | #### LoadingComponent.
321 |
322 | 🌀 React Component which is going to appear when isLoading equals to true.
323 |
324 | These are going to be the values, for this example:
325 |
326 | ```jsx
327 | const shieldProps: NextShieldProps<
328 | ['/profile', '/dashboard', '/users', '/users/[id]'],
329 | ['/', '/login']
330 | > = {
331 | router,
332 | isAuth: false,
333 | isLoading: false,
334 | privateRoutes: ['/profile', '/dashboard', '/users', '/users/[id]'],
335 | publicRoutes: ['/', '/login'],
336 | hybridRoutes: ['/pricing'],
337 | loginRoute: '/login',
338 | accessRoute: '/profile'
339 | LoadingComponent: ,
340 | }
341 | ```
342 |
343 | ### Protect the app with NextShield.
344 |
345 | Go to the `_app.tsx` file and wrap it with the `Shield` component:
346 |
347 | ```jsx
348 | import type { AppProps } from 'next/app'
349 |
350 | import { Shield } from '@/components/routes/Shield'
351 |
352 | import '@/styles/globals.css'
353 |
354 | export default function MyApp({ Component, pageProps }: AppProps) {
355 | return (
356 |
357 |
358 |
359 | )
360 | }
361 | ```
362 |
363 | ### Create the routes.
364 |
365 | Create the following routes:
366 |
367 | 
368 |
369 | And write in each of them the following content:
370 |
371 | ```jsx
372 | import { Layout } from '@/components/routes/Layout'
373 |
374 | export default function PageName() {
375 | return (
376 |
377 |
PageName
378 |
379 | )
380 | }
381 | ```
382 |
383 | By now you must have something like this:
384 |
385 | 
386 |
387 | As you can see, you can only access the public & hybrid routes, your private routes are completely protected, Even if you see your history the private routes won't appear:
388 |
389 | 
390 |
391 | Now you can play and set `isAuth: true`, you immediately won't be able to access the public routes.
392 |
393 | 
394 |
395 | ### Add RBAC.
396 |
397 | Fine, you just got a simple example with auth users, now let's add something more interesting.
398 |
399 | To add RBAC you need to pass the prop called `RBAC` as an object with the following configuration:
400 |
401 | ```js
402 | RBAC: {
403 | ADMIN: {
404 | grantedRoutes: ['/dashboard', '/profile', '/users', '/users/[id]'],
405 | accessRoute: '/dashboard',
406 | },
407 | EMPLOYEE: {
408 | grantedRoutes: ['/profile', '/dashboard'],
409 | accessRoute: '/profile',
410 | },
411 | },
412 | ```
413 |
414 | As you can see you define the roles of your app in the object keys, and inside you must define the `grantedRoutes` which is an array with the routes that are going to be accessible for that role, also you must define the `accessRoute` inside of this object **and remove it from outside**.
415 |
416 | After that, you must pass the prop called `userRole`, which is the role of the current auth user, this must match with the object keys in `RBAC`, also this prop needs to be undefined when `isAuth` is false and defined when is true.
417 |
418 | ```js
419 | userRole: 'ADMIN', // Must be undefined when isAuth is false & defined when is true
420 | ```
421 |
422 | The end result should look like this:
423 |
424 | ```ts
425 | const shieldProps: NextShieldProps<
426 | ['/profile', '/dashboard', '/users', '/users/[id]'],
427 | ['/', '/login']
428 | > = {
429 | router,
430 | isAuth: true,
431 | isLoading: false,
432 | privateRoutes: ['/profile', '/dashboard', '/users', '/users/[id]'],
433 | publicRoutes: ['/', '/login'],
434 | hybridRoutes: ['/pricing'],
435 | loginRoute: '/login',
436 | LoadingComponent: ,
437 | RBAC: {
438 | ADMIN: {
439 | grantedRoutes: ['/dashboard', '/profile', '/users', '/users/[id]'],
440 | accessRoute: '/dashboard',
441 | },
442 | EMPLOYEE: {
443 | grantedRoutes: ['/profile', '/dashboard'],
444 | accessRoute: '/profile',
445 | },
446 | },
447 | userRole: 'ADMIN', // Must be undefined when isAuth is false & defined when is true
448 | }
449 | ```
450 |
451 | And that's it! You will get the same behavior as before:
452 |
453 | - Unable to access ungranted routes.
454 | - No trace on your history.
455 | - 0 flashy content, the `LoadingComponent` will always intercept the request before showing the page or redirecting the user.
456 | - Configure everything in one place.
457 |
458 | 
459 |
460 | ## Next Steps.
461 |
462 | 1. [Read the docs](https://imjulianeral.github.io/next-shield/).
463 | 1. [See the complete example on the repo (main branch)](https://github.com/imjulianeral/nextshield-examples)
464 | 1. Use it with your preferred auth provider.
465 | 1. Use [ComponentShield](https://imjulianeral.github.io/next-shield/docs/protect-components/ComponentShield) to get more control of what is displayed on the screen.
466 | 1. Wait for the following Examples!
467 |
--------------------------------------------------------------------------------
/docs/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Getting Started
6 |
7 | Let's discover **NextShield in less than 5 minutes**.
8 |
9 | ## Generate a new Next.js app
10 |
11 | ```shell
12 | npx create-next-app webapp
13 | ```
14 |
15 | ## Start your app
16 |
17 | Run the development server:
18 |
19 | ```shell
20 | cd webapp
21 |
22 | npm run dev
23 | ```
24 |
25 | Your app starts at `http://localhost:3000`.
26 |
--------------------------------------------------------------------------------
/docs/docs/props/LoadingComponent.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | 🌀 React Component which is going to appear when `isLoading` equals to `true`.
6 |
7 | `Loading.tsx`:
8 |
9 | ```tsx
10 | export function Loading() {
11 | return
Loading...
12 | }
13 | ```
14 |
15 | `_app.tsx`:
16 |
17 | ```tsx
18 | import { Loading } from '@components/routes/loading'
19 |
20 | const MyApp: NextPage = ({ Component, pageProps }) => {
21 | return (
22 | }
25 | ...
26 | >
27 |
28 |
29 | )
30 | }
31 | ```
32 |
--------------------------------------------------------------------------------
/docs/docs/props/RBAC.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 10
3 | ---
4 |
5 | # RBAC
6 |
7 | 🔏 🔐 🔒 Role Based Access Control.
8 |
9 | You can define an object literal to specify which roles are supported and which routes the role have access.
10 |
11 | **You must define the accessRoute on each Role.**
12 |
13 | ```tsx
14 | return (
15 |
29 |
30 |
31 | )
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/docs/props/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Props",
3 | "position": 4
4 | }
5 |
--------------------------------------------------------------------------------
/docs/docs/props/accessRoute.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 9
3 | ---
4 |
5 | # accessRoute
6 |
7 | 🚧 Route where your user is going to access after login, must be a private route.
8 |
9 | ```tsx
10 | ...
11 | return (
12 |
17 |
18 |
19 | )
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/docs/props/hybridRoutes.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 7
3 | ---
4 |
5 | 🚦🚦🚦 Array of hybrid routes. These are always accessible; doesn't matter the auth state.
6 |
7 | ```ts
8 | const hybridRoutes = ['/support', '/pricing', '/products/[slug]']
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/docs/props/isAuth.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # isAuth
6 |
7 | 🔑 This value must be provided by the state of your app. Indicates if the user is authenticated or not.
8 |
9 | Here's a simple example with firebase auth. But applies the same logic for any auth provider. 😋
10 |
11 | ```ts
12 | const [isAuth, setAuth] = useState(false)
13 | useEffect(() => {
14 | const unsubscribe = auth().onAuthStateChanged(user => {
15 | if (user) {
16 | setAuth(true)
17 | return
18 | }
19 |
20 | setAuth(false)
21 | })
22 |
23 | return () => unsubscribe()
24 | }, [isAuth])
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/docs/props/isLoading.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # isLoading
6 |
7 | ⏳ This value must be provided by the state of your app. Indicates if the user is already available or not.
8 |
9 | Here's a simple example with firebase auth. But applies the same logic for any auth provider. 😋
10 |
11 | ```ts
12 | const [isAuth, setAuth] = useState(false)
13 | const [isLoading, setLoading] = useState(true)
14 | useEffect(() => {
15 | const unsubscribe = auth().onAuthStateChanged(user => {
16 | if (user) {
17 | setAuth(true)
18 | setLoading(false)
19 | return
20 | }
21 |
22 | setAuth(false)
23 | setLoading(false)
24 | })
25 |
26 | return () => unsubscribe()
27 | }, [isAuth])
28 | ```
29 |
--------------------------------------------------------------------------------
/docs/docs/props/loginRoute.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 8
3 | ---
4 |
5 | # loginRoute
6 |
7 | 📋 Login page, must be a public route.
8 |
9 | ```tsx
10 | ...
11 | return (
12 |
17 |
18 |
19 | )
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/docs/props/privateRoutes.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | ---
4 |
5 | 🚧 🚧 🚧 Array of private routes. These are only accessible when the user is authenticated.
6 |
7 | ```ts
8 | const privateRoutes = ['/control-panel', '/sales', '/user/[id]']
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/docs/props/publicRoutes.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 6
3 | ---
4 |
5 | 👀 👀 👀 Array of public routes. These are only accessible when the user is NOT authenticated.
6 |
7 | ```ts
8 | const publicRoutes = ['/', '/login', '/services/[slug]']
9 | ```
10 |
--------------------------------------------------------------------------------
/docs/docs/props/router.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | ---
4 |
5 | # Router
6 |
7 | ⇆ Instance of your router.
8 |
9 | ```tsx
10 | const router = useRouter()
11 | ...
12 | return (
13 |
18 |
19 |
20 | )
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/docs/props/userRole.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 11
3 | ---
4 |
5 | # userRole
6 |
7 | 🎭 The auth user role.
8 |
9 | - This value must be provided when using RBAC.
10 | - Should by provided by the session or state of the application.
11 | - Must match with the roles defined on RBAC
12 |
13 | ```tsx
14 | const { user } = useAuth()
15 |
16 | return (
17 |
22 |
23 |
24 | )
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/docs/protect-components/ComponentShield.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # ComponentShield
6 |
7 | 🛡️ A Shield for your Components, it handles when a component shows or not based on the user's role or by a custom condition that you provide. This avoids the use of the ternary operator on your JSX code.
8 |
9 | ## Show By Condition
10 |
11 | When using a component by condition, you are only required to pass a boolean value to the `showIf` prop, a condition which returns `true` or `false` to determine if the components passed as `children` are going to be available or not.
12 |
13 | ```tsx
14 | return (
15 |
16 |
You are authorized
17 |
18 | )
19 | ```
20 |
21 | ### Fallback
22 |
23 | You can provide a custom fallback which is going to be shown when the condition returns `false`
24 |
25 | ```tsx
26 | return (
27 | You are unauthorized}>
28 |
You are authorized
29 |
30 | )
31 | ```
32 |
33 | ## Show By User Role
34 |
35 | If you want to use `ComponentShield` for **RBAC** you must use the three following props:
36 |
37 | - `RBAC`: To tell you're going to use RBAC.
38 | - `showForRole`: The role who is going to have access to the children components.
39 | - `userRole`: The current auth user's role.
40 |
41 | The logic is simple, if `showForRole` and `userRole` are not equal, returns `null`, if they are equal the components passed as `children` will be shown.
42 |
43 | ```tsx
44 | return (
45 |
46 |
You are an ADMIN
47 |
48 | )
49 | ```
50 |
--------------------------------------------------------------------------------
/docs/docs/protect-components/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Protect Components",
3 | "position": 5
4 | }
5 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Tutorial - Basics",
3 | "position": 2
4 | }
5 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/configure-your-shield.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # Configure your Shield
6 |
7 | In order to configure `NextShield`:
8 |
9 | - Provide your **public & private routes**.
10 | - Provide the **state** where you store your authenticated user and when this user is available (`isAuth` & `isLoading`).
11 | - Put your `LoadingComponent`.
12 | - Add your router instance.
13 | - Finally specify the route where you put your login page (`loginRoute`) and the route where your user is going to be redirected (`accessRoute`) after login.
14 |
15 | ## Set up your Shield.
16 |
17 | For this example, we are going to hard code the required props:
18 |
19 | ```jsx title="pages/_app.js"
20 | export default function MyApp({ Component, pageProps }) {
21 | const router = useRouter()
22 |
23 | return (
24 | Loading...}
33 | >
34 |
35 |
36 | )
37 | }
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/congratulations.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 5
3 | ---
4 |
5 | # Congratulations!
6 |
7 | You have just learned the **basics of NextShield!**
8 |
9 |
10 |
11 | ## What's next?
12 |
13 | - Read the [props](../props/isAuth)!
14 | - Use [TypeScript](../tutorial-extras/typescript)!!! NextShield is designed to be used with TS!!!
15 | - Add [RBAC](../tutorial-extras/RBAC).
16 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/create-the-pages.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 3
3 | ---
4 |
5 | # Create the Pages!
6 |
7 | Create the following pages:
8 |
9 | - `private.jsx`
10 | - `control-panel.jsx`
11 | - `login.jsx`
12 |
13 | ## Add the content
14 |
15 | For each page add the following content:
16 |
17 | ```jsx title="pages/PageName.jsx"
18 | import Head from 'next/head'
19 | import Link from 'next/link'
20 |
21 | export default function PageName() {
22 | return (
23 | <>
24 |
25 | PageName | JS Example - NextShield
26 |
27 |
28 |
29 |
30 |
PageName
31 |
32 | Private
33 |
34 |
35 | Control Panel
36 |
37 |
38 | Login
39 |
40 |
41 | Home
42 |
43 | >
44 | )
45 | }
46 | ```
47 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/install-nextshield.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Install NextShield
6 |
7 | Install it through `npm` or your favorite dependency manager 😉
8 |
9 | ```shell
10 | npm i next-shield
11 | ```
12 |
13 | ## Wrap your app with a shield
14 |
15 | Go to `pages/_app.js` and wrap the `Component` with `NextShield`:
16 |
17 | ```jsx title="pages/_app.js"
18 | import { NextShield } from 'next-shield'
19 | import { useRouter } from 'next/router'
20 |
21 | import '../styles/globals.css'
22 |
23 | export default function MyApp({ Component, pageProps }) {
24 | const router = useRouter()
25 |
26 | return (
27 |
28 |
29 |
30 | )
31 | }
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-basics/test-it.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 4
3 | ---
4 |
5 | # Test it!
6 |
7 | Play with it! Change the `isAuth` prop and you'll be automatically redirected to your public or private routes. Also you'll notice:
8 |
9 | - 🤯 `0` flashy content!
10 | - 🤟 You have all setup in one place!
11 | - 😎 Avoid to write repetitive code like: `router.push(route)` after a sign in or sign out operations, delegate.
12 | - 🥳 You don't need to write code to handle public or private routes anymore!
13 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-extras/RBAC.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 2
3 | ---
4 |
5 | # RBAC
6 |
7 | You can add RBAC in **NextShield** by adding 2 properties:
8 |
9 | - RBAC: Object literal where you define the existing roles and the routes, **you must add the `accessRoute` on each role**.
10 | - userRole: String prop, **must match with the defined roles in RBAC**.
11 |
12 | ### Example
13 |
14 | ```tsx title="components/Shield.tsx"
15 | import { useRouter } from 'next/router'
16 | import { NextShield, NextShieldProps } from 'next-shield'
17 |
18 | export function Shield({ children }: Props) {
19 | const router = useRouter()
20 |
21 | const shieldConfig: NextShieldProps<
22 | ['/private', '/control-panel', '/dashboard'],
23 | ['/', '/login']
24 | > = {
25 | router,
26 | isAuth: true,
27 | isLoading: false,
28 | LoadingComponent:
Loading...
,
29 | privateRoutes: ['/private', '/control-panel', '/dashboard'],
30 | publicRoutes: ['/', '/login'],
31 | loginRoute: '/login',
32 | RBAC: {
33 | ADMIN: {
34 | grantedRoutes: ['/dashboard', '/control-panel'],
35 | accessRoute: '/dashboard',
36 | },
37 | EMPLOYEE: {
38 | grantedRoutes: ['/private', '/dashboard'],
39 | accessRoute: '/private',
40 | },
41 | },
42 | userRole: 'ADMIN' | 'EMPLOYEE',
43 | }
44 |
45 | return {children}
46 | }
47 | ```
48 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-extras/_category_.json:
--------------------------------------------------------------------------------
1 | {
2 | "label": "Tutorial - Extras",
3 | "position": 3
4 | }
5 |
--------------------------------------------------------------------------------
/docs/docs/tutorial-extras/typescript.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # TypeScript
6 |
7 | **NextShield** is designed to be used with TypeScript! you'll get some benefits like:
8 |
9 | - Enforces you to write the defined routes in the props and nothing more.
10 | - Type check in strings and arrays.
11 | - Accurate autocompletion.
12 |
13 | ## Translate your project to TS
14 |
15 | 1. Install the TS dependencies: `npm install --save-dev typescript @types/react @types/node`.
16 | 2. Create the file `tsconfig.json` in the root folder.
17 | 3. Rename your file extension to `tsx`.
18 | 4. Run the server with `npx run dev`.
19 | 5. Now go to `_app.tsx` and add the types to the page:
20 |
21 | ```tsx title="pages/_app.tsx"
22 | import type { NextPage } from 'next'
23 | import type { AppProps } from 'next/app'
24 |
25 | const MyApp: NextPage = ({ Component, pageProps }) => {
26 | return (
27 | Loading...}
36 | >
37 |
38 |
39 | )
40 | }
41 |
42 | export default MyApp
43 | ```
44 |
45 | 6. I really encourage you to create a new component to configure NextShield, your `_app.tsx` will become a large file in other way. So, create a components directory with a component called `Shield.tsx` or other name you like.
46 | 7. Import `NextShield` and `NextShieldProps` on that file.
47 | 8. `NextShieldProps` require to define the public and private routes as generics, the first generic is for private and the second for public routes:
48 |
49 | ```tsx title="components/Shield.tsx"
50 | const shieldConfig: NextShieldProps<
51 | ['/private', '/control-panel'],
52 | ['/', '/login']
53 | > = {...}
54 | ```
55 |
56 | 9. Now you are forced to write the same routes on:
57 |
58 | - privateRoutes
59 | - publicRoutes
60 | - accessRoute
61 | - loginRoute
62 | - RBAC
63 |
64 | 10. The final result should look like this:
65 |
66 | ```tsx title="components/Shield.tsx"
67 | import { useRouter } from 'next/router'
68 | import { NextShield, NextShieldProps } from 'next-shield'
69 |
70 | export function Shield({ children }: Props) {
71 | const router = useRouter()
72 |
73 | const shieldConfig: NextShieldProps<['/private', '/control-panel'], ['/', '/login']> = {
74 | router,
75 | isAuth: true,
76 | isLoading: false,
77 | LoadingComponent: