├── .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 |
61 | setFormData({ ...formData, email: e.target.value })} 72 | /> 73 | setFormData({ ...formData, email: e.target.value })} 84 | /> 85 | 86 | 99 | 100 | 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 |
64 | setFormData({ ...formData, firstName: e.target.value })} 75 | /> 76 | setFormData({ ...formData, lastName: e.target.value })} 86 | /> 87 | setFormData({ ...formData, email: e.target.value })} 97 | /> 98 | setFormData({ ...formData, password: e.target.value })} 109 | /> 110 | 111 | 124 | 125 | 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 |
57 | 64 | 65 | 66 |
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 |
77 | setFormData({ ...formData, email: e.target.value })} 88 | /> 89 | setFormData({ ...formData, password: e.target.value })} 100 | /> 101 | 102 | 115 | 116 | Social Login Providers 117 | 118 | 119 | 120 | 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 |
80 | setFormData({ ...formData, firstName: e.target.value })} 91 | /> 92 | setFormData({ ...formData, lastName: e.target.value })} 102 | /> 103 | setFormData({ ...formData, email: e.target.value })} 113 | /> 114 | setFormData({ ...formData, password: e.target.value })} 125 | /> 126 | 127 | 140 | 141 | 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 | 61 | 65 | 69 | 73 | 77 | 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 |
78 | setFormData({ ...formData, email: e.target.value })} 89 | /> 90 | setFormData({ ...formData, password: e.target.value })} 101 | /> 102 | 103 | 116 | 117 | Social Login Providers 118 | 119 | 120 | 121 | 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 |
80 | setFormData({ ...formData, firstName: e.target.value })} 91 | /> 92 | setFormData({ ...formData, lastName: e.target.value })} 102 | /> 103 | setFormData({ ...formData, email: e.target.value })} 113 | /> 114 | setFormData({ ...formData, password: e.target.value })} 125 | /> 126 | 127 | 140 | 141 | 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 |
76 | setFormData({ ...formData, email: e.target.value })} 87 | /> 88 | setFormData({ ...formData, password: e.target.value })} 99 | /> 100 | 101 | 114 | 115 | 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 |
80 | setFormData({ ...formData, firstName: e.target.value })} 91 | /> 92 | setFormData({ ...formData, lastName: e.target.value })} 102 | /> 103 | setFormData({ ...formData, email: e.target.value })} 113 | /> 114 | setFormData({ ...formData, password: e.target.value })} 125 | /> 126 | 127 | 140 | 141 | 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 |
57 | 64 | 65 | 66 |
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 | 61 | 65 | 69 | 73 | 77 | 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 |
79 | setFormData({ ...formData, email: e.target.value })} 90 | /> 91 | setFormData({ ...formData, password: e.target.value })} 102 | /> 103 | 104 | 117 | 118 | Social Login Providers 119 | 120 | 121 | 122 | 123 | 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 |
80 | setFormData({ ...formData, firstName: e.target.value })} 91 | /> 92 | setFormData({ ...formData, lastName: e.target.value })} 102 | /> 103 | setFormData({ ...formData, email: e.target.value })} 113 | /> 114 | setFormData({ ...formData, password: e.target.value })} 125 | /> 126 | 127 | 140 | 141 | 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 | --------------------------------------------------------------------------------