├── .eslintignore
├── public
├── favicon.ico
├── vercel.svg
├── jwt.svg
└── nextjs.svg
├── .prettierrc.js
├── pages
├── api
│ ├── hello.js
│ ├── about.js
│ └── auth.js
├── about.js
└── index.js
├── .gitignore
├── components
├── navigation
│ └── User.jsx
├── header
│ └── Header.jsx
├── footer
│ └── Footer.jsx
├── form
│ ├── FormLogin.jsx
│ ├── FormPost.jsx
│ ├── FormRegister.jsx
│ └── FormJob.jsx
└── layout
│ └── Layout.jsx
├── package.json
├── README.md
├── .eslintrc.js
├── middleware
└── utils.js
├── auth.drawio
└── schemas.drawio
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dyarfi/nextjs-jwt/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tabWidth: 2,
3 | singleQuote: true,
4 | trailingComma: 'all',
5 | };
6 |
--------------------------------------------------------------------------------
/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 | export default (req, res) => {
3 | res.statusCode = 200;
4 | res.json({ name: 'John Doe' });
5 | };
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
32 | .vercel
33 |
--------------------------------------------------------------------------------
/pages/api/about.js:
--------------------------------------------------------------------------------
1 | const CODE = 200;
2 |
3 | export default (req, res) => {
4 | return new Promise(resolve => {
5 | const { method } = req;
6 | let message = {};
7 | try {
8 | switch (method) {
9 | case 'POST':
10 | /* Post method */
11 | break;
12 | case 'PUT':
13 | /* Put method */
14 | break;
15 | case 'PATCH':
16 | /* Patch method */
17 | break;
18 | /* Get */
19 | default:
20 | break;
21 | }
22 | res.statusCode = CODE;
23 | res.json({ message });
24 | } catch (error) {
25 | throw error;
26 | }
27 | res.status(405).end();
28 | return resolve();
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/components/navigation/User.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | /* Components */
4 | // import DarkModeToggle from "../DarkModeToggle";
5 |
6 | const User = ({ props }) => {
7 | const { user } = props;
8 |
9 | return (
10 |
11 | {(user && (
12 |
13 | Logout
14 |
15 | )) || (
16 | <>
17 | Have an Account?
18 |
19 | Login
20 |
21 | or
22 |
23 | Register
24 |
25 | >
26 | )}
27 |
28 | );
29 | };
30 |
31 | export default User;
32 |
--------------------------------------------------------------------------------
/components/header/Header.jsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | /* Components */
4 | // import DarkModeToggle from "../DarkModeToggle";
5 |
6 | const Header = ({ props }) => {
7 | return (
8 | <>
9 |
26 |
31 | >
32 | );
33 | };
34 |
35 | export default Header;
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-jwt",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "bcryptjs": "^2.4.3",
12 | "js-cookie": "^2.2.1",
13 | "jsonwebtoken": "^8.5.1",
14 | "next": "^9.5.3",
15 | "react": "16.13.1",
16 | "react-dom": "16.13.1"
17 | },
18 | "devDependencies": {
19 | "@babel/core": "^7.5.5",
20 | "@babel/plugin-proposal-class-properties": "^7.5.5",
21 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
22 | "@babel/polyfill": "^7.4.4",
23 | "@babel/preset-env": "^7.5.5",
24 | "@babel/preset-react": "^7.0.0",
25 | "babel-eslint": "^10.0.2",
26 | "eslint": "^6.1.0",
27 | "eslint-config-airbnb": "^17.1.1",
28 | "eslint-config-prettier": "^6.0.0",
29 | "eslint-plugin-import": "^2.18.0",
30 | "eslint-plugin-jsx-a11y": "^6.2.3",
31 | "eslint-plugin-prettier": "^3.1.0",
32 | "eslint-plugin-react": "^7.14.2",
33 | "prettier": "^1.18.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/jwt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | ## Learn More
18 |
19 | To learn more about Next.js, take a look at the following resources:
20 |
21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
23 |
24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
25 |
26 | ## Deploy on Vercel
27 |
28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
29 |
30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
31 |
--------------------------------------------------------------------------------
/components/footer/Footer.jsx:
--------------------------------------------------------------------------------
1 | export default function Footer({ copyright = '2020' }) {
2 | return (
3 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/public/nextjs.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | node: true,
6 | },
7 | extends: ['airbnb', 'prettier', 'prettier/react'],
8 | globals: {
9 | Atomics: 'readonly',
10 | SharedArrayBuffer: 'readonly',
11 | },
12 | parser: 'babel-eslint',
13 | parserOptions: {
14 | ecmaFeatures: {
15 | jsx: true,
16 | },
17 | ecmaVersion: 2018,
18 | sourceType: 'module',
19 | allowImportExportEverywhere: true,
20 | },
21 | plugins: ['react', 'prettier'],
22 | rules: {
23 | 'prettier/prettier': 'error',
24 | 'no-console': 0,
25 | 'no-nested-ternary': 0,
26 | 'import/order': 0,
27 | 'import/no-extraneous-dependencies': 0,
28 | 'jsx-a11y/anchor-is-valid': 0,
29 | 'react/no-array-index-key': 0,
30 | 'react/prefer-stateless-function': 0,
31 | 'react/sort-comp': 0,
32 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
33 | 'no-use-before-define': [
34 | 'error',
35 | { functions: false, classes: false, variables: false },
36 | ],
37 | 'react/jsx-filename-extension': [
38 | 1,
39 | {
40 | extensions: ['.js', 'jsx'],
41 | },
42 | ],
43 | 'react/forbid-prop-types': 0,
44 | 'react/prop-types': 0,
45 | /* 'react/prop-types': [
46 | 'enabled',
47 | { ignore: 'ignore', customValidators: 'customValidator' },
48 | ], */
49 | // 'max-len': ['error', 80],
50 | },
51 | settings: {
52 | 'import/resolver': {
53 | node: {
54 | extensions: ['.js', '.jsx', '.ts', '.tsx'],
55 | },
56 | },
57 | },
58 | };
59 |
--------------------------------------------------------------------------------
/components/form/FormLogin.jsx:
--------------------------------------------------------------------------------
1 | function FormLogin({ props }) {
2 | const {
3 | onSubmitHandler,
4 | onChangeHandler,
5 | stateFormData,
6 | stateFormError,
7 | stateFormMessage,
8 | } = props;
9 | return (
10 |
54 | );
55 | }
56 | export default FormLogin;
57 |
--------------------------------------------------------------------------------
/components/form/FormPost.jsx:
--------------------------------------------------------------------------------
1 | function FormPost(props) {
2 | const {
3 | onSubmit,
4 | onChange,
5 | stateFormData,
6 | stateFormError,
7 | stateFormValid,
8 | stateFormMessage,
9 | } = props;
10 | return (
11 |
57 | );
58 | }
59 | export default FormPost;
60 |
--------------------------------------------------------------------------------
/middleware/utils.js:
--------------------------------------------------------------------------------
1 | import Router from 'next/router';
2 | import Cookies from 'js-cookie';
3 |
4 | import jwt from 'jsonwebtoken';
5 |
6 | const SECRET_KEY = process.env.JWT_KEY;
7 |
8 | /*
9 | * @params {jwtToken} extracted from cookies
10 | * @return {object} object of extracted token
11 | */
12 | export function verifyToken(jwtToken) {
13 | try {
14 | return jwt.verify(jwtToken, SECRET_KEY);
15 | } catch (e) {
16 | console.log('e:', e);
17 | return null;
18 | }
19 | }
20 |
21 | /*
22 | * @params {request} extracted from request response
23 | * @return {object} object of parse jwt cookie decode object
24 | */
25 | export function getAppCookies(req) {
26 | const parsedItems = {};
27 | if (req.headers.cookie) {
28 | const cookiesItems = req.headers.cookie.split('; ');
29 | cookiesItems.forEach(cookies => {
30 | const parsedItem = cookies.split('=');
31 | parsedItems[parsedItem[0]] = decodeURI(parsedItem[1]);
32 | });
33 | }
34 | return parsedItems;
35 | }
36 |
37 | /*
38 | * @params {request} extracted from request response, {setLocalhost} your localhost address
39 | * @return {object} objects of protocol, host and origin
40 | */
41 | export function absoluteUrl(req, setLocalhost) {
42 | var protocol = 'https:';
43 | var host = req
44 | ? req.headers['x-forwarded-host'] || req.headers['host']
45 | : window.location.host;
46 | if (host.indexOf('localhost') > -1) {
47 | if (setLocalhost) host = setLocalhost;
48 | protocol = 'http:';
49 | }
50 | return {
51 | protocol: protocol,
52 | host: host,
53 | origin: protocol + '//' + host,
54 | url: req,
55 | };
56 | }
57 |
58 | /*
59 | * @params {none} set action for logout and remove cookie
60 | * @return {function} router function to redirect
61 | */
62 | export function setLogout(e) {
63 | e.preventDefault();
64 | Cookies.remove('token');
65 | Router.push('/');
66 | }
67 |
--------------------------------------------------------------------------------
/pages/about.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Link from 'next/link';
3 |
4 | /* middleware */
5 | import {
6 | absoluteUrl,
7 | getAppCookies,
8 | verifyToken,
9 | setLogout,
10 | } from '../middleware/utils';
11 |
12 | /* components */
13 | import Layout from '../components/layout/Layout';
14 |
15 | export default function About(props) {
16 | const { profile } = props;
17 |
18 | function handleOnClickLogout(e) {
19 | setLogout(e);
20 | }
21 |
22 | return (
23 |
24 |
25 |
26 | About Page
27 | {!profile ? (
28 | Login to continue
29 | ) : (
30 |
31 |
39 |
40 |
48 |
49 |
50 | )}
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | export async function getServerSideProps(context) {
58 | const { req } = context;
59 | const { origin } = absoluteUrl(req);
60 |
61 | const baseApiUrl = `${origin}/api/about`;
62 |
63 | const { token } = getAppCookies(req);
64 | const profile = token ? verifyToken(token.split(' ')[1]) : '';
65 |
66 | return {
67 | props: {
68 | baseApiUrl,
69 | profile,
70 | },
71 | };
72 | }
73 |
--------------------------------------------------------------------------------
/components/form/FormRegister.jsx:
--------------------------------------------------------------------------------
1 | function FormRegister({ props }) {
2 | const {
3 | onSubmitHandler,
4 | onChangeHandler,
5 | stateFormData,
6 | stateFormError,
7 | stateFormMessage,
8 | } = props;
9 |
10 | return (
11 |
79 | );
80 | }
81 | export default FormRegister;
82 |
--------------------------------------------------------------------------------
/components/form/FormJob.jsx:
--------------------------------------------------------------------------------
1 | function FormJob(props) {
2 | const {
3 | onSubmit,
4 | onChange,
5 | stateFormData,
6 | stateFormError,
7 | stateFormValid,
8 | stateFormMessage,
9 | } = props;
10 | return (
11 |
89 | );
90 | }
91 | export default FormJob;
92 |
--------------------------------------------------------------------------------
/auth.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/pages/api/auth.js:
--------------------------------------------------------------------------------
1 | import bcrypt from 'bcryptjs';
2 | import jwt from 'jsonwebtoken';
3 |
4 | /* JWT secret key */
5 | const KEY = process.env.JWT_KEY;
6 | /* Users collection sample */
7 | const USERS = [
8 | {
9 | id: 1,
10 | email: 'example1@example.com',
11 | password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq', // password
12 | createdAt: '2020-06-14 18:23:45',
13 | },
14 | {
15 | id: 2,
16 | email: 'example2@example.com',
17 | password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq', // password
18 | createdAt: '2020-06-14 18:23:45',
19 | },
20 | {
21 | id: 3,
22 | email: 'example3@example.com',
23 | password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq', // password
24 | createdAt: '2020-06-14 18:23:45',
25 | },
26 | {
27 | id: 4,
28 | email: 'example4@example.com',
29 | password: '$2y$10$mj1OMFvVmGAR4gEEXZGtA.R5wYWBZTis72hSXzpxEs.QoXT3ifKSq', // password
30 | createdAt: '2020-06-14 18:23:45',
31 | },
32 | ];
33 |
34 | export default (req, res) => {
35 | return new Promise(resolve => {
36 | const { method } = req;
37 | try {
38 | switch (method) {
39 | case 'POST':
40 | /* Get Post Data */
41 | const { email, password } = req.body;
42 | /* Any how email or password is blank */
43 | if (!email || !password) {
44 | return res.status(400).json({
45 | status: 'error',
46 | error: 'Request missing username or password',
47 | });
48 | }
49 | /* Check user email in database */
50 | const user = USERS.find(user => {
51 | return user.email === email;
52 | });
53 | /* Check if exists */
54 | if (!user) {
55 | /* Send error with message */
56 | res.status(400).json({ status: 'error', error: 'User Not Found' });
57 | }
58 | /* Variables checking */
59 | if (user) {
60 | const userId = user.id,
61 | userEmail = user.email,
62 | userPassword = user.password,
63 | userCreated = user.createdAt;
64 | /* Check and compare password */
65 | bcrypt.compare(password, userPassword).then(isMatch => {
66 | /* User matched */
67 | if (isMatch) {
68 | /* Create JWT Payload */
69 | const payload = {
70 | id: userId,
71 | email: userEmail,
72 | createdAt: userCreated,
73 | };
74 | /* Sign token */
75 | jwt.sign(
76 | payload,
77 | KEY,
78 | {
79 | expiresIn: 31556926, // 1 year in seconds
80 | },
81 | (err, token) => {
82 | /* Send succes with token */
83 | res.status(200).json({
84 | success: true,
85 | token: 'Bearer ' + token,
86 | });
87 | },
88 | );
89 | } else {
90 | /* Send error with message */
91 | res
92 | .status(400)
93 | .json({ status: 'error', error: 'Password incorrect' });
94 | }
95 | });
96 | }
97 | break;
98 | case 'PUT':
99 | break;
100 | case 'PATCH':
101 | break;
102 | default:
103 | break;
104 | }
105 | } catch (error) {
106 | throw error;
107 | }
108 | return resolve();
109 | });
110 | };
111 |
--------------------------------------------------------------------------------
/pages/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import Link from 'next/link';
3 | import Router from 'next/router';
4 | import Cookies from 'js-cookie';
5 |
6 | /* middleware */
7 | import {
8 | absoluteUrl,
9 | getAppCookies,
10 | verifyToken,
11 | setLogout,
12 | } from '../middleware/utils';
13 |
14 | /* components */
15 | import FormLogin from '../components/form/FormLogin';
16 | import Layout from '../components/layout/Layout';
17 |
18 | const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,2|3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
19 |
20 | /* login schemas */
21 | const FORM_DATA_LOGIN = {
22 | email: {
23 | value: '',
24 | label: 'Email',
25 | min: 10,
26 | max: 36,
27 | required: true,
28 | validator: {
29 | regEx: emailRegEx,
30 | error: 'Please insert valid email',
31 | },
32 | },
33 | password: {
34 | value: '',
35 | label: 'Password',
36 | min: 6,
37 | max: 36,
38 | required: true,
39 | validator: {
40 | regEx: /^[a-z\sA-Z0-9\W\w]+$/,
41 | error: 'Please insert valid password',
42 | },
43 | },
44 | };
45 |
46 | export default function Home(props) {
47 | const { baseApiUrl, profile } = props;
48 | const [stateFormData, setStateFormData] = useState(FORM_DATA_LOGIN);
49 | const [stateFormError, setStateFormError] = useState([]);
50 | const [stateFormValid, setStateFormValid] = useState(false);
51 | const [loading, setLoading] = useState(false);
52 | const [stateFormMessage, setStateFormMessage] = useState({});
53 |
54 | function onChangeHandler(e) {
55 | const { name, value } = e.currentTarget;
56 |
57 | setStateFormData({
58 | ...stateFormData,
59 | [name]: {
60 | ...stateFormData[name],
61 | value,
62 | },
63 | });
64 |
65 | /* validation handler */
66 | validationHandler(stateFormData, e);
67 | }
68 |
69 | async function onSubmitHandler(e) {
70 | e.preventDefault();
71 |
72 | let data = { ...stateFormData };
73 |
74 | /* email */
75 | data = { ...data, email: data.email.value || '' };
76 | /* password */
77 | data = { ...data, password: data.password.value || '' };
78 |
79 | /* validation handler */
80 | const isValid = validationHandler(stateFormData);
81 |
82 | if (isValid) {
83 | // Call an external API endpoint to get posts.
84 | // You can use any data fetching library
85 | setLoading(!loading);
86 | const loginApi = await fetch(`${baseApiUrl}/auth`, {
87 | method: 'POST',
88 | headers: {
89 | Accept: 'application/json',
90 | 'Content-Type': 'application/json',
91 | },
92 | body: JSON.stringify(data),
93 | }).catch(error => {
94 | console.error('Error:', error);
95 | });
96 | let result = await loginApi.json();
97 | if (result.success && result.token) {
98 | Cookies.set('token', result.token);
99 | // window.location.href = referer ? referer : "/";
100 | // const pathUrl = referer ? referer.lastIndexOf("/") : "/";
101 | Router.push('/');
102 | } else {
103 | setStateFormMessage(result);
104 | }
105 | setLoading(false);
106 | }
107 | }
108 |
109 | function validationHandler(states, e) {
110 | const input = (e && e.target.name) || '';
111 | const errors = [];
112 | let isValid = true;
113 |
114 | if (input) {
115 | if (states[input].required) {
116 | if (!states[input].value) {
117 | errors[input] = {
118 | hint: `${states[e.target.name].label} required`,
119 | isInvalid: true,
120 | };
121 | isValid = false;
122 | }
123 | }
124 | if (
125 | states[input].value &&
126 | states[input].min > states[input].value.length
127 | ) {
128 | errors[input] = {
129 | hint: `Field ${states[input].label} min ${states[input].min}`,
130 | isInvalid: true,
131 | };
132 | isValid = false;
133 | }
134 | if (
135 | states[input].value &&
136 | states[input].max < states[input].value.length
137 | ) {
138 | errors[input] = {
139 | hint: `Field ${states[input].label} max ${states[input].max}`,
140 | isInvalid: true,
141 | };
142 | isValid = false;
143 | }
144 | if (
145 | states[input].validator !== null &&
146 | typeof states[input].validator === 'object'
147 | ) {
148 | if (
149 | states[input].value &&
150 | !states[input].validator.regEx.test(states[input].value)
151 | ) {
152 | errors[input] = {
153 | hint: states[input].validator.error,
154 | isInvalid: true,
155 | };
156 | isValid = false;
157 | }
158 | }
159 | } else {
160 | Object.entries(states).forEach(item => {
161 | item.forEach(field => {
162 | errors[item[0]] = '';
163 | if (field.required) {
164 | if (!field.value) {
165 | errors[item[0]] = {
166 | hint: `${field.label} required`,
167 | isInvalid: true,
168 | };
169 | isValid = false;
170 | }
171 | }
172 | if (field.value && field.min >= field.value.length) {
173 | errors[item[0]] = {
174 | hint: `Field ${field.label} min ${field.min}`,
175 | isInvalid: true,
176 | };
177 | isValid = false;
178 | }
179 | if (field.value && field.max <= field.value.length) {
180 | errors[item[0]] = {
181 | hint: `Field ${field.label} max ${field.max}`,
182 | isInvalid: true,
183 | };
184 | isValid = false;
185 | }
186 | if (field.validator !== null && typeof field.validator === 'object') {
187 | if (field.value && !field.validator.regEx.test(field.value)) {
188 | errors[item[0]] = {
189 | hint: field.validator.error,
190 | isInvalid: true,
191 | };
192 | isValid = false;
193 | }
194 | }
195 | });
196 | });
197 | }
198 | if (isValid) {
199 | setStateFormValid(isValid);
200 | }
201 | setStateFormError({
202 | ...errors,
203 | });
204 | return isValid;
205 | }
206 |
207 | function handleOnClickLogout(e) {
208 | setLogout(e);
209 | }
210 |
211 | return (
212 |
213 |
214 |
215 |
216 | Welcome to Next.js!
217 |
218 | JWT Authentication
219 | {!profile ? (
220 | <>
221 | Login to continue
222 |
223 | Use : example1@example.com or{' '}
224 | example2@example.com with the password:
225 | password
226 |
227 |
228 |
238 |
239 | >
240 | ) : (
241 |
249 | )}
250 |
251 |
252 |
253 | );
254 | }
255 |
256 | export async function getServerSideProps(context) {
257 | const { req } = context;
258 | const { origin } = absoluteUrl(req);
259 |
260 | const baseApiUrl = `${origin}/api`;
261 |
262 | const { token } = getAppCookies(req);
263 | const profile = token ? verifyToken(token.split(' ')[1]) : '';
264 | return {
265 | props: {
266 | baseApiUrl,
267 | profile,
268 | },
269 | };
270 | }
271 |
--------------------------------------------------------------------------------
/components/layout/Layout.jsx:
--------------------------------------------------------------------------------
1 | /* next.js head */
2 | import Head from 'next/head';
3 |
4 | /* components */
5 | import Header from '../header/Header';
6 | import Footer from '../footer/Footer';
7 |
8 | export default function Layout({
9 | children,
10 | title = 'Next.js with JWT Authentication | A boilerplate JWT Authentication and Next.js from dyarfi.github.io',
11 | description = 'Next.js with JWT Authentication | A boilerplate JWT Authentication and Next.js from dyarfi.github.io',
12 | keywords = 'Next.js, JWT, Json Web Tokens, Authentication, Application',
13 | type = 'website',
14 | url = '/',
15 | image = '/nextjs.svg',
16 | origin = '',
17 | }) {
18 | return (
19 |
20 |
21 |
22 |
{title}
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
48 |
52 |
53 |
54 | {children}
55 |
56 |
495 | {process.env.NODE_ENV !== 'development' && (
496 | <>
497 |
501 |
508 | >
509 | )}
510 |
511 | );
512 | }
513 |
--------------------------------------------------------------------------------
/schemas.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
--------------------------------------------------------------------------------