![logo_ironhack_blue 7](https://user-images.githubusercontent.com/23629340/40541063-a07a0a8a-601a-11e8-91b5-2f13e4e6b441.png) # React | Authentication ## Learning Goals After this lesson, you will be able to: - Understand how to use authentication in your React App - Make requests to your REST API sending credentials - Create a MERN application with `login`, `signup` and `logout` features ## Introduction We learned how to integrate the frontend (client side) and backend (server side) applications, where we developed a REST API on NodeJS/Express, and made requests from our React App. So we will continue working on these apps and incorporate users into them. ## Backend Set-Up Let's start from the backend/server side. Remember when you cloned `project-management-server` that there was a couple of commented out files – now is the time to go back and un-comment them. First, go to `models/user-model.js` and un-comment it. As we can see, our users will have a `username` and a `password`. Next, go to `app.js` and follow the steps given there. 1. Install the dependencies: ```bash $ npm install --save passport-local passport bcryptjs express-session connect-mongo ``` 2. Un-comment these lines: ```js // app.js ... const session = require('express-session'); const passport = require('passport'); require('./configs/passport'); ... ``` 3. Un-comment `configs/passport.js` 4. Add session settings: ```js // app.js ... // ADD SESSION SETTINGS HERE: const MongoStore = require('connect-mongo')(session); app.use(session({ secret: "doesn't matter in our case", // but it's required resave: false, saveUninitialized: false, // don't create cookie for non-logged-in user // MongoStore makes sure the user stays logged in also when the server restarts store: new MongoStore({ mongooseConnection: mongoose.connection }) })); ... ``` We use the `express-session` npm package to maintain the sessions and our users in it. 5. We installed `passport` and now want to configure our app to use it: ```js // app.js ... // USE passport.initialize() and passport.session() HERE: app.use(passport.initialize()); app.use(passport.session()); ... ``` This is the list of steps which are required in order to enable existence of users in our app. This is not the first time we are going through this process so we will not go in depth in explaining how and why to use `passport`. Now we have everything set up to proceed to creating `signup`, `login` and `logout` routes. All these routes will end up inside `routes/auth-routes.js`. Let's start with creating users, i.e. adding the `signup` route. :::info We will not have any GET routes since we are not rendering any pages on the server side. Our forms will be rendered on the client/React side. Also, the same as in other backend routes, we will use `.json()` to send/retrieve data to/from the database. ::: ```js // routes/auth-routes.js const express = require('express'); const authRoutes = express.Router(); const passport = require('passport'); const bcrypt = require('bcryptjs'); // require the user model !!!! const User = require('../models/user-model'); authRoutes.post('/signup', (req, res, next) => { const username = req.body.username; const password = req.body.password; if (!username || !password) { res.status(400).json({ message: 'Provide username and password' }); return; } if(password.length < 7){ res.status(400).json({ message: 'Please make your password at least 8 characters long for security reasons.' }); return; } User.findOne({ username }, (err, foundUser) => { if(err){ res.status(500).json({message: "Username check went bad."}); return; } if (foundUser) { res.status(400).json({ message: 'Username taken. Choose another one.' }); return; } const salt = bcrypt.genSaltSync(10); const hashPass = bcrypt.hashSync(password, salt); const aNewUser = new User({ username:username, password: hashPass }); aNewUser.save(err => { if (err) { res.status(400).json({ message: 'Saving user to database went wrong.' }); return; } // Automatically log in user after sign up // .login() here is actually a predefined passport method req.login(aNewUser, (err) => { if (err) { res.status(500).json({ message: 'Login after signup went bad.' }); return; } // Send the user's information to the frontend // We can use also: res.status(200).json(req.user); res.status(200).json(aNewUser); }); }); }); }); module.exports = authRoutes; ``` :::info Note how in `error` handlers, we don't render any more, since we won't have any views. We are using `res.status()` with error code and we are sending a `message` to be displayed on the frontend side. ::: Before we head to testing this route, don't forget we have to require `auth-routes.js` in `app.js`. ```js // app.js ... // ROUTES MIDDLEWARE STARTS HERE: const authRoutes = require('./routes/auth-routes'); app.use('/api', authRoutes); ... ``` Great. Let's head to **`Postman`** and see if we can create a user... ... and yes, we can successfully create a new user, so let's next proceed to the `login` route: ```jsx // routes/auth-routes.js ... authRoutes.post('/login', (req, res, next) => { passport.authenticate('local', (err, theUser, failureDetails) => { if (err) { res.status(500).json({ message: 'Something went wrong authenticating user' }); return; } if (!theUser) { // "failureDetails" contains the error messages // from our logic in "LocalStrategy" { message: '...' }. res.status(401).json(failureDetails); return; } // save user in session req.login(theUser, (err) => { if (err) { res.status(500).json({ message: 'Session save went bad.' }); return; } // We are now logged in (that's why we can also send req.user) res.status(200).json(theUser); }); })(req, res, next); }); ... ``` This route heavily depends on using `passport` and its methods (`authenticate()` and `login()`). Also, notice the `local` argument passed to *authenticate()*. It refers to the `local strategy` which is being used in `configs/passport.js`. At this point, we can test the route. Works perfectly! Amazing. Let's also add a route that simply returns the currently logged in user (or `null` if no user is logged in): ```jsx // routes/auth-routes.js ... // GET "/checkuser" allows the client to check to see: // (a) if we are logged-in // (b) the details of the logged-in user (if any) authRoutes.get("/checkuser", (req, res, next) => { if (req.user) { res.json({ userDoc: req.user }); } else { res.json({ userDoc: null }); } }); ... ``` Aaand.. let's finish up our backend routes with a `logout` route: ```jsx // routes/auth-routes.js ... authRoutes.post('/logout', (req, res, next) => { // req.logout() is defined by passport req.logout(); res.status(200).json({ message: 'Log out success!' }); }); module.exports = authRoutes; ``` There's just one more thing that we have to update as well. Our projects at this point can have their owners. First let's update `models/project-model.js` by adding an owner property: ```jsx // models/project-model.js ... const User = require('./user-model'); // <== !!! const projectSchema = new Schema({ title: String, description: String, tasks: [{type: Schema.Types.ObjectId, ref: 'Task'}], owner: {type: Schema.Types.ObjectId, ref: 'User'} // <== !!! }); ... ``` And then let's go to `routes/project-routes.js` and let's update the *POST* route to create a new project: ```jsx //routes/project-routes.js ... // POST route => to create a new project router.post('/projects', (req, res, next) => { Project.create({ title: req.body.title, description: req.body.description, tasks: [], owner: req.user._id // <== add this ! }) .then(response => { res.json(response); }) .catch(err => { res.json(err); }) }); ... ``` Now if we test this route again through `Postman`, we can see that our projects have owners! :clap: ## Frontend Set-Up :::info Remember, the client side is running on port `3000` (by default) and the server is running on port `5555`. ::: Eventually, down the road, we will have components for *signup* and *login* and each component will have an `axios` call to the corresponding route in the backend. This would be the same approach as we had while creating our *project* components. But we want to show you that you can organize things a bit differently by centralizing all the `axios` calls into one file that works like a *bridge* between the two apps - server and client. ### Auth Service Inside the `client/src` folder, create a file `api.js`. In this file, we will create all the functions that interact with our own backend/API. We will then use these functions from multiple places in our React app. Let's first add the signup-functionality to `api.js`: ```jsx // src/api.js import axios from "axios"; export const signup = (username, password) => { return axios.post('/api/signup', { username, password }) .then(response => response.data) } ``` This way whichever component wants to signup a user just imports this function and calls it with `username` and `password`. After receiving the response, we will pass the JSON data back to the component. ### `Signup` Component Now we need to create a `<Signup />` component. Inside the `components` folder create an `auth` folder and add a `Signup.js` file. Our component will have a `<form>`, and when the user clicks on the *Signup* button, it will call the `signup` function we just defined in `src/api.js`. First, let's set up our `Signup` component: ```jsx // auth/Signup.js import React, { Component } from 'react'; import { signup } from '../../api.js'; class Signup extends Component { constructor(props){ super(props); this.state = { username: '', password: '' }; } // handleChange() and handleSubmit() will be added here render(){ return( // more code will be added here ) } } export default Signup; ``` As you can see, we added the `signup` function from `api.js` for us to use it later. Let's add the form now as well as the methods to handle change and submit. ```jsx // auth/Signup.js import { Link } from 'react-router-dom'; // <== !!! ... handleFormSubmit = (event) => { event.preventDefault(); const username = this.state.username; const password = this.state.password; signup(username, password) .then(response => { this.setState({ username: "", password: "", }); // this.props.updateUser(response) }) .catch(error => console.log(error)) } handleChange = (event) => { const { name, value } = event.target; this.setState({ [name]: value }); } render() { return ( <div> <form onSubmit={this.handleFormSubmit}> <label>Username:</label> <input type="text" name="username" value={this.state.username} onChange={e => this.handleChange(e)} /> <label>Password:</label> <textarea name="password" value={this.state.password} onChange={e => this.handleChange(e)} /> <input type="submit" value="Signup" /> </form> <p>Already have account? <Link to={"/"}> Login</Link> </p> </div> ) } ... ``` Now we have set up our `<Signup />` component so let's call it and use it. In `App.js` let's import `Signup.js` and add one more route in between `<Switch />`: ```jsx // App.js ... import Signup from './components/auth/Signup'; ... <Switch> <Route exact path="/signup" component={Signup}/> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> ... ``` Let's test it. We can see that a new user is created in the database, so we managed to establish a connection between our two apps. :tada: :tada: However, so far we are not keeping track of our user. The way we will go about this is to introduce the state on the global level, or as we learned, to lift the state up to the `<App \>` component. This means we will have to add a `constructor()` to `App.js` and make some changes to get the user state to be set accordingly. ```jsx // App.js import React, { Component } from 'react'; import './App.css'; import { Switch, Route } from 'react-router-dom'; import ProjectList from './components/projects/ProjectList'; import Navbar from './components/navbar/Navbar'; import ProjectDetails from './components/projects/ProjectDetails'; import Signup from './components/auth/Signup'; class App extends Component { constructor(props){ super(props) this.state = { loggedInUser: null }; } updateTheUser = (userObj) => { this.setState({ loggedInUser: userObj }) } render() { return ( <div className="App"> <Navbar /> <Switch> <Route exact path='/signup' render={() => <Signup updateUser={this.updateTheUser}/>}/> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> </div> ); } } export default App; ``` Let's go to `Signup.js` and **un-comment** the line with **`this.props.updateUser(response)`**. Plus, let's explain what we are trying to do here: First, let's explain why we changed the `<Route />` component that holds the `<Signup />` component: by using `render` we allow passing props down to a component, in our case to the `Signup` component. Why we are passing a prop? Well, the line that we just un-commented in `Signup` holds the method that actually grabs the user object from `Signup` component and takes it up to `App.js`. The only way for us to pull this off was to remodel the way we rendered the `Signup` component by adding `render` and just replicating the usual way we pass properties (i.e. *<Signup updateUser={this.updateTheUser}/>*). Now that we have the `user` object in `App.js`, we actually use it when updating the state in `updateTheUser()` and set our `loggedInUser` to the passed `user` from the `Signup` component – our `App.js` now knows when we have a user in the current session. With the user available on a global level in `App.js` it is very easy to pass it down to any other component. Let's now demonstrate this with the `<Navbar />` component. Note that our `<Navbar />` is actually just a functional (stateless) component. In `App.js`, pass a `userInSession` down to `<Navbar />` as follows: ```jsx // App.js ... <Navbar userInSession={this.state.loggedInUser} /> ... ``` `userInSession` is the prop that's being passed to `Navbar` so now we can adapt what this component renders based on the value of this prop: ```jsx // navbar/Navbar.js ... const navbar = () => { if (this.props.userInSession) { return ( <nav className="nav-style"> <ul> <li>Welcome, {this.props.userInSession.username}</li> <li> <Link to='/projects' style={{ textDecoration: 'none' }}>Projects</Link> </li> </ul> </nav> ) } else { return ( <div> <nav className="nav-style"> <ul> <li><Link to='/signup' style={{ textDecoration: 'none' }}>Signup</Link></li> </ul> </nav> </div> ) } } export default navbar; ``` In general in React, `props` are externally passed in to a component by its parent component. Sometimes these props are hooked to **the state of the parent component**. So if the state of the parent component changes, the props passed to the component changes and it will be updated. As we just said, **something needs to change in the parent component in order to trigger an update in the child component**. To see the change in the `<Navbar />` component, we have now set up everything so its parent component (`App.js`) updates its state in order to pass the updated props to `<Navbar />`. Let's test! Looks like we are proper React developers by now! :relieved: Let's finish up authentication by adding `login` and `logout` functionalities. In `auth-service.js` add the following methods: ```jsx // src/api.js ... export const login = (username, password) => { return axios.post('/api/login', { username, password }) .then(response => response.data) } export const logout = () => { return axios.post('/api/logout', {}) .then(response => response.data) } ... ``` And let's add the respective `<Login />` component to our `auth` folder: ```jsx // auth/Login.js import React, { Component } from 'react'; import { login } from '../../api' import { Link } from 'react-router-dom'; class Login extends Component { constructor(props){ super(props); this.state = { username: '', password: '' }; } handleFormSubmit = (event) => { event.preventDefault(); const username = this.state.username; const password = this.state.password; login(username, password) .then( response => { this.setState({ username: "", password: "" }); this.props.updateUser(response) }) .catch( error => console.log(error) ) } handleChange = (event) => { const {name, value} = event.target; this.setState({[name]: value}); } render(){ return( <div> <form onSubmit={this.handleFormSubmit}> <label>Username:</label> <input type="text" name="username" value={this.state.username} onChange={ e => this.handleChange(e)}/> <label>Password:</label> <textarea name="password" value={this.state.password} onChange={ e => this.handleChange(e)} /> <input type="submit" value="Login" /> </form> <p>Don't have account? <Link to={"/signup"}> Signup</Link> </p> </div> ) } } export default Login; ``` Also we need to update `<Navbar />` with regards to both `login` and `logout` so our `<Navbar />` component will now look like this: ```jsx // components/navbar/Navbar.js import React from 'react'; import { Link } from 'react-router-dom'; import { logout } from '../../api' const logoutUser = (props) =>{ logout() .then(() => { props.updateUser(null); // sets the global user object to 'null' }) } const navbar = (props) => { if (props.userInSession) { return ( <nav className="nav-style"> <ul> <li>Welcome, {props.userInSession.username}</li> <li> <Link to='/projects' style={{ textDecoration: 'none' }}>Projects</Link> </li> <li> <Link to='/'> <button onClick={() => logoutUser(props)}>Logout</button> </Link> </li> </ul> </nav> ) } else { return ( <div> <nav className="nav-style"> <ul> <li><Link to='/' style={{ textDecoration: 'none' }}>Login</Link></li> <li><Link to='/signup' style={{ textDecoration: 'none' }}>Signup</Link></li> </ul> </nav> </div> ) } } export default navbar; ``` And finally, let's update `App.js`: ```jsx // App.js ... import Login from './components/auth/Login'; ... <Navbar userInSession={this.state.loggedInUser} updateUser={this.updateTheUser} /> <Switch> <Route exact path='/signup' render={() => <Signup getUser={this.getTheUser}/>}/> <Route exact path='/' render={() => <Login updateUser={this.updateTheUser}/>}/> <Route exact path="/projects" component={ProjectList}/> <Route exact path="/projects/:id" component={ProjectDetails} /> </Switch> ... ``` Now, whenever a user signs up or logs in/out the navbar adapts accordingly and shows a link to `Projects` and `Logout` (if the user is logged in) or a link to `Signup` and `Login` (if the user is not logged in yet). But when a user reloads the page (or first navigates to the page after some time) currently our React app does not know whether the user is already logged in. Let's set it up so React always knows this right away! In `client/src/index.js` remove the current call of `ReactDOM.render` - instead add: ```jsx ... document.getElementById('root').innerText = 'The React app has not connected to the backend yet.' axios.get('/checkuser').then(res => { ReactDOM.render( <Router><App user={res.data} /></Router>, document.getElementById('root')); }).catch(err => { alert('backend not running or /checkuser route not defined !') }) ... ``` This setup will now only render our React app as soon as it knows from the backend if any user is logged in and which user it is. Note that it passes a `user` prop into the `<App />` component. Let's adapt `App.js` to use this user prop as its initial state - in `App.js` adapt the constructor like this: ```jsx ... constructor(props) { super(props) this.state = { loggedInUser: this.props.user }; // <== adapt this ! } ... ``` Try reloading your page now and you will see the user now stays logged in :smiley: Pretty much we have everything we need in our app now. But we want our apps to be perfect and to protect our users and their projects. :muscle: Let's update the `<ProjectDetails />` component so the users can update and delete only their own projects, not everyone's. We will definitely use that `owner` property of our projects since it is nothing more or less then user's ID. So if `loggedInUser`'s ID matches `someProject.owner`, great, that means the project belongs to the currently logged in user. In any other case, sorry, read only :v: . :::info Note this makes sure only the owner of the current project sees 'edit' and 'delete' buttons, it does not actually prevent other users from editing and deleting the project (this will have to be configured in the backend). ::: ```jsx // projects/ProjectDetails.js ... ownershipCheck = (project) => { if(this.props.loggedInUser && project.owner == this.props.loggedInUser._id){ return ( <div> <div>{this.renderEditForm()} </div> <button onClick={() => this.deleteProject(this.state._id)}>Delete project</button> </div> ) } } render(){ return( <div> <h1>{this.state.title}</h1> <p>{this.state.description}</p> <div > {this.ownershipCheck(this.state)} </div> <Link to={'/projects'}>Back to projects</Link> </div> ) } ... ``` Now if we choose a project that was created by some other user, we will not see the `edit` and `delete` options. :bulb: **Advanced topic**: **Protecting routes** Our app already only shows a link to the `/projects` page if the user is logged in (this is implemented in the `<Navbar />` component). But of course the user could navigate to `/projects` directly by typing it in the browser (or because they have bookmarked the page). If we want to make sure when they are not logged in they get redirected to the login page in this case, in `App.js` we can replace the current `<Route />` to `/projects` with the following: ```jsx ... <Route exact path="/projects" render={() => { if (this.state.loggedInUser) { return <ProjectList /> } else { // if the user is not logged in, redirects to `/` return <Redirect to={{pathname: '/'}}/> } }} /> ... ``` Congrats! You are now one step away from your final project. Next - deployment. ## Summary We learned how to integrate backend and frontend applications that both include users and protected routes. Also, we lifted the state of `loggedInUser` so each component can keep track of the status of currently logged in user and get notified when the user logs out. ## Extra Resources - [React Forms](https://reactjs.org/docs/forms.html) - [Axios](https://github.com/axios/axios)