Last active
July 18, 2019 02:43
-
-
Save luishrd/afecd77686297a3125b68b96ef6371a6 to your computer and use it in GitHub Desktop.
CS10 - JWT Auth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./client/src/App.js | |
import React, { Component } from 'react'; | |
import { Route, withRouter } from 'react-router-dom'; | |
import logo from './logo.svg'; | |
import './App.css'; | |
import Signin from './auth/Signin'; | |
import Users from './users/Users'; | |
class App extends Component { | |
render() { | |
return ( | |
<div className="App"> | |
<header className="App-header"> | |
<img src={logo} className="App-logo" alt="logo" /> | |
<h1 className="App-title">Welcome to React</h1> | |
<div> | |
{localStorage.getItem('jwt') && ( | |
<button onClick={this.signout}>Signout</button> | |
)} | |
</div> | |
</header> | |
<Route path="/signin" component={Signin} /> | |
<Route path="/users" component={Users} /> | |
</div> | |
); | |
} | |
signout = () => { | |
if (localStorage.getItem('jwt')) { | |
localStorage.removeItem('jwt'); | |
this.props.history.push('/signin'); | |
} | |
}; | |
} | |
export default withRouter(App); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./client/package.json | |
{ | |
"name": "client", | |
"version": "0.1.0", | |
"private": true, | |
"dependencies": { | |
"axios": "^0.18.0", | |
"react": "^16.4.1", | |
"react-dom": "^16.4.1", | |
"react-router-dom": "^4.3.1", | |
"react-scripts": "1.1.4" | |
}, | |
"scripts": { | |
"start": "react-scripts start", | |
"build": "react-scripts build", | |
"test": "react-scripts test --env=jsdom", | |
"eject": "react-scripts eject" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./client/src/index.js | |
import React from 'react'; | |
import ReactDOM from 'react-dom'; | |
import { BrowserRouter as Router } from 'react-router-dom'; | |
import registerServiceWorker from './registerServiceWorker'; | |
import './index.css'; | |
import App from './App'; | |
ReactDOM.render( | |
<Router> | |
<App /> | |
</Router>, | |
document.getElementById('root') | |
); | |
registerServiceWorker(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./server/package.json | |
{ | |
"name": "jwts", | |
"version": "1.0.0", | |
"description": "", | |
"main": "server.js", | |
"scripts": { | |
"start": "nodemon" | |
}, | |
"keywords": [], | |
"author": "", | |
"license": "ISC", | |
"dependencies": { | |
"bcrypt": "^2.0.1", | |
"cors": "^2.8.4", | |
"express": "^4.16.3", | |
"jsonwebtoken": "^8.3.0", | |
"mongoose": "^5.1.5" | |
}, | |
"devDependencies": { | |
"nodemon": "^1.17.5" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./server/server.js | |
const express = require('express'); | |
const mongoose = require('mongoose'); | |
const cors = require('cors'); | |
const jwt = require('jsonwebtoken'); | |
const User = require('./users/UserModel.js'); | |
const server = express(); | |
const secret = "toss me, but don't tell the elf!"; | |
const corsOptions = { | |
origin: 'http://localhost:3000', // allow only the React application to connect | |
credentials: true, // sets the Access-Control-Allow-Credentials CORS header | |
}; | |
server.use(express.json()); | |
server.use(cors(corsOptions)); | |
server.post('/api/register', (req, res) => { | |
User.create(req.body) | |
.then(user => { | |
const token = generateToken(user); | |
res.status(201).json({ username: user.username, token }); | |
}) | |
.catch(err => res.status(500).json(err)); | |
}); | |
server.post('/api/login', (req, res) => { | |
const { username, password } = req.body; | |
User.findOne({ username }) | |
.then(user => { | |
if (user) { | |
user | |
.validatePassword(password) | |
.then(passwordsMatch => { | |
if (passwordsMatch) { | |
// generate token | |
const token = generateToken(user); | |
// send token to the client | |
res.status(200).json({ message: `welcome ${username}!`, token }); | |
} else { | |
res.status(401).send('invalid credentials'); | |
} | |
}) | |
.catch(err => { | |
res.send('error comparing passwords'); | |
}); | |
} else { | |
res.status(401).send('invalid credentials'); | |
} | |
}) | |
.catch(err => { | |
res.send(err); | |
}); | |
}); | |
function generateToken(user) { | |
const options = { | |
expiresIn: '1h', | |
}; | |
const payload = { name: user.username }; | |
// sign the token | |
return jwt.sign(payload, secret, options); | |
} | |
function restricted(req, res, next) { | |
const token = req.headers.authorization; | |
if (token) { | |
jwt.verify(token, secret, (err, decodedToken) => { | |
// req.jwtPayload.decodedToken = decodedToken; | |
if (err) { | |
return res | |
.status(401) | |
.json({ message: 'you shall not pass! not decoded' }); | |
} | |
next(); | |
}); | |
} else { | |
res.status(401).json({ message: 'you shall not pass! no token' }); | |
} | |
} | |
server.get('/api/users', restricted, (req, res) => { | |
User.find({}) | |
.select('username') | |
.then(users => { | |
res.status(200).json(users); | |
}) | |
.catch(err => { | |
return res.status(500).json(err); | |
}); | |
}); | |
const port = process.env.PORT || 5000; | |
mongoose | |
.connect('mongodb://localhost/cs10jwt') | |
.then(() => { | |
console.log('\n=== Connected to MongoDB ==='); | |
server.listen(port, (req, res) => { | |
console.log(`\n=== API up on port ${port} ===\n`); | |
}); | |
}) | |
.catch(err => | |
console.log('\n=== Error connecting to MongoDb, is it running? ===\n', err) | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./client/src/auth/signin.js | |
import React from 'react'; | |
import axios from 'axios'; | |
class Signin extends React.Component { | |
state = { | |
username: 'sam', | |
password: 'mellon', | |
}; | |
render() { | |
return ( | |
<form onSubmit={this.submitHandler}> | |
<div> | |
<label>Username</label> | |
<input | |
value={this.state.username} | |
onChange={this.inputChangeHandler} | |
name="username" | |
type="text" | |
/> | |
</div> | |
<div> | |
<label>Password</label> | |
<input | |
value={this.state.password} | |
onChange={this.inputChangeHandler} | |
name="password" | |
type="password" | |
/> | |
</div> | |
<div> | |
<button type="submit">Signin</button> | |
</div> | |
</form> | |
); | |
} | |
submitHandler = event => { | |
event.preventDefault(); | |
axios | |
.post('http://localhost:5000/api/login', this.state) | |
.then(response => { | |
localStorage.setItem('jwt', response.data.token); | |
console.log('signing props', this.props); | |
this.props.history.push('/users'); | |
}) | |
.catch(err => console.log('bad panda!')); | |
}; | |
inputChangeHandler = event => { | |
const { name, value } = event.target; | |
this.setState({ [name]: value }); | |
}; | |
} | |
export default Signin; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./server/users/UserModel.js | |
const mongoose = require('mongoose'); | |
const bcrypt = require('bcrypt'); | |
const userSchema = new mongoose.Schema({ | |
username: { | |
type: String, | |
required: true, | |
unique: true, | |
lowercase: true, | |
}, | |
password: { | |
type: String, | |
required: true, | |
// minlength: 4, // make this at least 12 in production | |
maxlength: 25, | |
validate: checkPasswordLength, | |
msg: 'password is too weak', | |
}, | |
race: { | |
type: String, | |
required: true, | |
validate: { | |
validator: /(hobbit|human|elf)/i, | |
msg: 'invalid race', | |
}, | |
}, | |
}); | |
function checkPasswordLength(password) { | |
return password.length > 12; | |
} | |
userSchema.pre('save', function(next) { | |
return bcrypt | |
.hash(this.password, 10) // this time we'll use promises instead of a callback | |
.then(hash => { | |
this.password = hash; | |
return next(); | |
}) | |
.catch(err => { | |
return next(err); | |
}); | |
}); | |
userSchema.methods.validatePassword = function(passwordGuess) { | |
return bcrypt.compare(passwordGuess, this.password); | |
}; | |
module.exports = mongoose.model('User', userSchema, 'users'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ./client/users/Users.js | |
import React from 'react'; | |
import axios from 'axios'; | |
class Users extends React.Component { | |
state = { | |
users: [], | |
}; | |
render() { | |
return ( | |
<ul> | |
{this.state.users.map(user => <li key={user._id}>{user.username}</li>)} | |
</ul> | |
); | |
} | |
componentDidMount() { | |
// get the token from somewhere | |
const token = localStorage.getItem('jwt'); | |
// attach the token as the Authorization header | |
const requestOptions = { | |
headers: { | |
Authorization: token, | |
}, | |
}; | |
axios | |
.get('http://localhost:5000/api/users', requestOptions) | |
.then(response => { | |
this.setState({ users: response.data }); | |
console.log(response.data); | |
}) | |
.catch(err => { | |
console.error(err); | |
}); | |
} | |
} | |
export default Users; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment