├── .gitignore ├── README.md ├── client ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── actions │ │ ├── index.js │ │ └── types.js │ ├── components │ │ ├── auth │ │ │ ├── form_helpers.js │ │ │ ├── require_auth.js │ │ │ ├── signin.js │ │ │ ├── signin_form.js │ │ │ ├── signout.js │ │ │ ├── signup.js │ │ │ └── signup_form.js │ │ ├── feature.js │ │ ├── header.js │ │ └── welcome.js │ ├── index.js │ ├── reducers │ │ ├── auth_reducer.js │ │ └── index.js │ └── style │ │ └── style.css └── yarn.lock └── server ├── controllers └── authentication.js ├── index.js ├── models └── user.js ├── package.json ├── router.js └── services └── passport.js /.gitignore: -------------------------------------------------------------------------------- 1 | server/node_modules 2 | server/config.js 3 | client/node_modules 4 | client/bundle.js 5 | client/npm-debug.log 6 | client/yarn-debug.log 7 | client/.DS_Store 8 | client/build 9 | client/coverage 10 | 11 | # IntelliJ 12 | *.iml 13 | /.idea 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-router-v4-redux-auth 2 | 3 | This is an application that models the implementation of JWT with the following: 4 | 5 | frontend: 6 | * react-router v4 7 | * react 8 | * redux 9 | * redux-form v6.7 10 | * material-ui 11 | 12 | backend: 13 | * express.js 14 | * MongoDB 15 | * passport.js 16 | 17 | 18 | 19 | ## Usage: 20 | 21 | 1. Start a MongoDB server running on port `27017` 22 | 23 | 1. In `/server`, create a new file called `config.js` to hold your JWT `secretOrKey`. Here is an example: 24 | 25 | ```javascript 26 | /* 27 | /server/config.js 28 | */ 29 | 30 | module.exports = { 31 | secret: 'sdajkljdsalkj8932904ujaskfs' 32 | } 33 | ``` 34 | 35 | 1. Navigate to `/server` in your terminal 36 | 1. run `npm install`. 37 | 1. run `npm run dev` to start the server 38 | 39 | 1. Navigate to `/client` in a new terminal window 40 | 1. run `yarn install` 41 | 1. run `yarn start` to start the webpack dev server 42 | 43 | 1. Navigate to `localhost:3000` in your browser 44 | 45 | 46 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.16.1", 7 | "lodash": "^4.17.4", 8 | "material-ui": "^0.18.0", 9 | "react": "^15.5.4", 10 | "react-dom": "^15.5.4", 11 | "react-redux": "^5.0.4", 12 | "react-router-dom": "^4.1.1", 13 | "react-tap-event-plugin": "^2.0.1", 14 | "redux": "^3.6.0", 15 | "redux-form": "^6.7.0", 16 | "redux-thunk": "^2.2.0" 17 | }, 18 | "devDependencies": { 19 | "react-scripts": "0.9.5" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test --env=jsdom", 25 | "eject": "react-scripts eject" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gillisd/react-router-v4-redux-auth/39bb35e50eb1af131711858f8012459191e297fe/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 17 | React App 18 | 19 | 20 |
21 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/src/actions/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { UNAUTH_USER, AUTH_USER, AUTH_ERROR, FETCH_MESSAGE } from './types' 3 | const ROOT_URL = 'http://localhost:3090' 4 | 5 | export function signinUser({email, password}) { 6 | 7 | return function (dispatch) { 8 | 9 | // submit email and password to server 10 | const request = axios.post(`${ROOT_URL}/signin`, {email, password}) 11 | request 12 | .then(response => { 13 | // -Save the JWT token 14 | localStorage.setItem('token', response.data.token) 15 | 16 | // -if request is good, we need to update state to indicate user is authenticated 17 | dispatch({type: AUTH_USER}) 18 | }) 19 | 20 | // If request is bad... 21 | // -Show an error to the user 22 | .catch(() => { 23 | dispatch(authError('bad login info')) 24 | }) 25 | 26 | } 27 | } 28 | 29 | export function signoutUser() { 30 | localStorage.removeItem('token') 31 | return { 32 | type: UNAUTH_USER 33 | } 34 | } 35 | 36 | export function signupUser({email, password, passwordConfirmation}) { 37 | return function (dispatch) { 38 | axios.post(`${ROOT_URL}/signup`, {email, password, passwordConfirmation}) 39 | .then(response => { 40 | dispatch({type: AUTH_USER}) 41 | localStorage.setItem('token', response.data.token) 42 | }) 43 | .catch(({response}) => { 44 | dispatch(authError(response.data.error)) 45 | }) 46 | } 47 | } 48 | 49 | export function authError(error) { 50 | return { 51 | type: AUTH_ERROR, 52 | payload: error 53 | } 54 | } 55 | 56 | export function fetchMessage() { 57 | return function (dispatch) { 58 | axios.get(ROOT_URL, { 59 | headers: {authorization: localStorage.getItem('token')} 60 | }) 61 | .then(response => { 62 | dispatch({ 63 | type: FETCH_MESSAGE, 64 | payload: response.data.message 65 | }) 66 | }) 67 | } 68 | } -------------------------------------------------------------------------------- /client/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const AUTH_USER = 'auth_user' 2 | export const UNAUTH_USER = 'unauth_user' 3 | export const AUTH_ERROR = 'auth_error' 4 | export const FETCH_MESSAGE = 'fetch_message' -------------------------------------------------------------------------------- /client/src/components/auth/form_helpers.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TextField from 'material-ui/TextField' 3 | 4 | export const renderTextField = ({input, type, label, meta: {touched, error}, ...custom}) => ( // Define stateless component to render input and errors 5 |
6 | 12 | {touched && error && {error}} 13 |
14 | ) 15 | -------------------------------------------------------------------------------- /client/src/components/auth/require_auth.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Route, Redirect } from 'react-router-dom' 4 | 5 | export const PrivateRoute = ({component: ComposedComponent, ...rest}) => { 6 | 7 | class Authentication extends Component { 8 | 9 | // redirect if not authenticated; otherwise, return the component imputted into 10 | handleRender(props) { 11 | if (!this.props.authenticated) { 12 | return 19 | } else { 20 | return 21 | } 22 | } 23 | 24 | render() { 25 | return ( 26 | 27 | ) 28 | } 29 | } 30 | 31 | function mapStateToProps(state) { 32 | return {authenticated: state.auth.authenticated}; 33 | } 34 | 35 | const AuthenticationContainer = connect(mapStateToProps)(Authentication) 36 | return 37 | } -------------------------------------------------------------------------------- /client/src/components/auth/signin.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import SigninForm from './signin_form' 3 | import * as actions from '../../actions' 4 | import { connect } from 'react-redux' 5 | import { Redirect } from 'react-router-dom' 6 | 7 | class Signin extends Component { 8 | 9 | componentWillUnmount() { 10 | if (this.props.errorMessage) { 11 | this.props.authError(null) 12 | } 13 | } 14 | 15 | displayRedirectMessages() { 16 | const location = this.props.location 17 | return location.state &&
{location.state.message}
18 | } 19 | 20 | handleSubmit({email, password}) { 21 | this.props.signinUser({email, password}) 22 | } 23 | 24 | getRedirectPath() { 25 | const locationState = this.props.location.state 26 | if (locationState && locationState.from.pathname) { 27 | return locationState.from.pathname // redirects to referring url 28 | } else { 29 | return '/' 30 | } 31 | } 32 | 33 | render() { 34 | return (this.props.authenticated) ? 35 | 40 | : 41 |
42 | {this.displayRedirectMessages()} 43 | 44 |
45 | } 46 | } 47 | 48 | function mapStateToProps(state) { 49 | return { 50 | authenticated: state.auth.authenticated, 51 | errorMessage: state.auth.error 52 | } 53 | } 54 | 55 | export default connect(mapStateToProps, actions)(Signin) -------------------------------------------------------------------------------- /client/src/components/auth/signin_form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { reduxForm, Field } from 'redux-form' 3 | import { renderTextField } from './form_helpers' 4 | import RaisedButton from 'material-ui/RaisedButton' 5 | 6 | 7 | class SigninForm extends Component { 8 | 9 | renderAlert() { 10 | if (this.props.errorMessage) { 11 | return
12 | Oops: {this.props.errorMessage} 13 |
14 | } 15 | } 16 | 17 | render() { 18 | const {handleSubmit} = this.props 19 | 20 | return ( 21 |
22 | {this.renderAlert()} 23 |
24 | 25 | 30 | 31 | 36 | 37 | 38 | 39 |
40 | ) 41 | } 42 | } 43 | 44 | export default reduxForm({ 45 | form: 'signin' 46 | })(SigninForm) 47 | -------------------------------------------------------------------------------- /client/src/components/auth/signout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from '../../actions' 4 | class Signout extends Component { 5 | 6 | componentWillMount() { 7 | this.props.signoutUser() 8 | } 9 | 10 | render() { 11 | return
Bye Bye
12 | } 13 | } 14 | 15 | export default connect(null, actions)(Signout) -------------------------------------------------------------------------------- /client/src/components/auth/signup.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import SignupForm from './signup_form' 4 | import * as actions from '../../actions' 5 | import { Redirect } from 'react-router-dom' 6 | 7 | class Signup extends Component { 8 | 9 | componentWillUnmount() { 10 | if (this.props.errorMessage) { 11 | this.props.authError(null) 12 | } 13 | } 14 | 15 | handleSubmit({email, password, passwordConfirmation}) { 16 | this.props.signupUser({email, password, passwordConfirmation}) 17 | } 18 | 19 | getRedirectPath() { 20 | const locationState = this.props.location.state 21 | if (locationState && locationState.from.pathname) { 22 | return locationState.from.pathname 23 | } else { 24 | return '/' 25 | } 26 | } 27 | 28 | render() { 29 | return (this.props.authenticated) ? 30 | 35 | : 36 |
37 | 38 |
39 | } 40 | } 41 | 42 | function mapStateToProps(state) { 43 | return { 44 | authenticated: state.auth.authenticated, 45 | errorMessage: state.auth.error 46 | } 47 | } 48 | 49 | export default connect(mapStateToProps, actions)(Signup) -------------------------------------------------------------------------------- /client/src/components/auth/signup_form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { reduxForm, Field } from 'redux-form' 3 | import RaisedButton from 'material-ui/RaisedButton' 4 | import { renderTextField } from './form_helpers' 5 | 6 | class SignupForm extends Component { 7 | 8 | renderAlert() { 9 | if (this.props.errorMessage) { 10 | return
11 | Oops: {this.props.errorMessage} 12 |
13 | } 14 | } 15 | 16 | render() { 17 | const {handleSubmit} = this.props 18 | 19 | return ( 20 |
21 | {this.renderAlert()} 22 |
23 | 24 | 29 | 30 | 35 | 36 | 41 | 42 | 43 | 44 |
45 | ) 46 | } 47 | } 48 | 49 | const validate = values => { 50 | const errors = {} 51 | 52 | if (values.password !== values.passwordConfirmation) { 53 | errors.password = 'Passwords must match' 54 | } 55 | 56 | if (!values.email) { 57 | errors.email = 'Please enter an email' 58 | } 59 | 60 | if (!values.password) { 61 | errors.password = 'Please enter a password' 62 | } 63 | 64 | if (!values.passwordConfirmation) { 65 | errors.passwordConfirmation = 'Please confirm your password' 66 | } 67 | 68 | return errors 69 | } 70 | 71 | 72 | export default reduxForm({ 73 | form: 'signup', 74 | validate 75 | })(SignupForm) -------------------------------------------------------------------------------- /client/src/components/feature.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import * as actions from '../actions' 4 | import { Link } from 'react-router-dom' 5 | 6 | class Feature extends Component { 7 | 8 | componentWillMount() { 9 | this.props.fetchMessage() 10 | } 11 | 12 | render() { 13 | return ( 14 |
15 |

Welcome to the secure page!

16 |
17 |

Here's a secret response from the server that your token returned:

18 | ____________________________________________________________ 19 |

{this.props.message}

20 | ____________________________________________________________ 21 |
22 |

Notice that clicking these links redirect to the homepage, as you are already signed in:

23 |

24 | /signin | /signup 25 |

26 |
27 | 28 | ) 29 | } 30 | } 31 | 32 | function mapStateToProps(state) { 33 | return { 34 | message: state.auth.message 35 | } 36 | } 37 | 38 | export default connect(mapStateToProps, actions)(Feature) -------------------------------------------------------------------------------- /client/src/components/header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { connect } from 'react-redux' 3 | import { Link } from 'react-router-dom' 4 | 5 | class Header extends Component { 6 | 7 | renderLinks() { 8 | if (this.props.authenticated) { 9 | return [ 10 |
  • 11 | Sign Out 12 |
  • , 13 |
  • 14 | Protected Site 15 |
  • 16 | ] 17 | } else { 18 | return [ 19 |
  • 20 | Sign In 21 |
  • , 22 |
  • 23 | Sign Up 24 |
  • , 25 |
  • 26 | Protected Site 27 |
  • 28 | ] 29 | } 30 | } 31 | 32 | render() { 33 | return ( 34 | 40 | ) 41 | } 42 | } 43 | 44 | function mapStateToProps(state) { 45 | return { 46 | authenticated: state.auth.authenticated 47 | } 48 | } 49 | 50 | export default connect(mapStateToProps,)(Header) -------------------------------------------------------------------------------- /client/src/components/welcome.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default () => { 4 | return ( 5 |
    6 | This is the landing page... 7 | sign in to access the secure page 8 |
    9 | ) 10 | } 11 | 12 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { Provider } from 'react-redux' 4 | import { createStore, applyMiddleware } from 'redux' 5 | import reduxThunk from 'redux-thunk' 6 | import { BrowserRouter as Router, Route } from 'react-router-dom' 7 | import { AUTH_USER } from './actions/types' 8 | import Header from './components/header' 9 | import Welcome from './components/welcome' 10 | import injectTapEventPlugin from 'react-tap-event-plugin' 11 | import Signin from './components/auth/signin' 12 | import Signout from './components/auth/signout' 13 | import Signup from './components/auth/signup' 14 | import { PrivateRoute } from './components/auth/require_auth' 15 | import Feature from './components/feature' 16 | import reducers from './reducers' 17 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider' 18 | 19 | 20 | // Needed for onTouchTap with material-ui 21 | // http://stackoverflow.com/a/34015469/988941 22 | 23 | injectTapEventPlugin() 24 | 25 | const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore) 26 | const store = createStoreWithMiddleware(reducers) 27 | const token = localStorage.getItem('token') 28 | 29 | if (token) { 30 | store.dispatch({type: AUTH_USER}) 31 | } 32 | 33 | 34 | ReactDOM.render( 35 | 36 | 37 | 38 |
    39 |
    40 | 41 | 42 | 43 | 44 | 45 |
    46 |
    47 |
    48 |
    49 | , document.getElementById('root')) 50 | -------------------------------------------------------------------------------- /client/src/reducers/auth_reducer.js: -------------------------------------------------------------------------------- 1 | import { AUTH_USER, UNAUTH_USER, AUTH_ERROR, FETCH_MESSAGE } from '../actions/types' 2 | 3 | export default function authReducer(state = {}, action) { 4 | switch (action.type) { 5 | case AUTH_USER: 6 | return {...state, error: '', authenticated: true} 7 | case UNAUTH_USER: 8 | return {...state, authenticated: false} 9 | case AUTH_ERROR: 10 | return {...state, error: action.payload} 11 | case FETCH_MESSAGE: 12 | return {...state, message: action.payload} 13 | default: 14 | return state 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /client/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import { reducer as form } from 'redux-form' 3 | import authReducer from './auth_reducer' 4 | 5 | const rootReducer = combineReducers({ 6 | form, 7 | auth: authReducer 8 | }) 9 | 10 | export default rootReducer 11 | -------------------------------------------------------------------------------- /client/src/style/style.css: -------------------------------------------------------------------------------- 1 | .error { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /server/controllers/authentication.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jwt-simple'); 2 | const User = require('../models/user'); 3 | const config = require('../config'); 4 | 5 | function tokenForUser(user) { 6 | const timestamp = new Date().getTime(); 7 | return jwt.encode({sub: user.id, iat: timestamp}, config.secret) 8 | } 9 | 10 | exports.signin = function (req, res, next) { 11 | // User has already had their email and password auth'd 12 | // We just need to give them a token 13 | res.send({token: tokenForUser(req.user)}) 14 | } 15 | 16 | exports.signup = function (req, res, next) { 17 | const email = req.body.email; 18 | const password = req.body.password; 19 | // See if a user with the given email exists 20 | 21 | 22 | if (!email || !password) { 23 | return res.status(422).send({error: 'You must provide email and password'}) 24 | } 25 | 26 | User.findOne({email: email}, function (err, existingUser) { 27 | if (err) { 28 | return next(err); 29 | } 30 | 31 | // If a user with a given email does exist, return an error 32 | 33 | if (existingUser) { 34 | return res.status(422).send({error: 'Email is in use'}); 35 | } 36 | 37 | const user = new User({ 38 | email: email, 39 | password: password 40 | }); 41 | 42 | user.save(function (err) { 43 | if (err) { 44 | return next(err); 45 | } 46 | 47 | res.json({token: tokenForUser(user)}); 48 | }); 49 | 50 | }); 51 | 52 | 53 | // If a user with email does not exist, create and save a user record 54 | 55 | // Respond to request indicating that the user was created 56 | } -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const http = require('http'); 3 | const bodyparser = require('body-parser'); 4 | const morgan = require('morgan'); 5 | const app = express(); 6 | const router = require('./router') 7 | const mongoose = require('mongoose') 8 | const cors = require('cors') 9 | 10 | mongoose.connect('mongodb://localhost:auth/auth') 11 | 12 | app.use(morgan('combined')); 13 | app.use(cors()); 14 | app.use(bodyparser.json({type: '*/*'})) 15 | 16 | router(app); 17 | const port = process.env.PORT || 3090; 18 | const server = http.createServer(app); 19 | server.listen(port); 20 | console.log('Server listening on:', port); 21 | 22 | -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const bcrypt = require('bcrypt-nodejs') 4 | 5 | 6 | const userSchema = new Schema({ 7 | email: {type: String, unique: true, lowercase: true}, 8 | password: String 9 | }); 10 | 11 | // On Save Hook, encrypt password 12 | 13 | userSchema.pre('save', function (next) { 14 | const user = this; 15 | bcrypt.genSalt(10, function (err, salt) { 16 | if (err) { 17 | return next(err); 18 | } 19 | 20 | bcrypt.hash(user.password, salt, null, function (err, hash) { 21 | if (err) { 22 | return next(err); 23 | } 24 | 25 | user.password = hash; 26 | next(); 27 | }); 28 | 29 | }); 30 | }); 31 | 32 | userSchema.methods.comparePassword = function (candidatePassword, callback) { 33 | bcrypt.compare(candidatePassword, this.password, function (err, isMatch) { 34 | if (err) { 35 | return callback(err); 36 | } 37 | callback(null, isMatch); 38 | }); 39 | } 40 | 41 | const ModelClass = mongoose.model('user', userSchema); 42 | 43 | module.exports = ModelClass; -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon index.js" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt-nodejs": "0.0.3", 14 | "body-parser": "^1.17.1", 15 | "cors": "^2.8.3", 16 | "express": "^4.15.2", 17 | "jwt-simple": "^0.5.1", 18 | "mongoose": "^4.9.8", 19 | "morgan": "^1.8.1", 20 | "nodemon": "^1.11.0", 21 | "passport": "^0.3.2", 22 | "passport-jwt": "^2.2.1", 23 | "passport-local": "^1.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const Authentication = require('./controllers/authentication'); 2 | const passportService = require('./services/passport'); 3 | const passport = require('passport'); 4 | 5 | const requireAuth = passport.authenticate('jwt', {session: false}); 6 | const requireSignIn = passport.authenticate('local', {session: false}); 7 | 8 | module.exports = function (app) { 9 | app.get('/', requireAuth, function (req, res) { 10 | res.send({message: 'S3CR3T M3SS4G3'}); 11 | }); 12 | app.post('/signup', Authentication.signup); 13 | app.post('/signin', requireSignIn, Authentication.signin); 14 | } -------------------------------------------------------------------------------- /server/services/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const User = require('../models/user'); 3 | const config = require('../config'); 4 | const JwtStrategy = require('passport-jwt').Strategy; 5 | const ExtractJwt = require('passport-jwt').ExtractJwt; 6 | const LocalStrategy = require('passport-local'); 7 | 8 | 9 | // Create local strategy 10 | const localOptions = {usernameField: 'email'}; 11 | const localLogin = new LocalStrategy(localOptions, function (email, password, done) { 12 | // Verify this email and password, call done with the user 13 | // if it is correct email and password 14 | // otherwise call done with false 15 | User.findOne({email: email}, function (err, user) { 16 | if (err) { 17 | return done(err) 18 | } 19 | if (!user) { 20 | return done(null, false); 21 | } 22 | user.comparePassword(password, function (err, isMatch) { 23 | if (err) { 24 | return done(err); 25 | } 26 | if (!isMatch) { 27 | return done(null, false); 28 | } 29 | return done(null, user); 30 | }) 31 | // compare passwords - is `password` equal to user.password? 32 | }) 33 | 34 | }); 35 | 36 | 37 | // Setup options for JWT strategy 38 | 39 | const jwtOptions = { 40 | jwtFromRequest: ExtractJwt.fromHeader('authorization'), 41 | secretOrKey: config.secret 42 | }; 43 | 44 | // Create jwt strategy 45 | 46 | const jwtLogin = new JwtStrategy(jwtOptions, function (payload, done) { 47 | // See if the user ID in the payload exists in our database 48 | // If it does, call 'done' with that other 49 | // otherwise, call done without a user object 50 | User.findById(payload.sub, function (err, user) { 51 | if (err) { 52 | return done(err, false); 53 | } 54 | 55 | if (user) { 56 | done(null, user); 57 | } else { 58 | done(null, false); 59 | } 60 | }); 61 | }); 62 | 63 | 64 | // Tell passport to use this strategy 65 | passport.use(jwtLogin); 66 | passport.use(localLogin); --------------------------------------------------------------------------------