Routes:
Configure router
Build the forms.
If it's only for using local authentication, we can use middleware When users don't want to have a different username or password for everything they use, passport is helpful Examples: Using Github to log into things and using Google to log in to other things
Industry as a whole is moving toward OAuth Security experts say it's not perfect yet, but it's one of the best technologies we have. OAuth is about authorization but you can bring something else in to do authentication
OAuth 2 is go-to strategy companies use to do authentication
Firebase is a good service. Google bought it, so it's Google Firebase now Auth0 also has generous free tier
Authentication is you establishing your identity Authorization is you (as API user) saying once you know who someone is, what can they do
Role-based authorization is usually where people start
At the end of the day, you want to restrict whether or not a client can get to a resource
Difference between passport.use and passport.authenticate passport.use is how you tell passport the strategies you have, what middleware you have when you tell passport to use local middleware, somewhere passport is going to save an object that has a key of local and has all the configuration information your middleware (configuration object) will let you call express middleware later by calling the name of the key on that object When you say passport.authenticate with local, it says to read the configuration options and find out what it needs to do to use that
passport.use()
will register the strategy with passport
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')
const jwt = require ('jsonwebtoken')
const secret = 'Who but me? Loves JWT';
const port = process.env.PORT || 5000;
const User = require('./users/User');
const server = express()
const corsOptions = {
origin: 'http://localhost:3000',
credentials: true,
};
server.use(express.json());
server.use(cors(corsOptions));
// Helper Functions
const getTokenForUser = userObject => {
// creating a JWT and returning it.
// this function is more of a simple helper function than middleware,
// notice 'req, res, and next' are missing, this is because the auth is simple here.
// no need for custom middlware, just a helper function. :)
return jwt.sign(userObject, secret, { expiresIn: '1hr' })
};
const validateToken = (req, res, next) => {
// this piece of middleware is taking the token delivered up to the server and verifying it
// if no token is found in the header, you'll get a 422 status code
// if token is not valid, you'll receive a 422 status code
const token = req.headers.authorization;
if (!token) {
res.status(422).json({ error: 'No authorization token found on Authorization header.' })
} else {
jwt.verify(token, secret, (err, decoded) => {
if (err) {
res.status(401).json({ error: 'Token invalid, please log in', message: err })
} else {
// token is valid, so continue on to the next middleware/request handler function
next()
}
})
}
}
// Route Handlers
server.post('/api/users', (req, res) => {
const { username, password } = req.body;
const user = new User({ username, password });
user.save((err, user) => {
if (err) return res.send(err)
const token = getTokenForUser({ username: user.username })
res.json({ token });
});
});
server.get('/api/users', validateToken, (req, res) => {
User.find({})
.select('username')
.then(users => {
res.send(users);
})
.catch(err => {
return res.send(err);
});
});
server.post('/api/login', (req, res) => {
const { username, password } = req.body;
User.findOne({ username }, (err, user) => {
if (err) {
return res.status(500).json({ error: 'Invalid Username/Password });
}
if (!user) {
return res.status(422).json({ error: 'No user with that username in our DB' })
}
user.checkPassword(password, (err, isMatch) => {
if (err) {
return res.status(422).json({ error: 'passwords don't match' })
}
if (isMatch) {
const token = getTokenForUser({ username: user.username });
res.json({ token });
} else {
return res.status(422).json({ error: 'passwords don't match' });
}
});
});
});
// Connect to DB and start the API
mongoose.connect('mongodb://localhost/auth')
.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)
)
Question: What in the code is replacing Passport?
validateToken - not completely because you're not adding anything to request.user, but you could still do that
Are there any settings for how many times a user can attempt to log in?
Not on this server. Normally you'd use business logic for that, save it somewhere on the server and keep a count somewhere of failed attempts Don't know if libraries already implement that Might have a setting
Never, ever, ever trust the clients of your API Always validate
yarn add axios react-router-dom
axios to make HTTP requests
react-router-dom for routing
Simplest way to configure routing for React application: 'I tend to have my router at the highest possible level' Index.js
Index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-dom'
import './index.css'
import App from './App';
ReactDom.render(
<Router>
<App />
</Router>,
document.getElementById('root')
);
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>
{localStorage.getItem('token') && (
<button onClick={this.signout}> Sign out </button>
)} // this makes it so that it only renders the button when user is logged in
// this is on the App so that other routes have access to it (not just /users)
</header>
Routes go here
<Route path="/signin" component={Signin} />
<Route path="/users" component={Users} />
</div>
);
}
signout = () => {
localStorage.removeItem('token');
this.props.history.push('/signin')
}
}
export default withRouter(App);
Question: What about Switch?
I'm not going to use Switch. You normally use Switch when you want to make sure you only pick one route. This is going to be simple.
Signin.js
import React from 'react';
class Signin extends React.Component {
state = {
username: 'bilbo',
password: 'baggins'
}
render() {
return (
<form onSubmit={this.submitHandler}>
<div>
<label htmlFor="username" />
<input name="username"
value={this.state.username}
onChange={this.inputChangeHandler}
type="text" />
</div>
<div>
<label htmlFor="password" />
<input name="password"
value={this.state.password}
onChange={this.inputChangeHandler}
type="password">
</div>
<div>
<button>Sign in</button>
</div>
</form>
)
}
inputChangeHandler = (event) => {
const { name, value } = event.target
this.setState({ [name]: value })
}
submitHandler = (event) => {
event.preventDefault()
axios.post('http://localhost:5500/login', this.state)
.then(response => {
localStorage.setItem('token', response.data.token)
// localStorage is a global accessible object on window
this.props.history.push('/users');
}).catch(err => {
localStorage.removeItem('token') // if credentials are wrong, destroy token
}
}
}
export default Signin
What is the default behavior from a form when you hit submit?
How do we stop that?
Why the square brackets around the name?
const o = { name: 'Nalee' }
- two ways to get at that
o.name || o['name'][name]: value is the inverse
It's a way to have a dynamic updating of the state with a single event handler
Stole from React documentationtype="password" is what makes password appear as dots instead of text (UI gimmick) onSubmit on the form allows the user to submit the form by hitting enter instead of having to click the button
Response comes in as response.data 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 = event => {
const token = localStorage.getItem('token')
const authToken = Bearer ${token};
const requestOptions = {
headers: {
Authorization: authToken
}
};
axios.get('http://localhost:5500/users', requestOptions)
.then(response => {
this.setState({ users: response.data })
})
.catch(err => {
this.props.history.push('/signin');
})
}
}
export default Users
// Can save things in local storage in the browser and retrieve them by their key // Since App component is mounted in Index but not as part of a Route, it doesn't have this.props.history // So you have to import withRouter and when you export the App component you wrap it with withRouter // Can you clear login when you logout? Yeah, just set state to empty strings
Steps:
Signin
Not using single token for all users Separate token for each user New token after token has expired
Is localStorage domain-specific?
Any localStorage from Facebook will only be on Facebook Each page has its own localStorage environment
If you wanted to make it so that users not only had to be logged in, but had certain information .... Logged in and also has a role
// if you want roles, should add role to the userSchema or have it as an option in a put request // that way you can pass it to the token in order to use that to validate role
function makeToken(user) {
const timestamp = new Date().getTime();
const payload = {
sub: user._id,
iat: timestamp,
username: user.username,
role: user.role,
};
function checkRole(role) {
return function(req, res, next) {
if (role === req.user.role) {
next()
} else {
res.status(403).send('you have no power here')
}
}
}
server.post('/login', authenticate, roles('admin'), (req, res) => {
res.status(200).json({ token: makeToken(req.user), user: req.user })
})
server.get('/users', protected, roles('user admin'), (req, res) => {
Users.find()
.select('username')
.then(user => {
res.json(users)
})
.catch(err => {
res.status(500).json(err)
})
})
No logout for Passport - doesn't have sessions. Destroy token if you want to revoke access In order to combine tokens and being able to login on the server, they keep a blacklist of tokens Looks at JSON token ID and invalidates it when a user logs out Running list of all tokens On every request, it checks if the token is valid and then checks if it is blacklisted
When you're using tokens, the server does not save information about the logged in users No memory on the system about who is logged in Relies solely on the token being valid
Most people give you a token that has a short lifespan and a refresh token to maintain connection
Announcement at end: Patrick Kennedy did a brownbag on basics on textediting and using git and Github for success Looking at Brownbag .... video is available here: https://www.youtube.com/watch?v=z2_fW329LK8&feature=youtu.be