├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── getting-started
├── .babelrc
├── node_modules
│ └── .bin
│ │ ├── babel-node
│ │ ├── next
│ │ └── nodemon
├── nodemon.json
├── package.json
├── pages
│ ├── _document.jsx
│ ├── index.js
│ ├── login.jsx
│ └── register.jsx
├── server
│ └── index.js
└── static
│ └── favicon.ico
├── package.json
├── passport-facebook-strategy
├── .babelrc
├── components
│ └── FacebookLoginButton.jsx
├── next.config.js
├── nodemon.json
├── package.json
├── pages
│ ├── _document.jsx
│ ├── index.js
│ ├── login.jsx
│ └── register.jsx
├── server
│ ├── auth
│ │ ├── index.js
│ │ ├── strategies
│ │ │ ├── facebook.js
│ │ │ ├── index.js
│ │ │ └── jwt.js
│ │ └── utils.js
│ ├── database
│ │ ├── connection.js
│ │ ├── schema
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── user
│ │ │ ├── create.js
│ │ │ ├── get.js
│ │ │ └── index.js
│ ├── index.js
│ └── router
│ │ ├── auth.routes.js
│ │ └── index.js
├── static
│ └── favicon.ico
└── utils
│ ├── index.js
│ └── server.js
├── passport-google-oauth-strategy
├── .babelrc
├── components
│ └── GoogleLoginButton.jsx
├── next.config.js
├── nodemon.json
├── package.json
├── pages
│ ├── _document.jsx
│ ├── index.js
│ ├── login.jsx
│ └── register.jsx
├── server
│ ├── auth
│ │ ├── index.js
│ │ ├── strategies
│ │ │ ├── google.js
│ │ │ ├── index.js
│ │ │ └── jwt.js
│ │ └── utils.js
│ ├── database
│ │ ├── connection.js
│ │ ├── schema
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── user
│ │ │ ├── create.js
│ │ │ ├── get.js
│ │ │ └── index.js
│ ├── index.js
│ └── router
│ │ ├── auth.routes.js
│ │ └── index.js
├── static
│ └── favicon.ico
└── utils
│ ├── index.js
│ └── server.js
├── passport-jwt-mongo
├── .babelrc
├── next.config.js
├── nodemon.json
├── package.json
├── pages
│ ├── _document.jsx
│ ├── index.js
│ ├── login.jsx
│ └── register.jsx
├── server
│ ├── auth
│ │ ├── index.js
│ │ ├── strategies
│ │ │ ├── index.js
│ │ │ └── jwt.js
│ │ └── utils.js
│ ├── database
│ │ ├── connection.js
│ │ ├── schema
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── user
│ │ │ ├── create.js
│ │ │ ├── get.js
│ │ │ └── index.js
│ ├── index.js
│ └── router
│ │ ├── auth.routes.js
│ │ └── index.js
├── static
│ └── favicon.ico
└── utils
│ ├── index.js
│ └── server.js
├── passport-role-based-authorisation
├── .babelrc
├── components
│ ├── FacebookLoginButton.jsx
│ └── GoogleLoginButton.jsx
├── db.js
├── next.config.js
├── nodemon.json
├── package-lock.json
├── package.json
├── pages
│ ├── _document.jsx
│ ├── admin-dashboard.jsx
│ ├── anonymous-dashboard.jsx
│ ├── both-dashboard.jsx
│ ├── customer-dashboard.jsx
│ ├── index.js
│ ├── login.jsx
│ └── register.jsx
├── server
│ ├── auth
│ │ ├── index.js
│ │ ├── strategies
│ │ │ ├── facebook.js
│ │ │ ├── google.js
│ │ │ ├── index.js
│ │ │ └── jwt.js
│ │ └── utils.js
│ ├── database
│ │ ├── connection.js
│ │ ├── schema
│ │ │ ├── index.js
│ │ │ └── user.js
│ │ └── user
│ │ │ ├── create.js
│ │ │ ├── get.js
│ │ │ └── index.js
│ ├── index.js
│ └── router
│ │ ├── auth.routes.js
│ │ └── index.js
├── static
│ └── favicon.ico
└── utils
│ ├── index.js
│ ├── roles.js
│ └── server.js
└── yarn.lock
/.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 | .next
26 | .env
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 90,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": false,
6 | "singleQuote": true,
7 | "trailingComma": "none",
8 | "bracketSpacing": true,
9 | "jsxBracketSameLine": false,
10 | "arrowParens": "avoid"
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/.next": true,
4 | "getting-started": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Build an authentication system using Node.js, Express, and Passport.js
2 |
3 | The repository contains the resulting code from our mini-series on Authentication using Node.js, Express, and Passport.js. The comprehensive series is designed to teach you everything you need to know to add authentication and authorisation to your existing website, or for a new-build from-scratch project.
4 |
5 | You will learn;
6 |
7 | - How to set-up Next.js (React.js), with a custom Express.js back-end
8 | - How to build a login and registration page, using Material UI
9 | - How to set-up Passport.js with JSON Web Tokens (JWT)
10 | - How to make your application flexible and configurable using dotenv
11 | - How to set-up MongoDB using Docker. We will also use Mongoose to simplify database calls, and MongoDB Memory Server for effective unit testing
12 | - How to extend the login/registration options with both Google and Facebook Passport strategies
13 | - How to introduce role-based authorisation, so you can restrict access to certain sections of the website.
14 |
15 | Weather you’re a seasoned developer or just starting out, don’t worry. We take care to explain the details that matter in a simple and straightforward language that is approachable to all.
16 |
17 | The tutorial series can be found on [DeveloperHandbook.com](https://developerhandbook.com/passport.js/node-express-passport-authentication-mini-series/).
18 |
--------------------------------------------------------------------------------
/getting-started/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/getting-started/node_modules/.bin/babel-node:
--------------------------------------------------------------------------------
1 | ../../../node_modules/@babel/node/bin/babel-node.js
--------------------------------------------------------------------------------
/getting-started/node_modules/.bin/next:
--------------------------------------------------------------------------------
1 | ../../../node_modules/next/dist/bin/next
--------------------------------------------------------------------------------
/getting-started/node_modules/.bin/nodemon:
--------------------------------------------------------------------------------
1 | ../../../node_modules/nodemon/bin/nodemon.js
--------------------------------------------------------------------------------
/getting-started/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["server"],
3 | "exec": "NODE_ENV=development babel-node server/index.js",
4 | "ext": "js"
5 | }
6 |
--------------------------------------------------------------------------------
/getting-started/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nodemon",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.4.3",
12 | "express": "^4.17.1",
13 | "next": "9.3.3",
14 | "react": "^16.10.1",
15 | "react-dom": "^16.10.1"
16 | },
17 | "devDependencies": {
18 | "@babel/node": "^7.6.2",
19 | "@babel/preset-env": "^7.6.2",
20 | "nodemon": "^1.19.3"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/getting-started/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheets } from '@material-ui/styles'
4 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles'
5 |
6 | const theme = responsiveFontSizes(createMuiTheme())
7 |
8 | class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
23 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | MyDocument.getInitialProps = async ctx => {
53 | // Resolution order
54 | //
55 | // On the server:
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. document.getInitialProps
59 | // 4. app.render
60 | // 5. page.render
61 | // 6. document.render
62 | //
63 | // On the server with error:
64 | // 1. document.getInitialProps
65 | // 2. app.render
66 | // 3. page.render
67 | // 4. document.render
68 | //
69 | // On the client
70 | // 1. app.getInitialProps
71 | // 2. page.getInitialProps
72 | // 3. app.render
73 | // 4. page.render
74 |
75 | // Render app and page and get the context of the page with collected side effects.
76 | const sheets = new ServerStyleSheets()
77 | const originalRenderPage = ctx.renderPage
78 |
79 | ctx.renderPage = () =>
80 | originalRenderPage({
81 | enhanceApp: App => props => sheets.collect()
82 | })
83 |
84 | const initialProps = await Document.getInitialProps(ctx)
85 |
86 | return {
87 | ...initialProps,
88 | // Styles fragment is rendered after the app and page rendering finish.
89 | styles: [
90 |
91 | {initialProps.styles}
92 | {sheets.getStyleElement()}
93 |
94 | ]
95 | }
96 | }
97 |
98 | export default MyDocument
99 |
--------------------------------------------------------------------------------
/getting-started/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 |
4 | const Home = () => (
5 |
6 |
7 |
Home
8 |
9 |
10 |
11 |
12 |
Welcome to this awesome tutorial
13 |
16 |
17 |
18 | )
19 |
20 | export default Home
21 |
--------------------------------------------------------------------------------
/getting-started/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import Box from '@material-ui/core/Box'
5 | import CircularProgress from '@material-ui/core/CircularProgress'
6 | import Typography from '@material-ui/core/Typography'
7 | import TextField from '@material-ui/core/TextField'
8 | import Paper from '@material-ui/core/Paper'
9 |
10 | const useStyles = makeStyles(theme => ({
11 | layout: {
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center'
15 | },
16 | paper: {
17 | padding: theme.spacing(2),
18 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
19 | marginTop: theme.spacing(8),
20 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
21 | }
22 | },
23 | submit: {
24 | margin: theme.spacing(3, 0, 3)
25 | },
26 | form: {
27 | width: '100%', // Fix IE 11 issue.
28 | marginTop: theme.spacing(1)
29 | },
30 | buttonProgress: {
31 | position: 'absolute',
32 | top: '50%',
33 | left: '50%',
34 | marginTop: -12,
35 | marginLeft: -12
36 | }
37 | }))
38 |
39 | const LoginForm = () => {
40 | const classes = useStyles({})
41 | const [formData, setFormData] = React.useState({ email: '', password: '' })
42 | const [submitting, setSubmitting] = React.useState(false)
43 |
44 | return (
45 |
46 |
47 |
53 |
54 | Login
55 |
56 |
57 | Log in to your account dashboard
58 |
59 |
60 |
101 |
102 |
103 | )
104 | }
105 |
106 | export default LoginForm
107 |
--------------------------------------------------------------------------------
/getting-started/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Box from '@material-ui/core/Box'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import Paper from '@material-ui/core/Paper'
6 | import TextField from '@material-ui/core/TextField'
7 | import Button from '@material-ui/core/Button'
8 |
9 | const useStyles = makeStyles(theme => ({
10 | layout: {
11 | display: 'flex',
12 | flexDirection: 'column',
13 | alignItems: 'center',
14 | maxWidth: '768px',
15 | margin: '0 auto'
16 | },
17 | paper: {
18 | padding: theme.spacing(2),
19 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
20 | marginTop: theme.spacing(8),
21 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
22 | }
23 | },
24 | submit: {
25 | margin: theme.spacing(3, 0, 2)
26 | },
27 | form: {
28 | width: '100%', // Fix IE 11 issue.
29 | marginTop: theme.spacing(1)
30 | },
31 | buttonProgress: {
32 | position: 'absolute',
33 | top: '50%',
34 | left: '50%',
35 | marginTop: -12,
36 | marginLeft: -12
37 | }
38 | }))
39 |
40 | const Register = () => {
41 | const classes = useStyles({})
42 | const [formData, setFormData] = React.useState({
43 | firstName: '',
44 | lastName: '',
45 | email: '',
46 | password: ''
47 | })
48 | const [submitting, setSubmitting] = React.useState(false)
49 |
50 | return (
51 |
52 |
53 |
59 |
60 | Register
61 |
62 |
63 |
126 |
127 |
128 | )
129 | }
130 |
131 | export default Register
132 |
--------------------------------------------------------------------------------
/getting-started/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import next from 'next'
3 |
4 | const dev = process.env.NODE_ENV !== 'production'
5 | const nextApp = next({ dev })
6 | const handle = nextApp.getRequestHandler()
7 |
8 | const port = 3000
9 |
10 | nextApp.prepare().then(() => {
11 | const app = express()
12 |
13 | app.get('/my-custom-route', (req, res) =>
14 | res.status(200).json({ hello: 'Hello, from the back-end world!' })
15 | )
16 |
17 | app.get('*', (req, res) => {
18 | return handle(req, res)
19 | })
20 |
21 | app.listen(port, err => {
22 | if (err) throw err
23 | console.log(`> Ready on localhost:${port}`)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/getting-started/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpreecedev/passport-next-auth-tutorial/9fe9013c5f99a179773b60b8ae3f3753529c6d16/getting-started/static/favicon.ico
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "workspaces": [
4 | "getting-started",
5 | "passport-jwt-mongo",
6 | "passport-google-oauth-strategy",
7 | "passport-facebook-strategy",
8 | "passport-role-based-authorisation"
9 | ],
10 | "name": "passport-next-auth-tutorial",
11 | "version": "1.0.0",
12 | "keywords": [],
13 | "author": "Jon Preece",
14 | "license": "MIT"
15 | }
16 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/components/FacebookLoginButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent } from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 |
4 | const useStyles = makeStyles(theme =>
5 | createStyles({
6 | button: {
7 | display: 'flex',
8 | backgroundColor: '#4C69BA',
9 | backgroundImage: 'linear-gradient(#4C69BA, #3B55A0)',
10 | borderRadius: theme.shape.borderRadius,
11 | boxShadow: theme.shadows[1],
12 | height: '36px',
13 | cursor: 'pointer',
14 | textDecoration: 'none',
15 | '&:hover': {
16 | backgroundColor: '#5B7BD5',
17 | backgroundImage: 'linear-gradient(#5b7bd50a, #4864B1)'
18 | },
19 | '&:active': {
20 | boxShadow: 'inset 0 0 0 32px rgba(0,0,0,0.1)'
21 | }
22 | },
23 | wrapper: {
24 | marginTop: '1px',
25 | marginLeft: '1px',
26 | display: 'flex',
27 | justifyContent: 'center',
28 | alignItems: 'center',
29 | width: '34px',
30 | height: '34px',
31 | borderRadius: '2px',
32 | backgroundColor: '#fff'
33 | },
34 | icon: {
35 | width: '18px',
36 | height: '18px'
37 | },
38 | text: {
39 | margin: '0 34px 0 0',
40 | color: '#fff',
41 | fontSize: '14px',
42 | fontWeight: 'bold',
43 | textTransform: 'uppercase',
44 | flexGrow: 1,
45 | textAlign: 'center',
46 | alignSelf: 'center'
47 | }
48 | })
49 | )
50 |
51 | const FacebookLoginButton = () => {
52 | const classes = useStyles({})
53 |
54 | return (
55 |
56 |
67 | Login with Facebook
68 |
69 | )
70 | }
71 |
72 | export { FacebookLoginButton }
73 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/next.config.js:
--------------------------------------------------------------------------------
1 | const { parsed: localEnv } = require('dotenv').config()
2 |
3 | module.exports = {
4 | env: {
5 | BASE_API_URL: localEnv.BASE_API_URL
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["server"],
3 | "exec": "NODE_ENV=development babel-node server/index.js",
4 | "ext": "js"
5 | }
6 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "passport-facebook-strategy",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nodemon",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.4.3",
12 | "await-to-js": "^2.1.1",
13 | "bcrypt": "^3.0.6",
14 | "body-parser": "^1.19.0",
15 | "cookie-parser": "^1.4.4",
16 | "dotenv": "^8.1.0",
17 | "express": "^4.17.1",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^5.7.5",
20 | "next": "9.3.2",
21 | "passport": "^0.4.0",
22 | "passport-facebook": "^3.0.0",
23 | "passport-jwt": "^4.0.0",
24 | "react": "^16.10.1",
25 | "react-dom": "^16.10.1"
26 | },
27 | "devDependencies": {
28 | "@babel/node": "^7.6.2",
29 | "@babel/preset-env": "^7.6.2",
30 | "nodemon": "^1.19.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheets } from '@material-ui/styles'
4 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles'
5 |
6 | const theme = responsiveFontSizes(createMuiTheme())
7 |
8 | class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
23 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | MyDocument.getInitialProps = async ctx => {
53 | // Resolution order
54 | //
55 | // On the server:
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. document.getInitialProps
59 | // 4. app.render
60 | // 5. page.render
61 | // 6. document.render
62 | //
63 | // On the server with error:
64 | // 1. document.getInitialProps
65 | // 2. app.render
66 | // 3. page.render
67 | // 4. document.render
68 | //
69 | // On the client
70 | // 1. app.getInitialProps
71 | // 2. page.getInitialProps
72 | // 3. app.render
73 | // 4. page.render
74 |
75 | // Render app and page and get the context of the page with collected side effects.
76 | const sheets = new ServerStyleSheets()
77 | const originalRenderPage = ctx.renderPage
78 |
79 | ctx.renderPage = () =>
80 | originalRenderPage({
81 | enhanceApp: App => props => sheets.collect()
82 | })
83 |
84 | const initialProps = await Document.getInitialProps(ctx)
85 |
86 | return {
87 | ...initialProps,
88 | // Styles fragment is rendered after the app and page rendering finish.
89 | styles: [
90 |
91 | {initialProps.styles}
92 | {sheets.getStyleElement()}
93 |
94 | ]
95 | }
96 | }
97 |
98 | export default MyDocument
99 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 |
4 | const Home = () => (
5 |
6 |
7 |
Home
8 |
9 |
10 |
11 |
12 |
Welcome to this awesome tutorial
13 |
16 |
17 |
18 | )
19 |
20 | export default Home
21 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import Box from '@material-ui/core/Box'
5 | import CircularProgress from '@material-ui/core/CircularProgress'
6 | import Typography from '@material-ui/core/Typography'
7 | import TextField from '@material-ui/core/TextField'
8 | import Paper from '@material-ui/core/Paper'
9 | import { server } from '../utils'
10 | import { FacebookLoginButton } from '../components/FacebookLoginButton'
11 |
12 | const useStyles = makeStyles(theme => ({
13 | layout: {
14 | display: 'flex',
15 | flexDirection: 'column',
16 | alignItems: 'center'
17 | },
18 | paper: {
19 | padding: theme.spacing(2),
20 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
21 | marginTop: theme.spacing(8),
22 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
23 | }
24 | },
25 | submit: {
26 | margin: theme.spacing(3, 0, 3)
27 | },
28 | form: {
29 | width: '100%', // Fix IE 11 issue.
30 | marginTop: theme.spacing(1)
31 | },
32 | buttonProgress: {
33 | position: 'absolute',
34 | top: '50%',
35 | left: '50%',
36 | marginTop: -12,
37 | marginLeft: -12
38 | }
39 | }))
40 |
41 | const LoginForm = () => {
42 | const classes = useStyles({})
43 | const [formData, setFormData] = React.useState({ email: '', password: '' })
44 | const [submitting, setSubmitting] = React.useState(false)
45 |
46 | const handleSubmit = async e => {
47 | e.preventDefault()
48 | const { email, password } = formData
49 | const { success, data } = await server.postAsync('/auth/login', {
50 | email,
51 | password
52 | })
53 |
54 | if (success) {
55 | window.location.replace(data)
56 | return
57 | }
58 | }
59 |
60 | return (
61 |
62 |
63 |
69 |
70 | Login
71 |
72 |
73 | Log in to your account dashboard
74 |
75 |
76 |
121 |
122 |
123 | )
124 | }
125 |
126 | export default LoginForm
127 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Box from '@material-ui/core/Box'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import Paper from '@material-ui/core/Paper'
6 | import TextField from '@material-ui/core/TextField'
7 | import Button from '@material-ui/core/Button'
8 | import { server } from '../utils'
9 |
10 | const useStyles = makeStyles(theme => ({
11 | layout: {
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center',
15 | maxWidth: '768px',
16 | margin: '0 auto'
17 | },
18 | paper: {
19 | padding: theme.spacing(2),
20 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
21 | marginTop: theme.spacing(8),
22 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
23 | }
24 | },
25 | submit: {
26 | margin: theme.spacing(3, 0, 2)
27 | },
28 | form: {
29 | width: '100%', // Fix IE 11 issue.
30 | marginTop: theme.spacing(1)
31 | },
32 | buttonProgress: {
33 | position: 'absolute',
34 | top: '50%',
35 | left: '50%',
36 | marginTop: -12,
37 | marginLeft: -12
38 | }
39 | }))
40 |
41 | const Register = () => {
42 | const classes = useStyles({})
43 | const [formData, setFormData] = React.useState({
44 | firstName: '',
45 | lastName: '',
46 | email: '',
47 | password: ''
48 | })
49 | const [submitting, setSubmitting] = React.useState(false)
50 |
51 | const handleSubmit = async e => {
52 | e.preventDefault()
53 | const { firstName, lastName, email, password } = formData
54 | const { success, data } = await server.postAsync('/auth/register', {
55 | firstName,
56 | lastName,
57 | email,
58 | password
59 | })
60 | if (success) {
61 | window.location.replace(data)
62 | return
63 | }
64 | }
65 |
66 | return (
67 |
68 |
69 |
75 |
76 | Register
77 |
78 |
79 |
142 |
143 |
144 | )
145 | }
146 |
147 | export default Register
148 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/auth/index.js:
--------------------------------------------------------------------------------
1 | import * as utils from './utils'
2 | import * as strategies from './strategies'
3 |
4 | const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args)
5 |
6 | const initialiseAuthentication = app => {
7 | utils.setup()
8 |
9 | pipe(
10 | strategies.FacebookStrategy,
11 | strategies.JWTStrategy
12 | )(app)
13 | }
14 |
15 | export { utils, initialiseAuthentication, strategies }
16 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/auth/strategies/facebook.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportFacebook from 'passport-facebook'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserByProviderId, createUser } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const FacebookStrategy = passportFacebook.Strategy
9 |
10 | const strategy = app => {
11 | const strategyOptions = {
12 | clientID: process.env.FACEBOOK_APP_ID,
13 | clientSecret: process.env.FACEBOOK_APP_SECRET,
14 | callbackURL: `${process.env.SERVER_API_URL}/auth/facebook/callback`,
15 | profileFields: ['id', 'displayName', 'name', 'emails']
16 | }
17 |
18 | const verifyCallback = async (accessToken, refreshToken, profile, done) => {
19 | let [err, user] = await to(getUserByProviderId(profile.id))
20 | if (err || user) {
21 | return done(err, user)
22 | }
23 |
24 | const [createdError, createdUser] = await to(
25 | createUser({
26 | providerId: profile.id,
27 | provider: profile.provider,
28 | firstName: profile.name.givenName,
29 | lastName: profile.name.familyName,
30 | displayName: profile.displayName,
31 | email: profile.emails[0].value,
32 | password: null
33 | })
34 | )
35 |
36 | return done(createdError, createdUser)
37 | }
38 |
39 | passport.use(new FacebookStrategy(strategyOptions, verifyCallback))
40 |
41 | app.get(`${process.env.BASE_API_URL}/auth/facebook`, passport.authenticate('facebook'))
42 |
43 | app.get(
44 | `${process.env.BASE_API_URL}/auth/facebook/callback`,
45 | passport.authenticate('facebook', { failureRedirect: '/login' }),
46 | (req, res) => {
47 | return res
48 | .status(200)
49 | .cookie('jwt', signToken(req.user), {
50 | httpOnly: true
51 | })
52 | .redirect('/')
53 | }
54 | )
55 |
56 | return app
57 | }
58 |
59 | export { strategy }
60 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/auth/strategies/index.js:
--------------------------------------------------------------------------------
1 | import { strategy as JWTStrategy } from './jwt'
2 | import { strategy as FacebookStrategy } from './facebook'
3 |
4 | export { JWTStrategy, FacebookStrategy }
5 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/auth/strategies/jwt.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportJWT from 'passport-jwt'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserById } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const JWTStrategy = passportJWT.Strategy
9 |
10 | const strategy = () => {
11 | const strategyOptions = {
12 | jwtFromRequest: req => req.cookies.jwt,
13 | secretOrKey: process.env.JWT_SECRET,
14 | passReqToCallback: true
15 | }
16 |
17 | const verifyCallback = async (req, jwtPayload, cb) => {
18 | const [err, user] = await to(getUserById(jwtPayload.data._id))
19 |
20 | if (err) {
21 | return cb(err)
22 | }
23 | req.user = user
24 | return cb(null, user)
25 | }
26 |
27 | passport.use(new JWTStrategy(strategyOptions, verifyCallback))
28 | }
29 |
30 | const login = (req, user) => {
31 | return new Promise((resolve, reject) => {
32 | req.login(user, { session: false }, err => {
33 | if (err) {
34 | return reject(err)
35 | }
36 |
37 | return resolve(signToken(user))
38 | })
39 | })
40 | }
41 |
42 | export { strategy, login }
43 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/auth/utils.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import jwt from 'jsonwebtoken'
3 | import bcrypt from 'bcrypt'
4 | import { UserModel } from '../database/schema'
5 |
6 | const setup = () => {
7 | passport.serializeUser((user, done) => done(null, user._id))
8 |
9 | passport.deserializeUser(async (id, done) => {
10 | try {
11 | const user = await UserModel.findById(id)
12 | return done(null, user)
13 | } catch (err) {
14 | return done(err, null)
15 | }
16 | })
17 | }
18 |
19 | const signToken = user => {
20 | return jwt.sign({ data: user }, process.env.JWT_SECRET, {
21 | expiresIn: 604800
22 | })
23 | }
24 |
25 | const hashPassword = async password => {
26 | if (!password) {
27 | throw new Error('Password was not provided')
28 | }
29 |
30 | const salt = await bcrypt.genSalt(10)
31 | return await bcrypt.hash(password, salt)
32 | }
33 |
34 | const verifyPassword = async (candidate, actual) => {
35 | return await bcrypt.compare(candidate, actual)
36 | }
37 |
38 | export { setup, signToken, hashPassword, verifyPassword }
39 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/connection.js:
--------------------------------------------------------------------------------
1 | import { connect, connection } from 'mongoose'
2 |
3 | const connectToDatabase = async () =>
4 | await connect(
5 | process.env.DB_CONNECTION_STRING || '',
6 | {
7 | useFindAndModify: false,
8 | autoIndex: false, // Don't build indexes
9 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
10 | reconnectInterval: 500, // Reconnect every 500ms
11 | poolSize: 10, // Maintain up to 10 socket connections
12 | // If not connected, return errors immediately rather than waiting for reconnect
13 | bufferMaxEntries: 0,
14 | useNewUrlParser: true
15 | }
16 | )
17 |
18 | export { connectToDatabase, connection }
19 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/schema/index.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from './user'
2 |
3 | export { UserModel }
4 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/schema/user.js:
--------------------------------------------------------------------------------
1 | import { model, Schema } from 'mongoose'
2 |
3 | const UserSchema = new Schema({
4 | email: String,
5 | password: String,
6 | businessName: String,
7 | firstName: String,
8 | lastName: String,
9 | displayName: String,
10 | providerId: String,
11 | provider: String
12 | })
13 |
14 | const UserModel = model('User', UserSchema)
15 |
16 | export { UserModel }
17 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/user/create.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function createUser({
4 | firstName,
5 | lastName,
6 | email,
7 | password,
8 | providerId,
9 | provider
10 | }) {
11 | return new Promise(async (resolve, reject) => {
12 | const user = await UserModel.findOne({ email })
13 |
14 | if (user) {
15 | reject('Email is already in use')
16 | }
17 |
18 | resolve(
19 | await UserModel.create({
20 | providerId,
21 | provider,
22 | firstName,
23 | lastName,
24 | email,
25 | password
26 | })
27 | )
28 | })
29 | }
30 |
31 | export { createUser }
32 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/user/get.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function getUserById(id) {
4 | return await UserModel.findById(id).exec()
5 | }
6 |
7 | async function getUserByEmail(email) {
8 | return await UserModel.findOne({ email }).exec()
9 | }
10 |
11 | async function getUserByProviderId(providerId) {
12 | return await UserModel.findOne({ providerId }).exec()
13 | }
14 |
15 | export { getUserById, getUserByEmail, getUserByProviderId }
16 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/database/user/index.js:
--------------------------------------------------------------------------------
1 | import { getUserById, getUserByEmail, getUserByProviderId } from './get'
2 | import { createUser } from './create'
3 |
4 | export { getUserById, getUserByEmail, createUser, getUserByProviderId }
5 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 |
3 | import express from 'express'
4 | import next from 'next'
5 | import { urlencoded, json } from 'body-parser'
6 | import cookieParser from 'cookie-parser'
7 | import passport from 'passport'
8 |
9 | import router from './router'
10 | import { connectToDatabase } from './database/connection'
11 | import { initialiseAuthentication } from './auth'
12 |
13 | const dev = process.env.NODE_ENV !== 'production'
14 | const nextApp = next({ dev })
15 | const handle = nextApp.getRequestHandler()
16 |
17 | const port = 3000
18 |
19 | nextApp.prepare().then(async () => {
20 | const app = express()
21 |
22 | app.get('/my-custom-route', (req, res) =>
23 | res.status(200).json({ hello: 'Hello, from the back-end world!' })
24 | )
25 |
26 | app.use(urlencoded({ extended: true }))
27 | app.use(json())
28 | app.use(cookieParser())
29 |
30 | app.use(passport.initialize())
31 |
32 | router(app)
33 | initialiseAuthentication(app)
34 |
35 | app.get('*', (req, res) => {
36 | return handle(req, res)
37 | })
38 |
39 | await connectToDatabase()
40 |
41 | app.listen(port, err => {
42 | if (err) throw err
43 | console.log(`> Ready on localhost:${port}`)
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/router/auth.routes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { to } from 'await-to-js'
3 | import { verifyPassword, hashPassword } from '../auth/utils'
4 | import { login } from '../auth/strategies/jwt'
5 | import { createUser, getUserByEmail } from '../database/user'
6 |
7 | const router = express.Router()
8 |
9 | router.post('/login', async (req, res) => {
10 | const { email, password } = req.body
11 | const [err, user] = await to(getUserByEmail(email))
12 |
13 | const authenticationError = () => {
14 | return res.status(500).json({ success: false, data: 'Authentication error!' })
15 | }
16 |
17 | if (!(await verifyPassword(password, user.password))) {
18 | console.error('Passwords do not match')
19 | return authenticationError()
20 | }
21 |
22 | const [loginErr, token] = await to(login(req, user))
23 |
24 | if (loginErr) {
25 | console.error('Log in error', loginErr)
26 | return authenticationError()
27 | }
28 |
29 | return res
30 | .status(200)
31 | .cookie('jwt', token, {
32 | httpOnly: true
33 | })
34 | .json({
35 | success: true,
36 | data: '/'
37 | })
38 | })
39 |
40 | router.post('/register', async (req, res) => {
41 | const { firstName, lastName, email, password } = req.body
42 |
43 | if (!/\b\w+\@\w+\.\w+(?:\.\w+)?\b/.test(email)) {
44 | return res.status(500).json({ success: false, data: 'Enter a valid email address.' })
45 | } else if (password.length < 5 || password.length > 20) {
46 | return res.status(500).json({
47 | success: false,
48 | data: 'Password must be between 5 and 20 characters.'
49 | })
50 | }
51 |
52 | let [err, user] = await to(
53 | createUser({
54 | firstName,
55 | lastName,
56 | email,
57 | password: await hashPassword(password)
58 | })
59 | )
60 |
61 | if (err) {
62 | return res.status(500).json({ success: false, data: 'Email is already taken' })
63 | }
64 |
65 | const [loginErr, token] = await to(login(req, user))
66 |
67 | if (loginErr) {
68 | console.error(loginErr)
69 | return res.status(500).json({ success: false, data: 'Authentication error!' })
70 | }
71 |
72 | return res
73 | .status(200)
74 | .cookie('jwt', token, {
75 | httpOnly: true
76 | })
77 | .json({
78 | success: true,
79 | data: '/'
80 | })
81 | })
82 |
83 | export default router
84 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/server/router/index.js:
--------------------------------------------------------------------------------
1 | import authRoutes from './auth.routes'
2 |
3 | function Router(app) {
4 | app.use(`${process.env.BASE_API_URL}/auth`, authRoutes)
5 | }
6 |
7 | export default Router
8 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpreecedev/passport-next-auth-tutorial/9fe9013c5f99a179773b60b8ae3f3753529c6d16/passport-facebook-strategy/static/favicon.ico
--------------------------------------------------------------------------------
/passport-facebook-strategy/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as server from './server'
2 |
3 | export { server }
4 |
--------------------------------------------------------------------------------
/passport-facebook-strategy/utils/server.js:
--------------------------------------------------------------------------------
1 | function getServerApiUrl() {
2 | return process.env.BASE_API_URL
3 | }
4 |
5 | const callFetchAsync = async (url, method, body, headers = {}) => {
6 | try {
7 | const options = {
8 | headers: new Headers({
9 | 'Content-Type': 'application/json',
10 | ...headers
11 | }),
12 | body
13 | }
14 |
15 | if (body) {
16 | options.body = JSON.stringify(body)
17 | }
18 |
19 | const response = await fetch(`${getServerApiUrl()}${url}`, {
20 | method,
21 | credentials: 'same-origin',
22 | ...options
23 | })
24 |
25 | return await response.json()
26 | } catch (err) {
27 | return {
28 | success: false,
29 | data: err
30 | }
31 | }
32 | }
33 |
34 | const postAsync = (url, body) => {
35 | return callFetchAsync(url, 'POST', body)
36 | }
37 |
38 | export { postAsync }
39 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/components/GoogleLoginButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 |
4 | const useStyles = makeStyles(theme =>
5 | createStyles({
6 | button: {
7 | margin: theme.spacing(0, 0, 1),
8 | display: 'flex',
9 | backgroundColor: '#DD4B39',
10 | borderRadius: theme.shape.borderRadius,
11 | boxShadow: theme.shadows[1],
12 | height: '36px',
13 | cursor: 'pointer',
14 | textDecoration: 'none',
15 | '&:hover': {
16 | backgroundColor: '#E74B37'
17 | },
18 | '&:active': {
19 | boxShadow: 'inset 0 0 0 32px rgba(0,0,0,0.1)'
20 | }
21 | },
22 | wrapper: {
23 | marginTop: '1px',
24 | marginLeft: '1px',
25 | display: 'flex',
26 | justifyContent: 'center',
27 | alignItems: 'center',
28 | width: '34px',
29 | height: '34px',
30 | borderRadius: '2px',
31 | backgroundColor: '#fff'
32 | },
33 | icon: {
34 | width: '18px',
35 | height: '18px'
36 | },
37 | text: {
38 | margin: '0 34px 0 0',
39 | color: '#fff',
40 | fontSize: '14px',
41 | fontWeight: 'bold',
42 | textTransform: 'uppercase',
43 | flexGrow: 1,
44 | textAlign: 'center',
45 | alignSelf: 'center'
46 | }
47 | })
48 | )
49 |
50 | const GoogleLoginButton = () => {
51 | const classes = useStyles({})
52 |
53 | return (
54 |
55 |
56 |
78 |
79 | Login with Google
80 |
81 | )
82 | }
83 |
84 | export { GoogleLoginButton }
85 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/next.config.js:
--------------------------------------------------------------------------------
1 | const { parsed: localEnv } = require('dotenv').config()
2 |
3 | module.exports = {
4 | env: {
5 | BASE_API_URL: localEnv.BASE_API_URL
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["server"],
3 | "exec": "NODE_ENV=development babel-node server/index.js",
4 | "ext": "js"
5 | }
6 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "passport-google-oauth-strategy",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nodemon",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.4.3",
12 | "await-to-js": "^2.1.1",
13 | "bcrypt": "^3.0.6",
14 | "body-parser": "^1.19.0",
15 | "cookie-parser": "^1.4.4",
16 | "dotenv": "^8.1.0",
17 | "express": "^4.17.1",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^5.7.5",
20 | "next": "9.3.2",
21 | "passport": "^0.4.0",
22 | "passport-google-oauth": "^2.0.0",
23 | "passport-jwt": "^4.0.0",
24 | "react": "^16.10.1",
25 | "react-dom": "^16.10.1"
26 | },
27 | "devDependencies": {
28 | "@babel/node": "^7.6.2",
29 | "@babel/preset-env": "^7.6.2",
30 | "nodemon": "^1.19.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheets } from '@material-ui/styles'
4 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles'
5 |
6 | const theme = responsiveFontSizes(createMuiTheme())
7 |
8 | class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
23 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | MyDocument.getInitialProps = async ctx => {
53 | // Resolution order
54 | //
55 | // On the server:
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. document.getInitialProps
59 | // 4. app.render
60 | // 5. page.render
61 | // 6. document.render
62 | //
63 | // On the server with error:
64 | // 1. document.getInitialProps
65 | // 2. app.render
66 | // 3. page.render
67 | // 4. document.render
68 | //
69 | // On the client
70 | // 1. app.getInitialProps
71 | // 2. page.getInitialProps
72 | // 3. app.render
73 | // 4. page.render
74 |
75 | // Render app and page and get the context of the page with collected side effects.
76 | const sheets = new ServerStyleSheets()
77 | const originalRenderPage = ctx.renderPage
78 |
79 | ctx.renderPage = () =>
80 | originalRenderPage({
81 | enhanceApp: App => props => sheets.collect()
82 | })
83 |
84 | const initialProps = await Document.getInitialProps(ctx)
85 |
86 | return {
87 | ...initialProps,
88 | // Styles fragment is rendered after the app and page rendering finish.
89 | styles: [
90 |
91 | {initialProps.styles}
92 | {sheets.getStyleElement()}
93 |
94 | ]
95 | }
96 | }
97 |
98 | export default MyDocument
99 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 |
4 | const Home = () => (
5 |
6 |
7 |
Home
8 |
9 |
10 |
11 |
12 |
Welcome to this awesome tutorial
13 |
16 |
17 |
18 | )
19 |
20 | export default Home
21 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import Box from '@material-ui/core/Box'
5 | import CircularProgress from '@material-ui/core/CircularProgress'
6 | import Typography from '@material-ui/core/Typography'
7 | import TextField from '@material-ui/core/TextField'
8 | import Paper from '@material-ui/core/Paper'
9 | import { server } from '../utils'
10 |
11 | import { GoogleLoginButton } from '../components/GoogleLoginButton'
12 |
13 | const useStyles = makeStyles(theme => ({
14 | layout: {
15 | display: 'flex',
16 | flexDirection: 'column',
17 | alignItems: 'center'
18 | },
19 | paper: {
20 | padding: theme.spacing(2),
21 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
22 | marginTop: theme.spacing(8),
23 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
24 | }
25 | },
26 | submit: {
27 | margin: theme.spacing(3, 0, 3)
28 | },
29 | form: {
30 | width: '100%', // Fix IE 11 issue.
31 | marginTop: theme.spacing(1)
32 | },
33 | buttonProgress: {
34 | position: 'absolute',
35 | top: '50%',
36 | left: '50%',
37 | marginTop: -12,
38 | marginLeft: -12
39 | }
40 | }))
41 |
42 | const LoginForm = () => {
43 | const classes = useStyles({})
44 | const [formData, setFormData] = React.useState({ email: '', password: '' })
45 | const [submitting, setSubmitting] = React.useState(false)
46 |
47 | const handleSubmit = async e => {
48 | e.preventDefault()
49 | const { email, password } = formData
50 | const { success, data } = await server.postAsync('/auth/login', {
51 | email,
52 | password
53 | })
54 |
55 | if (success) {
56 | window.location.replace(data)
57 | return
58 | }
59 | }
60 |
61 | return (
62 |
63 |
64 |
70 |
71 | Login
72 |
73 |
74 | Log in to your account dashboard
75 |
76 |
77 |
122 |
123 |
124 | )
125 | }
126 |
127 | export default LoginForm
128 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Box from '@material-ui/core/Box'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import Paper from '@material-ui/core/Paper'
6 | import TextField from '@material-ui/core/TextField'
7 | import Button from '@material-ui/core/Button'
8 | import { server } from '../utils'
9 |
10 | const useStyles = makeStyles(theme => ({
11 | layout: {
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center',
15 | maxWidth: '768px',
16 | margin: '0 auto'
17 | },
18 | paper: {
19 | padding: theme.spacing(2),
20 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
21 | marginTop: theme.spacing(8),
22 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
23 | }
24 | },
25 | submit: {
26 | margin: theme.spacing(3, 0, 2)
27 | },
28 | form: {
29 | width: '100%', // Fix IE 11 issue.
30 | marginTop: theme.spacing(1)
31 | },
32 | buttonProgress: {
33 | position: 'absolute',
34 | top: '50%',
35 | left: '50%',
36 | marginTop: -12,
37 | marginLeft: -12
38 | }
39 | }))
40 |
41 | const Register = () => {
42 | const classes = useStyles({})
43 | const [formData, setFormData] = React.useState({
44 | firstName: '',
45 | lastName: '',
46 | email: '',
47 | password: ''
48 | })
49 | const [submitting, setSubmitting] = React.useState(false)
50 |
51 | const handleSubmit = async e => {
52 | e.preventDefault()
53 | const { firstName, lastName, email, password } = formData
54 | const { success, data } = await server.postAsync('/auth/register', {
55 | firstName,
56 | lastName,
57 | email,
58 | password
59 | })
60 | if (success) {
61 | window.location.replace(data)
62 | return
63 | }
64 | }
65 |
66 | return (
67 |
68 |
69 |
75 |
76 | Register
77 |
78 |
79 |
142 |
143 |
144 | )
145 | }
146 |
147 | export default Register
148 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/auth/index.js:
--------------------------------------------------------------------------------
1 | import * as utils from './utils'
2 | import * as strategies from './strategies'
3 |
4 | const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args)
5 |
6 | const initialiseAuthentication = app => {
7 | utils.setup()
8 |
9 | pipe(
10 | strategies.GoogleStrategy,
11 | strategies.JWTStrategy
12 | )(app)
13 | }
14 |
15 | export { utils, initialiseAuthentication, strategies }
16 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/auth/strategies/google.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportGoogle from 'passport-google-oauth'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserByProviderId, createUser } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const GoogleStrategy = passportGoogle.OAuth2Strategy
9 |
10 | const strategy = app => {
11 | const strategyOptions = {
12 | clientID: process.env.GOOGLE_CLIENT_ID,
13 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
14 | callbackURL: `${process.env.SERVER_API_URL}/auth/google/callback`
15 | }
16 |
17 | const verifyCallback = async (accessToken, refreshToken, profile, done) => {
18 | let [err, user] = await to(getUserByProviderId(profile.id))
19 | if (err || user) {
20 | return done(err, user)
21 | }
22 |
23 | const verifiedEmail =
24 | profile.emails.find(email => email.verified) || profile.emails[0]
25 |
26 | const [createdError, createdUser] = await to(
27 | createUser({
28 | provider: profile.provider,
29 | providerId: profile.id,
30 | firstName: profile.name.givenName,
31 | lastName: profile.name.familyName,
32 | displayName: profile.displayName,
33 | email: verifiedEmail.value,
34 | password: null
35 | })
36 | )
37 |
38 | return done(createdError, createdUser)
39 | }
40 |
41 | passport.use(new GoogleStrategy(strategyOptions, verifyCallback))
42 |
43 | app.get(
44 | `${process.env.BASE_API_URL}/auth/google`,
45 | passport.authenticate('google', {
46 | scope: [
47 | 'https://www.googleapis.com/auth/userinfo.profile',
48 | 'https://www.googleapis.com/auth/userinfo.email'
49 | ]
50 | })
51 | )
52 |
53 | app.get(
54 | `${process.env.BASE_API_URL}/auth/google/callback`,
55 | passport.authenticate('google', { failureRedirect: '/login' }),
56 | (req, res) => {
57 | return res
58 | .status(200)
59 | .cookie('jwt', signToken(req.user), {
60 | httpOnly: true
61 | })
62 | .redirect('/')
63 | }
64 | )
65 |
66 | return app
67 | }
68 |
69 | export { strategy }
70 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/auth/strategies/index.js:
--------------------------------------------------------------------------------
1 | import { strategy as JWTStrategy } from './jwt'
2 | import { strategy as GoogleStrategy } from './google'
3 |
4 | export { JWTStrategy, GoogleStrategy }
5 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/auth/strategies/jwt.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportJWT from 'passport-jwt'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserById } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const JWTStrategy = passportJWT.Strategy
9 |
10 | const strategy = () => {
11 | const strategyOptions = {
12 | jwtFromRequest: req => req.cookies.jwt,
13 | secretOrKey: process.env.JWT_SECRET,
14 | passReqToCallback: true
15 | }
16 |
17 | const verifyCallback = async (req, jwtPayload, cb) => {
18 | const [err, user] = await to(getUserById(jwtPayload.data._id))
19 |
20 | if (err) {
21 | return cb(err)
22 | }
23 | req.user = user
24 | return cb(null, user)
25 | }
26 |
27 | passport.use(new JWTStrategy(strategyOptions, verifyCallback))
28 | }
29 |
30 | const login = (req, user) => {
31 | return new Promise((resolve, reject) => {
32 | req.login(user, { session: false }, err => {
33 | if (err) {
34 | return reject(err)
35 | }
36 |
37 | return resolve(signToken(user))
38 | })
39 | })
40 | }
41 |
42 | export { strategy, login }
43 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/auth/utils.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import jwt from 'jsonwebtoken'
3 | import bcrypt from 'bcrypt'
4 | import { UserModel } from '../database/schema'
5 |
6 | const setup = () => {
7 | passport.serializeUser((user, done) => done(null, user._id))
8 |
9 | passport.deserializeUser(async (id, done) => {
10 | try {
11 | const user = await UserModel.findById(id)
12 | return done(null, user)
13 | } catch (err) {
14 | return done(err, null)
15 | }
16 | })
17 | }
18 |
19 | const signToken = user => {
20 | return jwt.sign({ data: user }, process.env.JWT_SECRET, {
21 | expiresIn: 604800
22 | })
23 | }
24 |
25 | const hashPassword = async password => {
26 | if (!password) {
27 | throw new Error('Password was not provided')
28 | }
29 |
30 | const salt = await bcrypt.genSalt(10)
31 | return await bcrypt.hash(password, salt)
32 | }
33 |
34 | const verifyPassword = async (candidate, actual) => {
35 | return await bcrypt.compare(candidate, actual)
36 | }
37 |
38 | export { setup, signToken, hashPassword, verifyPassword }
39 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/connection.js:
--------------------------------------------------------------------------------
1 | import { connect, connection } from 'mongoose'
2 |
3 | const connectToDatabase = async () =>
4 | await connect(
5 | process.env.DB_CONNECTION_STRING || '',
6 | {
7 | useFindAndModify: false,
8 | autoIndex: false, // Don't build indexes
9 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
10 | reconnectInterval: 500, // Reconnect every 500ms
11 | poolSize: 10, // Maintain up to 10 socket connections
12 | // If not connected, return errors immediately rather than waiting for reconnect
13 | bufferMaxEntries: 0,
14 | useNewUrlParser: true
15 | }
16 | )
17 |
18 | export { connectToDatabase, connection }
19 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/schema/index.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from './user'
2 |
3 | export { UserModel }
4 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/schema/user.js:
--------------------------------------------------------------------------------
1 | import { model, Schema } from 'mongoose'
2 |
3 | const UserSchema = new Schema({
4 | email: String,
5 | password: String,
6 | businessName: String,
7 | firstName: String,
8 | lastName: String,
9 | displayName: String,
10 | providerId: String,
11 | provider: String
12 | })
13 |
14 | const UserModel = model('User', UserSchema)
15 |
16 | export { UserModel }
17 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/user/create.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function createUser({
4 | firstName,
5 | lastName,
6 | email,
7 | password,
8 | providerId,
9 | provider
10 | }) {
11 | return new Promise(async (resolve, reject) => {
12 | const user = await UserModel.findOne({ email })
13 |
14 | if (user) {
15 | reject('Email is already in use')
16 | }
17 |
18 | resolve(
19 | await UserModel.create({
20 | providerId,
21 | provider,
22 | firstName,
23 | lastName,
24 | email,
25 | password
26 | })
27 | )
28 | })
29 | }
30 |
31 | export { createUser }
32 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/user/get.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function getUserById(id) {
4 | return await UserModel.findById(id).exec()
5 | }
6 |
7 | async function getUserByEmail(email) {
8 | return await UserModel.findOne({ email }).exec()
9 | }
10 |
11 | async function getUserByProviderId(providerId) {
12 | return await UserModel.findOne({ providerId }).exec()
13 | }
14 |
15 | export { getUserById, getUserByEmail, getUserByProviderId }
16 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/database/user/index.js:
--------------------------------------------------------------------------------
1 | import { getUserById, getUserByEmail, getUserByProviderId } from './get'
2 | import { createUser } from './create'
3 |
4 | export { getUserById, getUserByEmail, createUser, getUserByProviderId }
5 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 |
3 | import express from 'express'
4 | import next from 'next'
5 | import { urlencoded, json } from 'body-parser'
6 | import cookieParser from 'cookie-parser'
7 | import passport from 'passport'
8 |
9 | import router from './router'
10 | import { connectToDatabase } from './database/connection'
11 | import { initialiseAuthentication } from './auth'
12 |
13 | const dev = process.env.NODE_ENV !== 'production'
14 | const nextApp = next({ dev })
15 | const handle = nextApp.getRequestHandler()
16 |
17 | const port = 3000
18 |
19 | nextApp.prepare().then(async () => {
20 | const app = express()
21 |
22 | app.get('/my-custom-route', (req, res) =>
23 | res.status(200).json({ hello: 'Hello, from the back-end world!' })
24 | )
25 |
26 | app.use(urlencoded({ extended: true }))
27 | app.use(json())
28 | app.use(cookieParser())
29 |
30 | app.use(passport.initialize())
31 |
32 | router(app)
33 | initialiseAuthentication(app)
34 |
35 | app.get('*', (req, res) => {
36 | return handle(req, res)
37 | })
38 |
39 | await connectToDatabase()
40 |
41 | app.listen(port, err => {
42 | if (err) throw err
43 | console.log(`> Ready on localhost:${port}`)
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/router/auth.routes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { to } from 'await-to-js'
3 | import { verifyPassword, hashPassword } from '../auth/utils'
4 | import { login } from '../auth/strategies/jwt'
5 | import { createUser, getUserByEmail } from '../database/user'
6 |
7 | const router = express.Router()
8 |
9 | router.post('/login', async (req, res) => {
10 | const { email, password } = req.body
11 | const [err, user] = await to(getUserByEmail(email))
12 |
13 | const authenticationError = () => {
14 | return res.status(500).json({ success: false, data: 'Authentication error!' })
15 | }
16 |
17 | if (!(await verifyPassword(password, user.password))) {
18 | console.error('Passwords do not match')
19 | return authenticationError()
20 | }
21 |
22 | const [loginErr, token] = await to(login(req, user))
23 |
24 | if (loginErr) {
25 | console.error('Log in error', loginErr)
26 | return authenticationError()
27 | }
28 |
29 | return res
30 | .status(200)
31 | .cookie('jwt', token, {
32 | httpOnly: true
33 | })
34 | .json({
35 | success: true,
36 | data: '/'
37 | })
38 | })
39 |
40 | router.post('/register', async (req, res) => {
41 | const { firstName, lastName, email, password } = req.body
42 |
43 | if (!/\b\w+\@\w+\.\w+(?:\.\w+)?\b/.test(email)) {
44 | return res.status(500).json({ success: false, data: 'Enter a valid email address.' })
45 | } else if (password.length < 5 || password.length > 20) {
46 | return res.status(500).json({
47 | success: false,
48 | data: 'Password must be between 5 and 20 characters.'
49 | })
50 | }
51 |
52 | let [err, user] = await to(
53 | createUser({
54 | firstName,
55 | lastName,
56 | email,
57 | password: await hashPassword(password)
58 | })
59 | )
60 |
61 | if (err) {
62 | return res.status(500).json({ success: false, data: 'Email is already taken' })
63 | }
64 |
65 | const [loginErr, token] = await to(login(req, user))
66 |
67 | if (loginErr) {
68 | console.error(loginErr)
69 | return res.status(500).json({ success: false, data: 'Authentication error!' })
70 | }
71 |
72 | return res
73 | .status(200)
74 | .cookie('jwt', token, {
75 | httpOnly: true
76 | })
77 | .json({
78 | success: true,
79 | data: '/'
80 | })
81 | })
82 |
83 | export default router
84 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/server/router/index.js:
--------------------------------------------------------------------------------
1 | import authRoutes from './auth.routes'
2 |
3 | function Router(app) {
4 | app.use(`${process.env.BASE_API_URL}/auth`, authRoutes)
5 | }
6 |
7 | export default Router
8 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpreecedev/passport-next-auth-tutorial/9fe9013c5f99a179773b60b8ae3f3753529c6d16/passport-google-oauth-strategy/static/favicon.ico
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as server from './server'
2 |
3 | export { server }
4 |
--------------------------------------------------------------------------------
/passport-google-oauth-strategy/utils/server.js:
--------------------------------------------------------------------------------
1 | function getServerApiUrl() {
2 | return process.env.BASE_API_URL
3 | }
4 |
5 | const callFetchAsync = async (url, method, body, headers = {}) => {
6 | try {
7 | const options = {
8 | headers: new Headers({
9 | 'Content-Type': 'application/json',
10 | ...headers
11 | }),
12 | body
13 | }
14 |
15 | if (body) {
16 | options.body = JSON.stringify(body)
17 | }
18 |
19 | const response = await fetch(`${getServerApiUrl()}${url}`, {
20 | method,
21 | credentials: 'same-origin',
22 | ...options
23 | })
24 |
25 | return await response.json()
26 | } catch (err) {
27 | return {
28 | success: false,
29 | data: err
30 | }
31 | }
32 | }
33 |
34 | const postAsync = (url, body) => {
35 | return callFetchAsync(url, 'POST', body)
36 | }
37 |
38 | export { postAsync }
39 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/next.config.js:
--------------------------------------------------------------------------------
1 | const { parsed: localEnv } = require('dotenv').config()
2 |
3 | module.exports = {
4 | env: {
5 | BASE_API_URL: localEnv.BASE_API_URL
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["server"],
3 | "exec": "NODE_ENV=development babel-node server/index.js",
4 | "ext": "js"
5 | }
6 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "passport-jwt-mongo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nodemon",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.4.3",
12 | "await-to-js": "^2.1.1",
13 | "bcrypt": "^3.0.6",
14 | "body-parser": "^1.19.0",
15 | "cookie-parser": "^1.4.4",
16 | "dotenv": "^8.1.0",
17 | "express": "^4.17.1",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^5.7.5",
20 | "next": "9.3.2",
21 | "passport": "^0.4.0",
22 | "passport-jwt": "^4.0.0",
23 | "react": "^16.10.1",
24 | "react-dom": "^16.10.1"
25 | },
26 | "devDependencies": {
27 | "@babel/node": "^7.6.2",
28 | "@babel/preset-env": "^7.6.2",
29 | "nodemon": "^1.19.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheets } from '@material-ui/styles'
4 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles'
5 |
6 | const theme = responsiveFontSizes(createMuiTheme())
7 |
8 | class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
23 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | MyDocument.getInitialProps = async ctx => {
53 | // Resolution order
54 | //
55 | // On the server:
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. document.getInitialProps
59 | // 4. app.render
60 | // 5. page.render
61 | // 6. document.render
62 | //
63 | // On the server with error:
64 | // 1. document.getInitialProps
65 | // 2. app.render
66 | // 3. page.render
67 | // 4. document.render
68 | //
69 | // On the client
70 | // 1. app.getInitialProps
71 | // 2. page.getInitialProps
72 | // 3. app.render
73 | // 4. page.render
74 |
75 | // Render app and page and get the context of the page with collected side effects.
76 | const sheets = new ServerStyleSheets()
77 | const originalRenderPage = ctx.renderPage
78 |
79 | ctx.renderPage = () =>
80 | originalRenderPage({
81 | enhanceApp: App => props => sheets.collect()
82 | })
83 |
84 | const initialProps = await Document.getInitialProps(ctx)
85 |
86 | return {
87 | ...initialProps,
88 | // Styles fragment is rendered after the app and page rendering finish.
89 | styles: [
90 |
91 | {initialProps.styles}
92 | {sheets.getStyleElement()}
93 |
94 | ]
95 | }
96 | }
97 |
98 | export default MyDocument
99 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 |
4 | const Home = () => (
5 |
6 |
7 |
Home
8 |
9 |
10 |
11 |
12 |
Welcome to this awesome tutorial
13 |
16 |
17 |
18 | )
19 |
20 | export default Home
21 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import Box from '@material-ui/core/Box'
5 | import CircularProgress from '@material-ui/core/CircularProgress'
6 | import Typography from '@material-ui/core/Typography'
7 | import TextField from '@material-ui/core/TextField'
8 | import Paper from '@material-ui/core/Paper'
9 | import { server } from '../utils'
10 |
11 | const useStyles = makeStyles(theme => ({
12 | layout: {
13 | display: 'flex',
14 | flexDirection: 'column',
15 | alignItems: 'center'
16 | },
17 | paper: {
18 | padding: theme.spacing(2),
19 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
20 | marginTop: theme.spacing(8),
21 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
22 | }
23 | },
24 | submit: {
25 | margin: theme.spacing(3, 0, 3)
26 | },
27 | form: {
28 | width: '100%', // Fix IE 11 issue.
29 | marginTop: theme.spacing(1)
30 | },
31 | buttonProgress: {
32 | position: 'absolute',
33 | top: '50%',
34 | left: '50%',
35 | marginTop: -12,
36 | marginLeft: -12
37 | }
38 | }))
39 |
40 | const LoginForm = () => {
41 | const classes = useStyles({})
42 | const [formData, setFormData] = React.useState({ email: '', password: '' })
43 | const [submitting, setSubmitting] = React.useState(false)
44 |
45 | const handleSubmit = async e => {
46 | e.preventDefault()
47 | const { email, password } = formData
48 | const { success, data } = await server.postAsync('/auth/login', {
49 | email,
50 | password
51 | })
52 |
53 | if (success) {
54 | window.location.replace(data)
55 | return
56 | }
57 | }
58 |
59 | return (
60 |
61 |
62 |
68 |
69 | Login
70 |
71 |
72 | Log in to your account dashboard
73 |
74 |
75 |
116 |
117 |
118 | )
119 | }
120 |
121 | export default LoginForm
122 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Box from '@material-ui/core/Box'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import Paper from '@material-ui/core/Paper'
6 | import TextField from '@material-ui/core/TextField'
7 | import Button from '@material-ui/core/Button'
8 | import { server } from '../utils'
9 |
10 | const useStyles = makeStyles(theme => ({
11 | layout: {
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center',
15 | maxWidth: '768px',
16 | margin: '0 auto'
17 | },
18 | paper: {
19 | padding: theme.spacing(2),
20 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
21 | marginTop: theme.spacing(8),
22 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
23 | }
24 | },
25 | submit: {
26 | margin: theme.spacing(3, 0, 2)
27 | },
28 | form: {
29 | width: '100%', // Fix IE 11 issue.
30 | marginTop: theme.spacing(1)
31 | },
32 | buttonProgress: {
33 | position: 'absolute',
34 | top: '50%',
35 | left: '50%',
36 | marginTop: -12,
37 | marginLeft: -12
38 | }
39 | }))
40 |
41 | const Register = () => {
42 | const classes = useStyles({})
43 | const [formData, setFormData] = React.useState({
44 | firstName: '',
45 | lastName: '',
46 | email: '',
47 | password: ''
48 | })
49 | const [submitting, setSubmitting] = React.useState(false)
50 |
51 | const handleSubmit = async e => {
52 | e.preventDefault()
53 | const { firstName, lastName, email, password } = formData
54 | const { success, data } = await server.postAsync('/auth/register', {
55 | firstName,
56 | lastName,
57 | email,
58 | password
59 | })
60 | if (success) {
61 | window.location.replace(data)
62 | return
63 | }
64 | }
65 |
66 | return (
67 |
68 |
69 |
75 |
76 | Register
77 |
78 |
79 |
142 |
143 |
144 | )
145 | }
146 |
147 | export default Register
148 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/auth/index.js:
--------------------------------------------------------------------------------
1 | import * as utils from './utils'
2 | import * as strategies from './strategies'
3 |
4 | const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args)
5 |
6 | const initialiseAuthentication = app => {
7 | utils.setup()
8 |
9 | pipe(strategies.JWTStrategy)(app)
10 | }
11 |
12 | export { utils, initialiseAuthentication, strategies }
13 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/auth/strategies/index.js:
--------------------------------------------------------------------------------
1 | import { strategy as JWTStrategy } from './jwt'
2 |
3 | export { JWTStrategy }
4 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/auth/strategies/jwt.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportJWT from 'passport-jwt'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserById } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const JWTStrategy = passportJWT.Strategy
9 |
10 | const strategy = () => {
11 | const strategyOptions = {
12 | jwtFromRequest: req => req.cookies.jwt,
13 | secretOrKey: process.env.JWT_SECRET,
14 | passReqToCallback: true
15 | }
16 |
17 | const verifyCallback = async (req, jwtPayload, cb) => {
18 | const [err, user] = await to(getUserById(jwtPayload.data._id))
19 |
20 | if (err) {
21 | return cb(err)
22 | }
23 | req.user = user
24 | return cb(null, user)
25 | }
26 |
27 | passport.use(new JWTStrategy(strategyOptions, verifyCallback))
28 | }
29 |
30 | const login = (req, user) => {
31 | return new Promise((resolve, reject) => {
32 | req.login(user, { session: false }, err => {
33 | if (err) {
34 | return reject(err)
35 | }
36 |
37 | return resolve(signToken(user))
38 | })
39 | })
40 | }
41 |
42 | export { strategy, login }
43 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/auth/utils.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import jwt from 'jsonwebtoken'
3 | import bcrypt from 'bcrypt'
4 | import { UserModel } from '../database/schema'
5 |
6 | const setup = () => {
7 | passport.serializeUser((user, done) => done(null, user._id))
8 |
9 | passport.deserializeUser(async (id, done) => {
10 | try {
11 | const user = await UserModel.findById(id)
12 | return done(null, user)
13 | } catch (err) {
14 | return done(err, null)
15 | }
16 | })
17 | }
18 |
19 | const signToken = user => {
20 | return jwt.sign({ data: user }, process.env.JWT_SECRET, {
21 | expiresIn: 604800
22 | })
23 | }
24 |
25 | const hashPassword = async password => {
26 | if (!password) {
27 | throw new Error('Password was not provided')
28 | }
29 |
30 | const salt = await bcrypt.genSalt(10)
31 | return await bcrypt.hash(password, salt)
32 | }
33 |
34 | const verifyPassword = async (candidate, actual) => {
35 | return await bcrypt.compare(candidate, actual)
36 | }
37 |
38 | export { setup, signToken, hashPassword, verifyPassword }
39 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/connection.js:
--------------------------------------------------------------------------------
1 | import { connect, connection } from 'mongoose'
2 |
3 | const connectToDatabase = async () =>
4 | await connect(
5 | process.env.DB_CONNECTION_STRING || '',
6 | {
7 | useFindAndModify: false,
8 | autoIndex: false, // Don't build indexes
9 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
10 | reconnectInterval: 500, // Reconnect every 500ms
11 | poolSize: 10, // Maintain up to 10 socket connections
12 | // If not connected, return errors immediately rather than waiting for reconnect
13 | bufferMaxEntries: 0,
14 | useNewUrlParser: true
15 | }
16 | )
17 |
18 | export { connectToDatabase, connection }
19 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/schema/index.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from './user'
2 |
3 | export { UserModel }
4 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/schema/user.js:
--------------------------------------------------------------------------------
1 | import { model, Schema } from 'mongoose'
2 |
3 | const UserSchema = new Schema({
4 | email: String,
5 | password: String,
6 | businessName: String,
7 | firstName: String,
8 | lastName: String,
9 | displayName: String,
10 | providerId: String,
11 | provider: String
12 | })
13 |
14 | const UserModel = model('User', UserSchema)
15 |
16 | export { UserModel }
17 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/user/create.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function createUser({
4 | firstName,
5 | lastName,
6 | email,
7 | password,
8 | providerId,
9 | provider
10 | }) {
11 | return new Promise(async (resolve, reject) => {
12 | const user = await UserModel.findOne({ email })
13 |
14 | if (user) {
15 | reject('Email is already in use')
16 | }
17 |
18 | resolve(
19 | await UserModel.create({
20 | providerId,
21 | provider,
22 | firstName,
23 | lastName,
24 | email,
25 | password
26 | })
27 | )
28 | })
29 | }
30 |
31 | export { createUser }
32 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/user/get.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function getUserById(id) {
4 | return await UserModel.findById(id).exec()
5 | }
6 |
7 | async function getUserByEmail(email) {
8 | return await UserModel.findOne({ email }).exec()
9 | }
10 |
11 | export { getUserById, getUserByEmail }
12 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/database/user/index.js:
--------------------------------------------------------------------------------
1 | import { getUserById, getUserByEmail } from './get'
2 | import { createUser } from './create'
3 |
4 | export { getUserById, getUserByEmail, createUser }
5 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 |
3 | import express from 'express'
4 | import next from 'next'
5 | import { urlencoded, json } from 'body-parser'
6 | import cookieParser from 'cookie-parser'
7 | import passport from 'passport'
8 |
9 | import router from './router'
10 | import { connectToDatabase } from './database/connection'
11 | import { initialiseAuthentication } from './auth'
12 |
13 | const dev = process.env.NODE_ENV !== 'production'
14 | const nextApp = next({ dev })
15 | const handle = nextApp.getRequestHandler()
16 |
17 | const port = 3000
18 |
19 | nextApp.prepare().then(async () => {
20 | const app = express()
21 |
22 | app.get('/my-custom-route', (req, res) =>
23 | res.status(200).json({ hello: 'Hello, from the back-end world!' })
24 | )
25 |
26 | app.use(urlencoded({ extended: true }))
27 | app.use(json())
28 | app.use(cookieParser())
29 |
30 | app.use(passport.initialize())
31 |
32 | router(app)
33 | initialiseAuthentication(app)
34 |
35 | app.get('*', (req, res) => {
36 | return handle(req, res)
37 | })
38 |
39 | await connectToDatabase()
40 |
41 | app.listen(port, err => {
42 | if (err) throw err
43 | console.log(`> Ready on localhost:${port}`)
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/router/auth.routes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { to } from 'await-to-js'
3 | import { verifyPassword, hashPassword } from '../auth/utils'
4 | import { login } from '../auth/strategies/jwt'
5 | import { createUser, getUserByEmail } from '../database/user'
6 |
7 | const router = express.Router()
8 |
9 | router.post('/login', async (req, res) => {
10 | const { email, password } = req.body
11 | const [err, user] = await to(getUserByEmail(email))
12 |
13 | const authenticationError = () => {
14 | return res.status(500).json({ success: false, data: 'Authentication error!' })
15 | }
16 |
17 | if (!(await verifyPassword(password, user.password))) {
18 | console.error('Passwords do not match')
19 | return authenticationError()
20 | }
21 |
22 | const [loginErr, token] = await to(login(req, user))
23 |
24 | if (loginErr) {
25 | console.error('Log in error', loginErr)
26 | return authenticationError()
27 | }
28 |
29 | return res
30 | .status(200)
31 | .cookie('jwt', token, {
32 | httpOnly: true
33 | })
34 | .json({
35 | success: true,
36 | data: '/'
37 | })
38 | })
39 |
40 | router.post('/register', async (req, res) => {
41 | const { firstName, lastName, email, password } = req.body
42 |
43 | if (!/\b\w+\@\w+\.\w+(?:\.\w+)?\b/.test(email)) {
44 | return res.status(500).json({ success: false, data: 'Enter a valid email address.' })
45 | } else if (password.length < 5 || password.length > 20) {
46 | return res.status(500).json({
47 | success: false,
48 | data: 'Password must be between 5 and 20 characters.'
49 | })
50 | }
51 |
52 | let [err, user] = await to(
53 | createUser({
54 | firstName,
55 | lastName,
56 | email,
57 | password: await hashPassword(password)
58 | })
59 | )
60 |
61 | if (err) {
62 | return res.status(500).json({ success: false, data: 'Email is already taken' })
63 | }
64 |
65 | const [loginErr, token] = await to(login(req, user))
66 |
67 | if (loginErr) {
68 | console.error(loginErr)
69 | return res.status(500).json({ success: false, data: 'Authentication error!' })
70 | }
71 |
72 | return res
73 | .status(200)
74 | .cookie('jwt', token, {
75 | httpOnly: true
76 | })
77 | .json({
78 | success: true,
79 | data: '/'
80 | })
81 | })
82 |
83 | export default router
84 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/server/router/index.js:
--------------------------------------------------------------------------------
1 | import authRoutes from './auth.routes'
2 |
3 | function Router(app) {
4 | app.use(`${process.env.BASE_API_URL}/auth`, authRoutes)
5 | }
6 |
7 | export default Router
8 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpreecedev/passport-next-auth-tutorial/9fe9013c5f99a179773b60b8ae3f3753529c6d16/passport-jwt-mongo/static/favicon.ico
--------------------------------------------------------------------------------
/passport-jwt-mongo/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as server from './server'
2 |
3 | export { server }
4 |
--------------------------------------------------------------------------------
/passport-jwt-mongo/utils/server.js:
--------------------------------------------------------------------------------
1 | function getServerApiUrl() {
2 | return process.env.BASE_API_URL
3 | }
4 |
5 | const callFetchAsync = async (url, method, body, headers = {}) => {
6 | try {
7 | const options = {
8 | headers: new Headers({
9 | 'Content-Type': 'application/json',
10 | ...headers
11 | }),
12 | body
13 | }
14 |
15 | if (body) {
16 | options.body = JSON.stringify(body)
17 | }
18 |
19 | const response = await fetch(`${getServerApiUrl()}${url}`, {
20 | method,
21 | credentials: 'same-origin',
22 | ...options
23 | })
24 |
25 | return await response.json()
26 | } catch (err) {
27 | return {
28 | success: false,
29 | data: err
30 | }
31 | }
32 | }
33 |
34 | const postAsync = (url, body) => {
35 | return callFetchAsync(url, 'POST', body)
36 | }
37 |
38 | export { postAsync }
39 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel", "@babel/preset-env"]
3 | }
4 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/components/FacebookLoginButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { FunctionComponent } from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 |
4 | const useStyles = makeStyles(theme =>
5 | createStyles({
6 | button: {
7 | display: 'flex',
8 | backgroundColor: '#4C69BA',
9 | backgroundImage: 'linear-gradient(#4C69BA, #3B55A0)',
10 | borderRadius: theme.shape.borderRadius,
11 | boxShadow: theme.shadows[1],
12 | height: '36px',
13 | cursor: 'pointer',
14 | textDecoration: 'none',
15 | '&:hover': {
16 | backgroundColor: '#5B7BD5',
17 | backgroundImage: 'linear-gradient(#5b7bd50a, #4864B1)'
18 | },
19 | '&:active': {
20 | boxShadow: 'inset 0 0 0 32px rgba(0,0,0,0.1)'
21 | }
22 | },
23 | wrapper: {
24 | marginTop: '1px',
25 | marginLeft: '1px',
26 | display: 'flex',
27 | justifyContent: 'center',
28 | alignItems: 'center',
29 | width: '34px',
30 | height: '34px',
31 | borderRadius: '2px',
32 | backgroundColor: '#fff'
33 | },
34 | icon: {
35 | width: '18px',
36 | height: '18px'
37 | },
38 | text: {
39 | margin: '0 34px 0 0',
40 | color: '#fff',
41 | fontSize: '14px',
42 | fontWeight: 'bold',
43 | textTransform: 'uppercase',
44 | flexGrow: 1,
45 | textAlign: 'center',
46 | alignSelf: 'center'
47 | }
48 | })
49 | )
50 |
51 | const FacebookLoginButton = () => {
52 | const classes = useStyles({})
53 |
54 | return (
55 |
56 |
67 | Login with Facebook
68 |
69 | )
70 | }
71 |
72 | export { FacebookLoginButton }
73 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/components/GoogleLoginButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles, createStyles } from '@material-ui/core/styles'
3 |
4 | const useStyles = makeStyles(theme =>
5 | createStyles({
6 | button: {
7 | margin: theme.spacing(0, 0, 1),
8 | display: 'flex',
9 | backgroundColor: '#DD4B39',
10 | borderRadius: theme.shape.borderRadius,
11 | boxShadow: theme.shadows[1],
12 | height: '36px',
13 | cursor: 'pointer',
14 | textDecoration: 'none',
15 | '&:hover': {
16 | backgroundColor: '#E74B37'
17 | },
18 | '&:active': {
19 | boxShadow: 'inset 0 0 0 32px rgba(0,0,0,0.1)'
20 | }
21 | },
22 | wrapper: {
23 | marginTop: '1px',
24 | marginLeft: '1px',
25 | display: 'flex',
26 | justifyContent: 'center',
27 | alignItems: 'center',
28 | width: '34px',
29 | height: '34px',
30 | borderRadius: '2px',
31 | backgroundColor: '#fff'
32 | },
33 | icon: {
34 | width: '18px',
35 | height: '18px'
36 | },
37 | text: {
38 | margin: '0 34px 0 0',
39 | color: '#fff',
40 | fontSize: '14px',
41 | fontWeight: 'bold',
42 | textTransform: 'uppercase',
43 | flexGrow: 1,
44 | textAlign: 'center',
45 | alignSelf: 'center'
46 | }
47 | })
48 | )
49 |
50 | const GoogleLoginButton = () => {
51 | const classes = useStyles({})
52 |
53 | return (
54 |
55 |
56 |
78 |
79 | Login with Google
80 |
81 | )
82 | }
83 |
84 | export { GoogleLoginButton }
85 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/db.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from './server/database/schema'
2 | import mongoose from 'mongoose'
3 |
4 | const { Types } = mongoose
5 |
6 | mongoose.connect('mongodb://root:example@localhost:27017/test?authSource=admin&w=1', {
7 | useNewUrlParser: true,
8 | useUnifiedTopology: true
9 | })
10 |
11 | UserModel.find({}).exec((err, users) => {
12 | users.forEach(u => console.log(u))
13 | })
14 |
15 | // UserModel.updateOne({ _id: "" }, { role: "Admin" }).exec()
16 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/next.config.js:
--------------------------------------------------------------------------------
1 | const { parsed: localEnv } = require('dotenv').config()
2 |
3 | module.exports = {
4 | env: {
5 | BASE_API_URL: localEnv.BASE_API_URL
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "watch": ["server"],
3 | "exec": "NODE_ENV=development babel-node server/index.js",
4 | "ext": "js"
5 | }
6 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "passport-role-based-authorisation",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "nodemon",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "@material-ui/core": "^4.4.3",
12 | "await-to-js": "^2.1.1",
13 | "bcrypt": "^3.0.6",
14 | "body-parser": "^1.19.0",
15 | "cookie-parser": "^1.4.4",
16 | "dotenv": "^8.1.0",
17 | "express": "^4.17.1",
18 | "jsonwebtoken": "^8.5.1",
19 | "mongoose": "^5.7.5",
20 | "next": "9.3.2",
21 | "passport": "^0.4.0",
22 | "passport-facebook": "^3.0.0",
23 | "passport-google-oauth": "^2.0.0",
24 | "passport-jwt": "^4.0.0",
25 | "react": "^16.10.1",
26 | "react-dom": "^16.10.1"
27 | },
28 | "devDependencies": {
29 | "@babel/node": "^7.6.2",
30 | "@babel/preset-env": "^7.6.2",
31 | "nodemon": "^1.19.3"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/_document.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Document, { Html, Head, Main, NextScript } from 'next/document'
3 | import { ServerStyleSheets } from '@material-ui/styles'
4 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core/styles'
5 |
6 | const theme = responsiveFontSizes(createMuiTheme())
7 |
8 | class MyDocument extends Document {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
18 |
19 |
23 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | MyDocument.getInitialProps = async ctx => {
53 | // Resolution order
54 | //
55 | // On the server:
56 | // 1. app.getInitialProps
57 | // 2. page.getInitialProps
58 | // 3. document.getInitialProps
59 | // 4. app.render
60 | // 5. page.render
61 | // 6. document.render
62 | //
63 | // On the server with error:
64 | // 1. document.getInitialProps
65 | // 2. app.render
66 | // 3. page.render
67 | // 4. document.render
68 | //
69 | // On the client
70 | // 1. app.getInitialProps
71 | // 2. page.getInitialProps
72 | // 3. app.render
73 | // 4. page.render
74 |
75 | // Render app and page and get the context of the page with collected side effects.
76 | const sheets = new ServerStyleSheets()
77 | const originalRenderPage = ctx.renderPage
78 |
79 | ctx.renderPage = () =>
80 | originalRenderPage({
81 | enhanceApp: App => props => sheets.collect()
82 | })
83 |
84 | const initialProps = await Document.getInitialProps(ctx)
85 |
86 | return {
87 | ...initialProps,
88 | // Styles fragment is rendered after the app and page rendering finish.
89 | styles: [
90 |
91 | {initialProps.styles}
92 | {sheets.getStyleElement()}
93 |
94 | ]
95 | }
96 | }
97 |
98 | export default MyDocument
99 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/admin-dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Box from '@material-ui/core/Box'
4 | import Typography from '@material-ui/core/Typography'
5 | import Paper from '@material-ui/core/Paper'
6 |
7 | const useStyles = makeStyles(theme => ({
8 | layout: {
9 | display: 'flex',
10 | flexDirection: 'column',
11 | alignItems: 'center'
12 | },
13 | paper: {
14 | padding: theme.spacing(2),
15 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
16 | marginTop: theme.spacing(8),
17 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
18 | }
19 | }
20 | }))
21 |
22 | const AdminDashboard = () => {
23 | const classes = useStyles({})
24 | return (
25 |
26 |
27 |
33 |
34 | Admin Dashboard
35 |
36 |
37 | Welcome, you are logged in as an administrator!
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default AdminDashboard
46 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/anonymous-dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Box from '@material-ui/core/Box'
4 | import Typography from '@material-ui/core/Typography'
5 | import Paper from '@material-ui/core/Paper'
6 |
7 | const useStyles = makeStyles(theme => ({
8 | layout: {
9 | display: 'flex',
10 | flexDirection: 'column',
11 | alignItems: 'center'
12 | },
13 | paper: {
14 | padding: theme.spacing(2),
15 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
16 | marginTop: theme.spacing(8),
17 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
18 | }
19 | }
20 | }))
21 |
22 | const AnonymousDashboard = () => {
23 | const classes = useStyles({})
24 | return (
25 |
26 |
27 |
33 |
34 | Anonymous User Dashboard
35 |
36 |
37 | Welcome, you are anonymous, but that's cool with us
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default AnonymousDashboard
46 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/both-dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Box from '@material-ui/core/Box'
4 | import Typography from '@material-ui/core/Typography'
5 | import Paper from '@material-ui/core/Paper'
6 |
7 | const useStyles = makeStyles(theme => ({
8 | layout: {
9 | display: 'flex',
10 | flexDirection: 'column',
11 | alignItems: 'center'
12 | },
13 | paper: {
14 | padding: theme.spacing(2),
15 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
16 | marginTop: theme.spacing(8),
17 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
18 | }
19 | }
20 | }))
21 |
22 | const BothDashboard = () => {
23 | const classes = useStyles({})
24 | return (
25 |
26 |
27 |
33 |
34 | General Dashboard
35 |
36 |
37 | Welcome, you are logged in. Either Admin, or{' '}
38 | Customer.
39 |
40 |
41 |
42 |
43 | )
44 | }
45 |
46 | export default BothDashboard
47 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/customer-dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Box from '@material-ui/core/Box'
4 | import Typography from '@material-ui/core/Typography'
5 | import Paper from '@material-ui/core/Paper'
6 |
7 | const useStyles = makeStyles(theme => ({
8 | layout: {
9 | display: 'flex',
10 | flexDirection: 'column',
11 | alignItems: 'center'
12 | },
13 | paper: {
14 | padding: theme.spacing(2),
15 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
16 | marginTop: theme.spacing(8),
17 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
18 | }
19 | }
20 | }))
21 |
22 | const CustomerDashboard = () => {
23 | const classes = useStyles({})
24 | return (
25 |
26 |
27 |
33 |
34 | Customer Dashboard
35 |
36 |
37 | Welcome, you are logged in as a customer!
38 |
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | export default CustomerDashboard
46 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from 'next/head'
3 |
4 | const Home = () => (
5 |
6 |
7 |
Home
8 |
9 |
10 |
11 |
12 |
Welcome to this awesome tutorial
13 |
16 |
17 |
18 | )
19 |
20 | export default Home
21 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import Button from '@material-ui/core/Button'
4 | import Box from '@material-ui/core/Box'
5 | import CircularProgress from '@material-ui/core/CircularProgress'
6 | import Typography from '@material-ui/core/Typography'
7 | import TextField from '@material-ui/core/TextField'
8 | import Paper from '@material-ui/core/Paper'
9 | import { server } from '../utils'
10 |
11 | import { GoogleLoginButton } from '../components/GoogleLoginButton'
12 | import { FacebookLoginButton } from '../components/FacebookLoginButton'
13 |
14 | const useStyles = makeStyles(theme => ({
15 | layout: {
16 | display: 'flex',
17 | flexDirection: 'column',
18 | alignItems: 'center'
19 | },
20 | paper: {
21 | padding: theme.spacing(2),
22 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
23 | marginTop: theme.spacing(8),
24 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
25 | }
26 | },
27 | submit: {
28 | margin: theme.spacing(3, 0, 3)
29 | },
30 | form: {
31 | width: '100%', // Fix IE 11 issue.
32 | marginTop: theme.spacing(1)
33 | },
34 | buttonProgress: {
35 | position: 'absolute',
36 | top: '50%',
37 | left: '50%',
38 | marginTop: -12,
39 | marginLeft: -12
40 | }
41 | }))
42 |
43 | const LoginForm = () => {
44 | const classes = useStyles({})
45 | const [formData, setFormData] = React.useState({ email: '', password: '' })
46 | const [submitting, setSubmitting] = React.useState(false)
47 |
48 | const handleSubmit = async e => {
49 | e.preventDefault()
50 | const { email, password } = formData
51 | const { success, data } = await server.postAsync('/auth/login', {
52 | email,
53 | password
54 | })
55 |
56 | if (success) {
57 | window.location.replace(data)
58 | return
59 | }
60 | }
61 |
62 | return (
63 |
64 |
65 |
71 |
72 | Login
73 |
74 |
75 | Log in to your account dashboard
76 |
77 |
78 |
124 |
125 |
126 | )
127 | }
128 |
129 | export default LoginForm
130 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/pages/register.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Typography from '@material-ui/core/Typography'
3 | import Box from '@material-ui/core/Box'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import Paper from '@material-ui/core/Paper'
6 | import TextField from '@material-ui/core/TextField'
7 | import Button from '@material-ui/core/Button'
8 | import { server } from '../utils'
9 |
10 | const useStyles = makeStyles(theme => ({
11 | layout: {
12 | display: 'flex',
13 | flexDirection: 'column',
14 | alignItems: 'center',
15 | maxWidth: '768px',
16 | margin: '0 auto'
17 | },
18 | paper: {
19 | padding: theme.spacing(2),
20 | [theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
21 | marginTop: theme.spacing(8),
22 | padding: `${theme.spacing(6)}px ${theme.spacing(4)}px`
23 | }
24 | },
25 | submit: {
26 | margin: theme.spacing(3, 0, 2)
27 | },
28 | form: {
29 | width: '100%', // Fix IE 11 issue.
30 | marginTop: theme.spacing(1)
31 | },
32 | buttonProgress: {
33 | position: 'absolute',
34 | top: '50%',
35 | left: '50%',
36 | marginTop: -12,
37 | marginLeft: -12
38 | }
39 | }))
40 |
41 | const Register = () => {
42 | const classes = useStyles({})
43 | const [formData, setFormData] = React.useState({
44 | firstName: '',
45 | lastName: '',
46 | email: '',
47 | password: ''
48 | })
49 | const [submitting, setSubmitting] = React.useState(false)
50 |
51 | const handleSubmit = async e => {
52 | e.preventDefault()
53 | const { firstName, lastName, email, password } = formData
54 | const { success, data } = await server.postAsync('/auth/register', {
55 | firstName,
56 | lastName,
57 | email,
58 | password
59 | })
60 | if (success) {
61 | window.location.replace(data)
62 | return
63 | }
64 | }
65 |
66 | return (
67 |
68 |
69 |
75 |
76 | Register
77 |
78 |
79 |
142 |
143 |
144 | )
145 | }
146 |
147 | export default Register
148 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/index.js:
--------------------------------------------------------------------------------
1 | import * as utils from './utils'
2 | import * as strategies from './strategies'
3 |
4 | const pipe = (...functions) => args => functions.reduce((arg, fn) => fn(arg), args)
5 |
6 | const initialiseAuthentication = app => {
7 | utils.setup()
8 |
9 | pipe(
10 | strategies.FacebookStrategy,
11 | strategies.GoogleStrategy,
12 | strategies.JWTStrategy
13 | )(app)
14 | }
15 |
16 | export { utils, initialiseAuthentication, strategies }
17 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/strategies/facebook.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportFacebook from 'passport-facebook'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserByProviderId, createUser } from '../../database/user'
6 | import { signToken, getRedirectUrl } from '../utils'
7 | import { ROLES } from '../../../utils'
8 |
9 | const FacebookStrategy = passportFacebook.Strategy
10 |
11 | const strategy = app => {
12 | const strategyOptions = {
13 | clientID: process.env.FACEBOOK_APP_ID,
14 | clientSecret: process.env.FACEBOOK_APP_SECRET,
15 | callbackURL: `${process.env.SERVER_API_URL}/auth/facebook/callback`,
16 | profileFields: ['id', 'displayName', 'name', 'emails']
17 | }
18 |
19 | const verifyCallback = async (accessToken, refreshToken, profile, done) => {
20 | let [err, user] = await to(getUserByProviderId(profile.id))
21 | if (err || user) {
22 | return done(err, user)
23 | }
24 |
25 | const [createdError, createdUser] = await to(
26 | createUser({
27 | providerId: profile.id,
28 | provider: profile.provider,
29 | firstName: profile.name.givenName,
30 | lastName: profile.name.familyName,
31 | displayName: profile.displayName,
32 | email: profile.emails[0].value,
33 | password: null,
34 | role: ROLES.Customer
35 | })
36 | )
37 |
38 | return done(createdError, createdUser)
39 | }
40 |
41 | passport.use(new FacebookStrategy(strategyOptions, verifyCallback))
42 |
43 | app.get(`${process.env.BASE_API_URL}/auth/facebook`, passport.authenticate('facebook'))
44 |
45 | app.get(
46 | `${process.env.BASE_API_URL}/auth/facebook/callback`,
47 | passport.authenticate('facebook', { failureRedirect: '/login' }),
48 | (req, res) => {
49 | return res
50 | .status(200)
51 | .cookie('jwt', signToken(req.user), {
52 | httpOnly: true
53 | })
54 | .redirect(getRedirectUrl(req.user.role))
55 | }
56 | )
57 |
58 | return app
59 | }
60 |
61 | export { strategy }
62 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/strategies/google.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportGoogle from 'passport-google-oauth'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserByProviderId, createUser } from '../../database/user'
6 | import { signToken, getRedirectUrl } from '../utils'
7 | import { ROLES } from '../../../utils'
8 |
9 | const GoogleStrategy = passportGoogle.OAuth2Strategy
10 |
11 | const strategy = app => {
12 | const strategyOptions = {
13 | clientID: process.env.GOOGLE_CLIENT_ID,
14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
15 | callbackURL: `${process.env.SERVER_API_URL}/auth/google/callback`
16 | }
17 |
18 | const verifyCallback = async (accessToken, refreshToken, profile, done) => {
19 | let [err, user] = await to(getUserByProviderId(profile.id))
20 | if (err || user) {
21 | return done(err, user)
22 | }
23 |
24 | const verifiedEmail =
25 | profile.emails.find(email => email.verified) || profile.emails[0]
26 |
27 | const [createdError, createdUser] = await to(
28 | createUser({
29 | provider: profile.provider,
30 | providerId: profile.id,
31 | firstName: profile.name.givenName,
32 | lastName: profile.name.familyName,
33 | displayName: profile.displayName,
34 | email: verifiedEmail.value,
35 | password: null,
36 | role: ROLES.Customer
37 | })
38 | )
39 |
40 | return done(createdError, createdUser)
41 | }
42 |
43 | passport.use(new GoogleStrategy(strategyOptions, verifyCallback))
44 |
45 | app.get(
46 | `${process.env.BASE_API_URL}/auth/google`,
47 | passport.authenticate('google', {
48 | scope: [
49 | 'https://www.googleapis.com/auth/userinfo.profile',
50 | 'https://www.googleapis.com/auth/userinfo.email'
51 | ]
52 | })
53 | )
54 |
55 | app.get(
56 | `${process.env.BASE_API_URL}/auth/google/callback`,
57 | passport.authenticate('google', { failureRedirect: '/login' }),
58 | (req, res) => {
59 | return res
60 | .status(200)
61 | .cookie('jwt', signToken(req.user), {
62 | httpOnly: true
63 | })
64 | .redirect(getRedirectUrl(req.user.role))
65 | }
66 | )
67 |
68 | return app
69 | }
70 |
71 | export { strategy }
72 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/strategies/index.js:
--------------------------------------------------------------------------------
1 | import { strategy as JWTStrategy } from './jwt'
2 | import { strategy as GoogleStrategy } from './google'
3 | import { strategy as FacebookStrategy } from './facebook'
4 |
5 | export { JWTStrategy, GoogleStrategy, FacebookStrategy }
6 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/strategies/jwt.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import passportJWT from 'passport-jwt'
3 | import { to } from 'await-to-js'
4 |
5 | import { getUserById } from '../../database/user'
6 | import { signToken } from '../utils'
7 |
8 | const JWTStrategy = passportJWT.Strategy
9 |
10 | const strategy = () => {
11 | const strategyOptions = {
12 | jwtFromRequest: req => req.cookies.jwt,
13 | secretOrKey: process.env.JWT_SECRET,
14 | passReqToCallback: true
15 | }
16 |
17 | const verifyCallback = async (req, jwtPayload, cb) => {
18 | const [err, user] = await to(getUserById(jwtPayload.data._id))
19 |
20 | if (err) {
21 | return cb(err)
22 | }
23 |
24 | req.user = user
25 | return cb(null, user)
26 | }
27 |
28 | passport.use(new JWTStrategy(strategyOptions, verifyCallback))
29 | }
30 |
31 | const login = (req, user) => {
32 | return new Promise((resolve, reject) => {
33 | req.login(user, { session: false }, err => {
34 | if (err) {
35 | return reject(err)
36 | }
37 |
38 | return resolve(signToken(user))
39 | })
40 | })
41 | }
42 |
43 | export { strategy, login }
44 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/auth/utils.js:
--------------------------------------------------------------------------------
1 | import passport from 'passport'
2 | import jwt from 'jsonwebtoken'
3 | import bcrypt from 'bcrypt'
4 | import { UserModel } from '../database/schema'
5 | import { ROLES } from '../../utils'
6 |
7 | const setup = () => {
8 | passport.serializeUser((user, done) => done(null, user._id))
9 |
10 | passport.deserializeUser(async (id, done) => {
11 | try {
12 | const user = await UserModel.findById(id)
13 | return done(null, user)
14 | } catch (err) {
15 | return done(err, null)
16 | }
17 | })
18 | }
19 |
20 | const signToken = user => {
21 | return jwt.sign({ data: user }, process.env.JWT_SECRET, {
22 | expiresIn: 604800
23 | })
24 | }
25 |
26 | const hashPassword = async password => {
27 | if (!password) {
28 | throw new Error('Password was not provided')
29 | }
30 |
31 | const salt = await bcrypt.genSalt(10)
32 | return await bcrypt.hash(password, salt)
33 | }
34 |
35 | const verifyPassword = async (candidate, actual) => {
36 | return await bcrypt.compare(candidate, actual)
37 | }
38 |
39 | const checkIsInRole = (...roles) => (req, res, next) => {
40 | if (!req.user) {
41 | return res.redirect('/login')
42 | }
43 |
44 | const hasRole = roles.find(role => req.user.role === role)
45 | if (!hasRole) {
46 | return res.redirect('/login')
47 | }
48 |
49 | return next()
50 | }
51 |
52 | const getRedirectUrl = role => {
53 | switch (role) {
54 | case ROLES.Admin:
55 | return '/admin-dashboard'
56 | case ROLES.Customer:
57 | return '/customer-dashboard'
58 | default:
59 | return '/'
60 | }
61 | }
62 |
63 | export { setup, signToken, hashPassword, verifyPassword, checkIsInRole, getRedirectUrl }
64 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/connection.js:
--------------------------------------------------------------------------------
1 | import { connect, connection } from 'mongoose'
2 |
3 | const connectToDatabase = async () =>
4 | await connect(
5 | process.env.DB_CONNECTION_STRING || '',
6 | {
7 | useFindAndModify: false,
8 | autoIndex: false, // Don't build indexes
9 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect
10 | reconnectInterval: 500, // Reconnect every 500ms
11 | poolSize: 10, // Maintain up to 10 socket connections
12 | // If not connected, return errors immediately rather than waiting for reconnect
13 | bufferMaxEntries: 0,
14 | useNewUrlParser: true
15 | }
16 | )
17 |
18 | export { connectToDatabase, connection }
19 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/schema/index.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from './user'
2 |
3 | export { UserModel }
4 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/schema/user.js:
--------------------------------------------------------------------------------
1 | import { model, Schema } from 'mongoose'
2 |
3 | const UserSchema = new Schema({
4 | email: String,
5 | password: String,
6 | businessName: String,
7 | firstName: String,
8 | lastName: String,
9 | displayName: String,
10 | providerId: String,
11 | provider: String,
12 | role: String
13 | })
14 |
15 | const UserModel = model('User', UserSchema)
16 |
17 | export { UserModel }
18 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/user/create.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function createUser({
4 | firstName,
5 | lastName,
6 | email,
7 | password,
8 | providerId,
9 | provider
10 | }) {
11 | return new Promise(async (resolve, reject) => {
12 | const user = await UserModel.findOne({ email })
13 |
14 | if (user) {
15 | reject('Email is already in use')
16 | }
17 |
18 | resolve(
19 | await UserModel.create({
20 | providerId,
21 | provider,
22 | firstName,
23 | lastName,
24 | email,
25 | password
26 | })
27 | )
28 | })
29 | }
30 |
31 | export { createUser }
32 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/user/get.js:
--------------------------------------------------------------------------------
1 | import { UserModel } from '../schema'
2 |
3 | async function getUserById(id) {
4 | return await UserModel.findById(id).exec()
5 | }
6 |
7 | async function getUserByEmail(email) {
8 | return await UserModel.findOne({ email }).exec()
9 | }
10 |
11 | async function getUserByProviderId(providerId) {
12 | return await UserModel.findOne({ providerId }).exec()
13 | }
14 |
15 | export { getUserById, getUserByEmail, getUserByProviderId }
16 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/database/user/index.js:
--------------------------------------------------------------------------------
1 | import { getUserById, getUserByEmail, getUserByProviderId } from './get'
2 | import { createUser } from './create'
3 |
4 | export { getUserById, getUserByEmail, createUser, getUserByProviderId }
5 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 |
3 | import express from 'express'
4 | import next from 'next'
5 | import { urlencoded, json } from 'body-parser'
6 | import cookieParser from 'cookie-parser'
7 | import passport from 'passport'
8 |
9 | import router from './router'
10 | import { connectToDatabase } from './database/connection'
11 | import { initialiseAuthentication, utils } from './auth'
12 | import { ROLES } from '../utils'
13 |
14 | const dev = process.env.NODE_ENV !== 'production'
15 | const nextApp = next({ dev })
16 | const handle = nextApp.getRequestHandler()
17 |
18 | const port = 3000
19 |
20 | nextApp.prepare().then(async () => {
21 | const app = express()
22 |
23 | app.use(urlencoded({ extended: true }))
24 | app.use(json())
25 | app.use(cookieParser())
26 |
27 | app.use(passport.initialize())
28 |
29 | router(app)
30 | initialiseAuthentication(app)
31 |
32 | app.get(
33 | '/admin-dashboard',
34 | passport.authenticate('jwt', { failureRedirect: '/login' }),
35 | utils.checkIsInRole(ROLES.Admin),
36 | (req, res) => {
37 | return handle(req, res)
38 | }
39 | )
40 |
41 | app.get(
42 | '/customer-dashboard',
43 | passport.authenticate('jwt', { failureRedirect: '/login' }),
44 | utils.checkIsInRole(ROLES.Customer),
45 | (req, res) => {
46 | return handle(req, res)
47 | }
48 | )
49 |
50 | app.get(
51 | '/both-dashboard',
52 | passport.authenticate('jwt', { failureRedirect: '/login' }),
53 | utils.checkIsInRole(ROLES.Admin, ROLES.Customer),
54 | (req, res) => {
55 | return handle(req, res)
56 | }
57 | )
58 |
59 | app.get('*', (req, res) => {
60 | return handle(req, res)
61 | })
62 |
63 | await connectToDatabase()
64 |
65 | app.listen(port, err => {
66 | if (err) throw err
67 | console.log(`> Ready on localhost:${port}`)
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/router/auth.routes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import { to } from 'await-to-js'
3 | import { verifyPassword, hashPassword, getRedirectUrl } from '../auth/utils'
4 | import { login } from '../auth/strategies/jwt'
5 | import { createUser, getUserByEmail } from '../database/user'
6 |
7 | const router = express.Router()
8 |
9 | router.post('/login', async (req, res) => {
10 | const { email, password } = req.body
11 | const [err, user] = await to(getUserByEmail(email))
12 |
13 | const authenticationError = () => {
14 | return res.status(500).json({ success: false, data: 'Authentication error!' })
15 | }
16 |
17 | if (!(await verifyPassword(password, user.password))) {
18 | console.error('Passwords do not match')
19 | return authenticationError()
20 | }
21 |
22 | const [loginErr, token] = await to(login(req, user))
23 |
24 | if (loginErr) {
25 | console.error('Log in error', loginErr)
26 | return authenticationError()
27 | }
28 |
29 | return res
30 | .status(200)
31 | .cookie('jwt', token, {
32 | httpOnly: true
33 | })
34 | .json({
35 | success: true,
36 | data: getRedirectUrl(user.role)
37 | })
38 | })
39 |
40 | router.post('/register', async (req, res) => {
41 | const { firstName, lastName, email, password } = req.body
42 |
43 | if (!/\b\w+\@\w+\.\w+(?:\.\w+)?\b/.test(email)) {
44 | return res.status(500).json({ success: false, data: 'Enter a valid email address.' })
45 | } else if (password.length < 5 || password.length > 20) {
46 | return res.status(500).json({
47 | success: false,
48 | data: 'Password must be between 5 and 20 characters.'
49 | })
50 | }
51 |
52 | let [err, user] = await to(
53 | createUser({
54 | firstName,
55 | lastName,
56 | email,
57 | password: await hashPassword(password)
58 | })
59 | )
60 |
61 | if (err) {
62 | return res.status(500).json({ success: false, data: 'Email is already taken' })
63 | }
64 |
65 | const [loginErr, token] = await to(login(req, user))
66 |
67 | if (loginErr) {
68 | console.error(loginErr)
69 | return res.status(500).json({ success: false, data: 'Authentication error!' })
70 | }
71 |
72 | return res
73 | .status(200)
74 | .cookie('jwt', token, {
75 | httpOnly: true
76 | })
77 | .json({
78 | success: true,
79 | data: getRedirectUrl(user.role)
80 | })
81 | })
82 |
83 | export default router
84 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/server/router/index.js:
--------------------------------------------------------------------------------
1 | import authRoutes from './auth.routes'
2 |
3 | function Router(app) {
4 | app.use(`${process.env.BASE_API_URL}/auth`, authRoutes)
5 | }
6 |
7 | export default Router
8 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jpreecedev/passport-next-auth-tutorial/9fe9013c5f99a179773b60b8ae3f3753529c6d16/passport-role-based-authorisation/static/favicon.ico
--------------------------------------------------------------------------------
/passport-role-based-authorisation/utils/index.js:
--------------------------------------------------------------------------------
1 | import * as server from './server'
2 | import { ROLES } from './roles'
3 |
4 | export { server, ROLES }
5 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/utils/roles.js:
--------------------------------------------------------------------------------
1 | const ROLES = {
2 | Admin: 'Admin',
3 | Customer: 'Customer'
4 | }
5 |
6 | export { ROLES }
7 |
--------------------------------------------------------------------------------
/passport-role-based-authorisation/utils/server.js:
--------------------------------------------------------------------------------
1 | function getServerApiUrl() {
2 | return process.env.BASE_API_URL
3 | }
4 |
5 | const callFetchAsync = async (url, method, body, headers = {}) => {
6 | try {
7 | const options = {
8 | headers: new Headers({
9 | 'Content-Type': 'application/json',
10 | ...headers
11 | }),
12 | body
13 | }
14 |
15 | if (body) {
16 | options.body = JSON.stringify(body)
17 | }
18 |
19 | const response = await fetch(`${getServerApiUrl()}${url}`, {
20 | method,
21 | credentials: 'same-origin',
22 | ...options
23 | })
24 |
25 | return await response.json()
26 | } catch (err) {
27 | return {
28 | success: false,
29 | data: err
30 | }
31 | }
32 | }
33 |
34 | const postAsync = (url, body) => {
35 | return callFetchAsync(url, 'POST', body)
36 | }
37 |
38 | export { postAsync }
39 |
--------------------------------------------------------------------------------